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.