Skip to content

Hash Tables

O(1) average-case lookups for key-value storage


Overview

A hash table (also known as hash map or dictionary) is a data structure that implements an associative array, mapping keys to values. It uses a hash function to compute an index into an array of buckets, from which the desired value can be found.

What is a Hash Table?

Hash tables provide constant-time average-case complexity for insertions, deletions, and lookups, making them one of the most efficient data structures for key-value storage. The core idea involves two parts:

  1. Hash Function: Transforms the search key into an array index
  2. Collision Resolution: Handles cases where multiple keys hash to the same index

How Hash Tables Work

text
Key "apple" --> hash("apple") --> 42 --> bucket[42] --> value "red"

               +--------+
    Key   -->  | Hash   |  --> Index --> +--------+--------+--------+
               | Function|               | Bucket | Bucket | Bucket |
               +--------+                |   0    |   1    |  ...   |
                                         +--------+--------+--------+
                                                    |
                                                    v
                                                  Value

Key Characteristics:

  • Key-Value Pairs: Each entry consists of a unique key mapped to a value
  • Hash Function: Converts keys to array indices (ideally with uniform distribution)
  • Buckets: Array positions where values (or chains) are stored
  • Load Factor: Ratio of entries to total buckets (n/m), affects performance

When to Use Hash Tables

Use CaseWhy Hash Tables Work Well
Fast lookups by keyO(1) average-case access
Counting frequenciesIncrement counts in constant time
DeduplicationSets provide O(1) membership testing
Caching/MemoizationStore computed results for reuse
Graph adjacency listsMap nodes to their neighbors
Two Sum style problemsFind complements in O(n)

When to Consider Alternatives

LimitationBetter Alternative
Need ordered iterationBalanced BST (TreeMap)
Memory constrainedArray if keys are integers in small range
Range queriesSorted array with binary search
Need minimum/maximumHeap
Worst-case O(1) requiredPerfect hashing (rare)

Document Structure

ProblemDifficultyKey InsightLink
Two SumEasyComplement lookupLink
Contains DuplicateEasySet membershipLink
Valid AnagramEasyCharacter frequencyLink
First Non-Repeating CharacterEasyCount then scanLink
Ransom NoteEasyCharacter availabilityLink
Maximum Profit (Best Time to Buy/Sell Stock)MediumTrack min so farLink
Group AnagramsMediumSorted key groupingLink
Longest Consecutive SequenceMediumSet for O(1) neighbor checkLink
Subarray Sum Equals KMediumPrefix sum + hash mapLink
Top K Frequent ElementsMediumCount + bucket sort/heapLink
LRU CacheMediumHash map + doubly linked listLink
Design File SystemMediumPath to value mappingLink
Copy List with Random PointerMediumNode mappingLink
4Sum IIMediumCount pairs in two arraysLink
Longest Substring Without RepeatingMediumSliding window + setLink
Minimum Window SubstringHardTwo hash maps for windowLink
All O'one Data StructureHardHash map + doubly linked listLink
Design HashMapEasyImplement hash table from scratchLink

Hash Table Operations

OperationAverageWorstNotes
InsertO(1)O(n)Worst case when all keys collide
DeleteO(1)O(n)Same collision scenario
SearchO(1)O(n)O(n) requires pathological hash function
UpdateO(1)O(n)Search + overwrite
ResizeO(n)O(n)Rehash all elements

Load Factor and Performance

The load factor (alpha = n/m, where n = entries, m = buckets) critically affects performance:

Load FactorEffect
alpha < 0.5Excellent performance, more memory
alpha ~ 0.7Good balance (common target)
alpha > 0.8Performance degrades, consider resize
alpha > 1.0Only possible with chaining

Python's dict automatically resizes when load factor exceeds ~2/3, maintaining efficient operations.


Collision Resolution

When two keys hash to the same index, we have a collision. There are two main strategies to handle this.

Mermaid Diagram

Chaining vs Open Addressing

AspectChaining (Separate)Open Addressing (Closed)
StructureArray of linked listsSingle array with probing
Collision handlingAppend to chainFind next open slot
Load factorCan exceed 1.0Must stay below ~0.8
MemoryExtra pointers overheadMore compact
Cache performancePoor (pointer chasing)Better (contiguous memory)
DeletionSimple (remove from list)Requires tombstones
ImplementationEasierMore complex
Use caseWhen load factor unknownMemory-constrained systems

Chaining (Separate Chaining)

Each bucket contains a linked list of entries that hash to that index.

python
# Chaining implementation sketch
class HashTableChaining:
    def __init__(self, size=16):
        self.buckets = [[] for _ in range(size)]

    def _hash(self, key):
        return hash(key) % len(self.buckets)

    def put(self, key, value):
        idx = self._hash(key)
        for i, (k, v) in enumerate(self.buckets[idx]):
            if k == key:
                self.buckets[idx][i] = (key, value)
                return
        self.buckets[idx].append((key, value))

    def get(self, key):
        idx = self._hash(key)
        for k, v in self.buckets[idx]:
            if k == key:
                return v
        return None

Open Addressing

All elements stored in the array itself. When collision occurs, probe for next open slot.

Linear Probing: Check h(k), h(k)+1, h(k)+2, ...

  • Simple but causes clustering (consecutive occupied slots)

Quadratic Probing: Check h(k), h(k)+1^2, h(k)+2^2, ...

  • Reduces clustering but may not find empty slot even if one exists

Double Hashing: Use second hash function for step size

  • h(k, i) = (h1(k) + i * h2(k)) % m
  • Best distribution, most complex
python
# Linear probing implementation sketch
class HashTableLinearProbing:
    def __init__(self, size=16):
        self.keys = [None] * size
        self.values = [None] * size
        self.size = size

    def _hash(self, key):
        return hash(key) % self.size

    def put(self, key, value):
        idx = self._hash(key)
        while self.keys[idx] is not None:
            if self.keys[idx] == key:
                self.values[idx] = value
                return
            idx = (idx + 1) % self.size  # Linear probe
        self.keys[idx] = key
        self.values[idx] = value

Python dict & set Tips

Python's built-in dict and set are highly optimized hash tables. Master these for interviews.

Built-in Hash Table Usage

python
from collections import defaultdict, Counter
from typing import Dict, Set

# Basic dict operations
d: Dict[str, int] = {}
d['key'] = value           # Insert/update - O(1)
value = d['key']           # Access - O(1), raises KeyError if missing
value = d.get('key', 0)    # Access with default - O(1), no error
del d['key']               # Delete - O(1)
'key' in d                 # Membership - O(1)

# Set operations
s: Set[int] = set()
s.add(1)                   # Insert - O(1)
s.remove(1)                # Delete - O(1), raises KeyError if missing
s.discard(1)               # Delete - O(1), no error if missing
1 in s                     # Membership - O(1)

defaultdict - Auto-initialize Missing Keys

python
from collections import defaultdict

# Building adjacency list for graph
graph = defaultdict(list)
graph['a'].append('b')     # No KeyError, auto-creates empty list
graph['a'].append('c')
# graph = {'a': ['b', 'c']}

# Counting with defaultdict
word_count = defaultdict(int)
for word in words:
    word_count[word] += 1  # No need to check if key exists

# Grouping items
groups = defaultdict(list)
for item in items:
    groups[item.category].append(item)

Counter - Frequency Counting

python
from collections import Counter

# Count character frequencies
counts = Counter("abracadabra")
# Counter({'a': 5, 'b': 2, 'r': 2, 'c': 1, 'd': 1})

# Most common elements
counts.most_common(2)      # [('a', 5), ('b', 2)]

# Counter arithmetic
c1 = Counter("aab")
c2 = Counter("ab")
c1 - c2                    # Counter({'a': 1}) - subtract counts
c1 + c2                    # Counter({'a': 3, 'b': 2}) - add counts
c1 & c2                    # Counter({'a': 1, 'b': 1}) - intersection (min)
c1 | c2                    # Counter({'a': 2, 'b': 1}) - union (max)

# Check if anagram (useful pattern!)
Counter(s1) == Counter(s2) # True if anagrams

Common Interview Patterns

python
# Two Sum Pattern - O(n) with hash map
def two_sum(nums, target):
    seen = {}  # value -> index
    for i, num in enumerate(nums):
        complement = target - num
        if complement in seen:
            return [seen[complement], i]
        seen[num] = i
    return []

# Frequency Count Pattern
def find_majority(nums):
    counts = Counter(nums)
    for num, count in counts.items():
        if count > len(nums) // 2:
            return num
    return -1

# Grouping Pattern (Group Anagrams)
def group_anagrams(strs):
    groups = defaultdict(list)
    for s in strs:
        key = tuple(sorted(s))  # Sorted chars as key
        groups[key].append(s)
    return list(groups.values())

# Set for O(1) Lookup (Longest Consecutive Sequence)
def longest_consecutive(nums):
    num_set = set(nums)
    max_len = 0
    for num in num_set:
        if num - 1 not in num_set:  # Start of sequence
            length = 1
            while num + length in num_set:
                length += 1
            max_len = max(max_len, length)
    return max_len

Hashable Requirements

Only hashable objects can be dictionary keys or set members:

python
# Hashable (immutable) - CAN be keys
d[42] = "int"
d["hello"] = "string"
d[(1, 2, 3)] = "tuple"
d[frozenset([1, 2])] = "frozenset"

# NOT hashable (mutable) - CANNOT be keys
# d[[1, 2, 3]] = "list"      # TypeError!
# d[{1, 2, 3}] = "set"       # TypeError!
# d[{'a': 1}] = "dict"       # TypeError!

# Convert to hashable for use as key
tuple([1, 2, 3])             # (1, 2, 3)
frozenset({1, 2, 3})         # frozenset({1, 2, 3})

When to Use Hash Tables

Pattern Recognition

If you see...Think about...
"Find if exists"Hash set for O(1) lookup
"Count occurrences"Counter or defaultdict(int)
"Find pair that sums to"Hash map for complement
"Group by property"defaultdict(list)
"Remove duplicates"Convert to set
"First/last occurrence"Hash map storing index
"Subarray with sum K"Prefix sum in hash map
"Longest substring with constraint"Sliding window + hash map/set
"Design with O(1) operations"Hash map + linked list

Decision Flowchart


Interview Focus Areas

Based on recent SDE interview patterns (2025-2026), these hash table topics are most frequently tested:

High Priority Topics

  1. Two Sum and Variants - The classic complement lookup problem appears in multiple forms
  2. Frequency Counting - First non-repeating character, top K frequent elements
  3. Grouping Problems - Group anagrams, categorize by property
  4. Subarray Problems with Hash Maps - Subarray sum equals K using prefix sums
  5. Design Questions - LRU Cache, Design HashMap, All O(1) Data Structure

Common Google Hash Table Questions

Question TypeExample ProblemKey Insight
Complement Lookup"Find two numbers summing to target"Store values in map, check for complement
Frequency Analysis"Find first non-repeated character"Count, then scan for count == 1
String Matching"Check if strings are anagrams"Counter comparison
Subarray Sum"Count subarrays with sum K"prefix_sum - K in map
LRU Cache Design"Implement cache with O(1) get/put"Hash map + doubly linked list
File System Design"Create/get paths in virtual FS"Path string to value mapping

Real Interview Questions (Reported 2025-2026)

  1. Simple hashmap implementation - Followed by questions about hash functions and collision handling
  2. File system design (LC 1166) - Use HashMap to solve with follow-ups about performance
  3. HashMap vs Trie decision - When to use each for dictionary storage
  4. Find pairs that add up to 10 - Classic two-sum variant
  5. Longest consecutive subsequence - Use set for O(1) neighbor checking

What Interviewers Look For

  1. Understanding Tradeoffs

    • Why O(1) average but O(n) worst case?
    • When would you use TreeMap vs HashMap?
    • Memory vs time tradeoffs
  2. Hash Function Knowledge

    • What makes a good hash function?
    • How does Python hash strings vs integers?
    • What happens with poor distribution?
  3. Implementation Details

    • How does resizing work?
    • Explain chaining vs open addressing
    • How to handle deletions in open addressing?
  4. Code Quality

    • Use appropriate Python idioms (Counter, defaultdict)
    • Handle edge cases (empty input, all duplicates)
    • Discuss complexity confidently

Interview Strategy


Practice Progression

Week 1: Foundations

DayFocusProblems
1Basic OperationsTwo Sum, Contains Duplicate, Valid Anagram
2Frequency CountingFirst Non-Repeating Character, Ransom Note
3Set OperationsLongest Consecutive Sequence, Intersection of Two Arrays

Week 2: Intermediate

DayFocusProblems
4GroupingGroup Anagrams, Top K Frequent Elements
5Prefix Sum + HashSubarray Sum Equals K, Contiguous Array
6Sliding Window + HashLongest Substring Without Repeating, Minimum Window Substring

Week 3: Advanced & Design

DayFocusProblems
7Design ProblemsDesign HashMap, LRU Cache
8Complex DesignAll O'one Data Structure, Design File System
9Mixed PracticeRandom selection from all patterns

Quick Reference Card

Time Complexity Cheat Sheet

text
Insert:     O(1) average, O(n) worst
Lookup:     O(1) average, O(n) worst
Delete:     O(1) average, O(n) worst
Iterate:    O(n)
Resize:     O(n)

Python Built-ins

python
dict()              # Empty dictionary
set()               # Empty set (NOT {} which is dict!)
{k: v for k, v in pairs}  # Dict comprehension
{x for x in items}  # Set comprehension
defaultdict(list)   # Auto-initialize with empty list
defaultdict(int)    # Auto-initialize with 0
Counter(iterable)   # Frequency counter

Common Mistakes to Avoid

MistakeCorrect Approach
d[key] on missing keyUse d.get(key, default)
Using mutable as keyConvert to tuple/frozenset
Modifying dict while iteratingIterate over list(d.keys())
{} for empty setUse set()
Forgetting hash collisions existMention in complexity analysis

Resources

Further Reading


Last updated: January 2026

Hash tables are the workhorse of technical interviews. Their O(1) average-case operations make them essential for optimizing brute-force solutions. Master the patterns here, and you'll transform many O(n^2) algorithms into elegant O(n) solutions.