In a chained hash table, why does inserting n colliding keys cost quadratic time?
answer
- what does insert check before storing?
- the chain grows by one each time
- the k-th insert walks k-1 nodes
- sum 1 + 2 + ... + (n-1)
- arithmetic series is quadratic
basics
~20 sEvery insert first scans its bucket to check whether the key is already present. When all n keys share one bucket, the k-th insert walks k-1 nodes, so the total is 1+2+...+(n-1), which is Theta(n squared).
solid answer
~40 sInsertion into a chained table is not just "append to a list": the table must decide whether this key already exists, and the only way to know is to walk the bucket comparing keys. Under normal input that walk is a constant few nodes. If an attacker supplies n keys that all reduce to one bucket, the chain grows by one on every insert, so the k-th insert compares against k-1 entries. Summing the arithmetic series gives `n(n-1)/2` comparisons — Theta(n^2). The attacker's cost is only the bytes of n keys, which is why a few hundred kilobytes of parameter names can cost seconds of CPU. Resizing does not save you: keys engineered to share the *full* hash value collide at every bucket count.
code
pseudocode · 10 lines// insert(key, value) into a chained table with m buckets
b = hash(key) mod m
node = buckets[b]
while node != nil:
if node.key == key: // duplicate check: walks the chain
node.value = value
return
node = node.next
// not found: attach a new node at the head
buckets[b] = make_node(key, value, buckets[b])go deeper
Recall that an insert must check whether the key already exists, and that this check scans the bucket. That single fact is what turns many collisions into a growing cost per operation.
Derive the sum out loud: chain length k-1 at the k-th insert, total n(n-1)/2, so Theta(n^2). Be ready to say why a bigger table does not help when the full hash values are equal.
Connect the arithmetic to an incident: bytes-in versus CPU-out amplification, the profiler fingerprint of time spent in key comparison, and why the offline-precomputed payload works against every deployment of the same software.
Frame it as an asymmetric cost the platform absorbs on every team's behalf, and judge whether the answer is bounding n at the parse layer, bounding per-bucket cost, or removing predictability from the hash.
## The insert that isn't O(1) A chained hash table stores each bucket as a linked list of entries. Insert looks like this: 1. compute the hash of the key and reduce it to a bucket index; 2. **walk that bucket's chain**, comparing the new key against each stored key, to find an existing entry; 3. if found, overwrite the value; otherwise attach a new node. Step 2 is where the cost hides. Map semantics require keys to be unique, so the duplicate check is not optional — it is the reason insert is proportional to the length of the chain it lands in, not to a constant. ## The series Suppose all n keys reduce to the same bucket. Chain length before the k-th insert is `k-1`, and the duplicate check walks all of it (no match is found, so it walks to the end). Total comparisons: ``` 0 + 1 + 2 + ... + (n-1) = n(n-1)/2 ``` That is Theta(n^2). With n = 10,000 keys it is roughly 50 million key comparisons, each one a full comparison of two strings that were deliberately built to be long-ish and equal in their cheap prefix checks. A single request has turned into seconds of CPU. Notice the asymmetry that makes this an attack rather than a bug report: the attacker pays for n bytes, the server pays for n^2 work. Amplification is the whole point. ## Same hash versus same bucket Two different targets, both usable: - **Same bucket index.** The index is `hash(key)` reduced modulo a bucket count `m`, and `m` is small — thousands, not billions. Even against a strong unkeyed hash, an attacker can generate random candidate keys and keep the ones landing in the chosen bucket; roughly one in `m` candidates qualifies. Cheap, and it requires no weakness in the function at all. - **Same full hash value.** For simple polynomial or additive hashes over strings, colliding pairs can be *constructed* algebraically: find two short fragments with equal hash contribution, and every concatenation of choices from those two fragments collides, giving exponentially many colliding keys from a tiny search. This is the stronger version, and it survives resizing. That second point matters for a common wrong answer. "The table will resize and split the chain" is only true when the keys merely shared an index; if their full hash values are identical they collide again in the new table, and the resize has simply spent extra work rehashing them. ## Precompute once, replay forever An unkeyed hash function is deterministic and identical in every process on every machine running that software. So the collision set is computed **offline, once**, and then works against every deployment of that software forever. There is no per-target work, which is why published proof-of-concept payload files exist and why this scales to a mass attack. ## Does another collision-resolution scheme escape it? No — the failure is about bucket occupancy, not about the shape of the overflow storage. With open addressing and linear probing, n keys targeting one slot form one long occupied run; the k-th insert probes about k slots before finding space, and the total is again quadratic, with deleted-entry markers making it worse. Any scheme that resolves collisions by *searching* inherits the shape. The schemes that do not are the ones that bound the search: an over-long bucket promoted to a balanced search structure gives O(log n) per operation, so n inserts cost O(n log n) instead of O(n^2). That is a damage bound, not a prevention — it does not stop the collisions, it just makes them affordable. ## Reading the cost correctly Two precision points interviewers listen for: - The bound is on a **worst-case sequence**, and it is not "amortized away". Amortization covers the resizing cost of growth-doubling, which really is O(1) per insert averaged over a sequence. It says nothing about chain scanning, because there is no cheap-operation surplus to pay for the expensive ones — every insert in this sequence is expensive. - The dominant term is **comparisons**, not hashing. Hashing each key is linear in key length and happens once per insert; the quadratic term comes from repeated traversal, so profiling shows time inside equality comparison, which is a useful diagnostic fingerprint. Ecosystems diverged on the remedy for exactly this arithmetic: some mainstream runtimes (Python, Ruby, Perl) made the hash unpredictable per process, while others capped the number of accepted request parameters or bounded the bucket cost with an ordered fallback structure.
- How does an attacker produce thousands of keys for one bucket?Two ways. Against any unkeyed function, generate candidates and keep those whose index matches the target — about one in `m` qualifies, and `m` is small. Against simple polynomial string hashes, construct equal-hash fragments algebraically and concatenate them, yielding exponentially many colliding keys from a tiny search. Both are done offline once and replayed.
- Does open addressing avoid the quadratic blowup?No. With linear probing, keys aimed at one slot form a single long occupied run, the k-th insert probes roughly k slots, and the total is quadratic again; deletion markers make it worse. The blowup comes from concentrated occupancy, not from the choice of chaining versus probing.
- Why doesn't amortized analysis rescue the insert cost here?Amortization pays for occasional expensive operations out of a surplus from cheap ones — that is how growth-doubling resizes average to O(1). Under this attack there are no cheap inserts to draw from: every one scans a chain that keeps growing, so the total over the sequence really is quadratic, not a rare spike.
saying these in an interview costs you the question
- Says resizing splits the chain and fixes it
- Calls the total linear because inserts are amortized O(1)
- Thinks duplicate keys, not colliding keys, cause the cost
- Believes only a broken hash function permits it
- Claims hashing cost dominates rather than chain traversal