Hashing
Quick reference
| Insert / lookup / delete (average case) | O(1) |
| Insert / lookup / delete (worst case) | O(n) |
Remember from Arrays & Strings Fundamentals that array indexing is O(1). The address comes directly from the index: base + index Γ size. This is only arithmetic, with no search. Hashing uses this same trick for keys that are not already small, simple integers. A hash function takes any key, for example a word, a tuple, or a full object. It changes the key into a number. The computer uses that number as a plain array index. When you look up "apple" in a hash table, the computer changes "apple" into a number. That number tells you exactly which slot in a backing array to check. No scan is necessary.
Imagine a library that puts books on shelves by a formula from the title, not in alphabetical order. For any title, you compute the formula and go directly to the shelf. You do not browse. This is the whole idea. You trade some memory (the backing array must have room for the possible outputs of the formula) for almost immediate lookup.
Structures
structure HashMap:
buckets // a plain array (Arrays & Strings Fundamentals), one slot for each possible hash value
size // the number of key/value pairs that are stored now
function hash(key):
return someFormula(key) % buckets.length // changes any key into a valid array indexcollisions β when two keys land in the same bucket
// Two different keys can hash to the same bucket index. This is a collision.
// One common fix is chaining: each bucket holds a small list instead of one slot.
function insert(map, key, value):
bucketIndex = hash(key)
if map.buckets[bucketIndex] is empty:
map.buckets[bucketIndex] = new list
map.buckets[bucketIndex].add((key, value)) // Add to the chain. Do not overwrite it.frequency count β hash map
function countFrequencies(items):
counts = new HashMap
for item in items:
counts[item] = counts.get(item, default=0) + 1
return countsmembership check β hash set
function hasDuplicate(items):
seen = new HashSet
for item in items:
if item in seen: // O(1) average: a hashed lookup, not a scan
return true
seen.add(item)
return falseThe "average" word is more important here than almost anywhere else. Remember the difference between the average case and the worst case from Big-O Notation. The speed of a hash table depends on a hash function that spreads the keys well. When this is true (the normal case), lookups are almost immediate.
| Operation | Time | Space | Why |
|---|---|---|---|
| Insert / lookup / delete (average case) | O(1) | O(1) per operation (O(n) total for n entries) | A good hash function spreads the keys evenly. So each bucket holds close to zero or one entries. To check a bucket is almost constant work. |
| Insert / lookup / delete (worst case) | O(n) | O(1) per operation | If every key goes to the same bucket (a bad hash function, or an adversarial input made to collide), that bucket's chain grows to length n. A search of the chain is a full scan. This is rare in practice, but real. |
Using a mutable value as a key
The computer computes the hash of a key from its contents at insert time. If the key is a mutable object and its contents change later, a new hash would be different. But the table does not compute the hash again. So the entry is now in the wrong bucket for the current key. Future lookups for it fail with no error. A key must be a value that does not change after you store it.
Assuming iteration order matches insertion order
Some hash map implementations keep the insertion order, or offer a variant that does. Other implementations give no such guarantee and can return the entries in an order that looks random. Code that depends on order without a check of the specific language guarantee works by accident in one language and fails in another.
Reaching for hashing when order actually matters
Hashing gives a fast answer to "is this here" or "what is paired with this". It does not give order. A hash set or map does not keep the keys sorted. A loop over one does not give useful order information. Problems that need "the smallest", "in sorted order", or "the next largest" need a different tool, even if fast lookup is also part of the problem.
Collisions silently degrading performance
Code that assumes every hash map operation is O(1) can be O(n) per operation on data that collides a lot. An attacker who knows your hash function can sometimes make exactly this input on purpose. "Average case O(1)" is a property of typical data, not a guarantee for all data.
Sign in to mark problems done β progress syncs across devices.