CMD Guide
HomeDSAHashing

Hashing, Collisions, Overflow, and Resizing in Hashtables

A hashtable collides when two distinct keys are squeezed by hash(key) mod capacity into the same bucket index — an unavoidable outcome of the pigeonhole principle once you map an unbounded key space onto a fixed-size array, and how you resolve that collision (probe elsewhere vs. chain) determines whether the table degrades gracefully or silently loses data.

Recognize the pattern

Brute force → optimal

Brute force (naive): a fixed array of size m where insert(key) writes directly to arr[hash(key) % m], silently overwriting whatever was there. This is O(1) only in the collision-free case, but it is incorrect — data loss on any collision, and it fails outright once more than m keys arrive (overflow).

Optimal: pair the hash function with a collision-resolution strategy (chaining or open addressing) and a resizing policy that rebuilds the table before the load factor climbs too high. This trades a small constant-factor overhead per operation for correctness and amortized O(1) behavior at any table size.

Collision resolution: two families

Chaining (open hashing)

Each bucket holds a small list (or tree, for very long chains) of all entries that hashed there. Insert appends to the bucket's list — O(1) to append, but a full lookup must scan the chain, costing O(chain length). Never overwrites; capacity is not a hard ceiling since chains can grow, though very long chains defeat the point of hashing.

Open addressing (closed hashing)

On collision, probe an alternate slot inside the same array using a deterministic sequence: linear probing ((h+i) % m), quadratic probing ((h+i²) % m), or double hashing ((h1 + i·h2) % m). Everything lives in the array itself — better cache locality than chaining, but the table has a hard capacity and clusters of filled slots ('primary clustering', specific to linear probing) make later probes longer. Quadratic probing reduces primary clustering but suffers milder 'secondary clustering'; double hashing approximates uniform probing and avoids both. Deletions need a 'tombstone' marker so later lookups don't stop probing too early.

Overflow and resizing

Overflow is not a corner case to patch after the fact — it is the reason resizing exists. Track load factor α = n / m (entries / capacity). When α crosses a threshold (commonly 0.7–0.75), allocate a new array of roughly double the capacity and rehash every existing key into it (bucket indices depend on m, so old slots are not simply copied). This single rehash costs O(n), but because doubling happens exponentially rarely, the amortized cost per insert stays O(1).

Complexity, derived

Chaining: with n keys in m buckets and a reasonably uniform hash, the expected chain length is α = n/m. Expected lookup/insert = O(1 + α); if the resize policy keeps α ≤ 1, that's O(1) expected. Space = O(n + m) for the array plus node overhead per entry.

Open addressing — the numbers depend on the probe sequence, not just on α: for linear probing specifically, Knuth's analysis gives expected probes for a successful search ≈ ½(1 + 1/(1−α)) and for an unsuccessful search ≈ ½(1 + 1/(1−α)²) — the squared term captures the extra cost of primary clustering. For double hashing, which closely approximates the idealized 'uniform hashing' assumption (each probe sequence is equally likely to be any permutation of slots), the results are meaningfully better: successful ≈ (1/α)·ln(1/(1−α)), unsuccessful ≈ 1/(1−α). Both families blow up as α → 1, which is why open addressing needs a lower resize threshold (~0.7) than chaining tolerates, but linear probing degrades fastest of the three schemes — the reason production hash maps that use open addressing (e.g. CPython's dict) favor perturbed/pseudo-random probing over plain linear probing. Space = O(m) — no per-node pointer overhead, but some slots are always wasted as headroom.

Resizing amortized cost: across a sequence of n inserts that trigger doublings at sizes 1, 2, 4, …, n, total rehash work = 1+2+4+…+n = O(2n) = O(n), i.e. O(1) amortized per insert — the classic geometric-series argument, identical to dynamic-array (ArrayList) growth analysis.

Worked example (chaining, m = 10)

Keyhash(key) % 10Bucket after insert
10088bucket 8: [1008]
10099bucket 9: [1009]
10100bucket 0: [1010]
10111bucket 1: [1011]
10211collision → bucket 1: [1011, 1021]

Load factor after 5 inserts: α = 5/10 = 0.5, still healthy. Had this been open addressing with linear probing, inserting 1021 would instead scan bucket 1 (occupied) → bucket 2 (empty) and place it there, leaving bucket 1 holding only 1011.

Java: separate chaining with resize

class HashMapChain<K, V> {
    static class Node<K, V> { K key; V val; Node<K, V> next;
        Node(K k, V v, Node<K, V> n) { key = k; val = v; next = n; } }

    private Node<K, V>[] buckets;
    private int size = 0;

    @SuppressWarnings("unchecked")
    HashMapChain(int capacity) { buckets = new Node[capacity]; }

    void put(K key, V val) {
        int i = idx(key, buckets.length);
        for (Node<K, V> n = buckets[i]; n != null; n = n.next)
            if (n.key.equals(key)) { n.val = val; return; }
        buckets[i] = new Node<>(key, val, buckets[i]);
        size++;
        if ((double) size / buckets.length > 0.75) resize();
    }

    V get(K key) {
        for (Node<K, V> n = buckets[idx(key, buckets.length)]; n != null; n = n.next)
            if (n.key.equals(key)) return n.val;
        return null;
    }

    @SuppressWarnings("unchecked")
    private void resize() {
        Node<K, V>[] old = buckets;
        buckets = new Node[old.length * 2];
        for (Node<K, V> head : old)
            for (Node<K, V> n = head; n != null; n = n.next) {
                int i = idx(n.key, buckets.length);
                buckets[i] = new Node<>(n.key, n.val, buckets[i]);
            }
    }

    private int idx(K key, int cap) { return (key.hashCode() & 0x7fffffff) % cap; }
}

Security: Hash Collision DoS (Denial of Service)

In production applications, hash maps are frequently used to parse user-supplied data (such as JSON API payloads or HTTP query parameters). This exposes a severe security vulnerability known as a Hash Collision DoS attack:

Pitfalls

When to use / when not, vs. alternatives

Chaining — use when key sizes are unpredictable, deletions are frequent, or the load factor may exceed 1; the trade-off is pointer-chasing overhead and worse cache locality per lookup.

Open addressing — use when memory locality matters and load factor is kept below ~0.7 (e.g. CPython's dict, which uses open addressing with a pseudo-random probe sequence rather than plain linear probing to avoid primary clustering); the trade-off is a hard capacity, trickier deletion, and clustering risk under a weak hash/probe sequence.

Named alternative — balanced BST (e.g. TreeMap): gives O(log n) worst-case guaranteed (no pathological O(n) collision chains) and sorted-order iteration, at the cost of losing average O(1) access. Choose a hashtable when you need average-case O(1) and don't need ordering; choose a BST when adversarial inputs or ordered traversal matter.

Takeaways

Recall: Why does resizing a hashtable at capacity m require rehashing every key instead of just copying old slots into a bigger array?


Adapted and expanded from the Hashing, Collisions, Overflow, and Resizing course notes for interview-prep depth (mechanism, complexity derivation, worked trace, and trade-off analysis added).

🤖 Don't fully get this? Learn it with Claude

Stuck on Hashing, Collisions, Overflow, and Resizing in Hashtables? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.

🎨 Explain it visually

Build the mental picture, not memorization.

I just read a lesson on **Hashing, Collisions, Overflow, and Resizing in Hashtables** (DSA) and want to truly understand it. Explain Hashing, Collisions, Overflow, and Resizing in Hashtables from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🤔 Walk me through it (interactive)

Socratic — adapts to where you're stuck.

Teach me **Hashing, Collisions, Overflow, and Resizing in Hashtables** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧪 Quiz me & fix my gaps

Active recall exposes what you missed.

Quiz me on **Hashing, Collisions, Overflow, and Resizing in Hashtables** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧠 Make it stick

Intuition + hook + flashcards for long-term memory.

Help me remember **Hashing, Collisions, Overflow, and Resizing in Hashtables** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.

📝 My notes