THIS EXPLANATION
THE ROOM
CDA·98 Computing, Data & AI 6 MIN · 8 STATIONS

Finding without searching

A Socratic walk-through of finding without searching — reasoned out one step at a time, not lectured.

abcdefgh
a

The question we started with

THE QUESTION #

How can a computer find one record among millions without examining any of the others?

Every search method we might sketch shares a shape: look at something, compare, decide where to look next. Even a very good one — halving a sorted list — still has to look thirty times to find one record in a billion. The cost falls slowly as the pile grows, but it does not stop rising.

Yet a hash table claims to find a record without looking at any other record, at any size. That does not sound like a faster search. It sounds like a different activity entirely. What is it doing instead?

b

Reasoning it through

REASONING #

Take the easiest possible case first. Suppose the keys are the integers 0 through 999, and you have an array of a thousand slots. Where do you put the record with key 412? In slot 412, obviously. And to find it later you do not search at all — you compute the address and read it. No comparison happens anywhere in that story.

That is direct addressing, and it is the whole idea. Notice what made it work: the key was already an address. So the real question is not "how do we search faster" but "when can a key be turned into an address?"

Now break the easy case. Real keys are email addresses, or URLs, or 64-bit integers. There are astronomically more possible keys than you will ever store, and you certainly cannot allocate a slot for every possible one. So you need a function that squeezes an enormous space of possible keys down into a small array of, say, a million slots.

What must immediately follow? If more keys exist than slots — and vastly more do — then some pairs of distinct keys must land in the same slot. That is the pigeonhole principle, and it is not a flaw in any particular hash function; it is arithmetic. Collisions are inevitable, so the design question was never how to avoid them but what to do when they happen.

The standard answer is disarmingly plain: each slot holds a small collection — a linked chain, or a run of nearby slots — and after computing the address you scan that collection. So we did not abolish searching. We shrank the region searched from the whole table to one slot's worth, and made the choice of region a computation instead of a comparison.

Now the guarantee. How long is that scan? If the table has m slots holding n keys and the hash scatters them evenly, each slot holds about n/m — the load factor — and a lookup costs a constant if we keep that ratio bounded, resizing when it grows. But look hard at the assumption "scatters them evenly". Even by whose choice? If I know your hash function, I can hand you a million keys that all collide, and your constant-time table becomes a linked list with a linear-time lookup.

That is not hypothetical. Hash-flooding was demonstrated as a practical denial-of-service technique against web frameworks — a few kilobytes of crafted form fields turning a request into quadratic work — most prominently in Klink and Wälde's 2011 disclosure. The fix was not a better fixed function but a keyed one, seeded with a secret at process start, so that the attacker cannot know which keys collide. SipHash was designed for exactly this in 2012 and adopted widely; Python has randomized string hashing on by default since 3.3.

That is worth stating precisely, because it changes the character of the claim. Constant-time lookup is an average-case result — an expectation over the randomness in the hash choice — not a worst-case one. The worst case remains linear. Universal hashing makes the expectation hold for every input, by moving the randomness from an assumption about the data into a coin the table itself flips.

c

The analogy

THE ANALOGY #
THE FIGURE

Think of a cloakroom where the ticket number is computed from your coat rather than issued in sequence. Hand over a navy wool overcoat and the attendant applies some fixed recipe to its colour, fabric and size, arriving at peg 73, and hangs it there. When you return, the same recipe on the same description sends the attendant straight to peg 73 — no walk down the rail, no examination of anyone else's coat. If two guests' coats happen to compute to 73, both hang there and the attendant checks the two of them.

WHERE IT BREAKS DOWN

the cloakroom recipe reads the coat, and reading it is not free — a hash must consume the entire key, so a "constant time" lookup is really constant in the number of records but linear in the length of the key, which is why hashing a long URL is not the same price as hashing a small integer.

d

Clarifying the model

THE MODEL #

Three refinements.

First, the answer to the original question is a little sharper than "it computes the location". It is that the data structure gives up on ordering to buy addressing. A sorted array knows which key is smallest, which is next, which lie between two bounds; a hash table knows none of that, because scattering keys evenly is the deliberate destruction of the order among them. Range queries, nearest-neighbour queries, and in-order traversal all become impossible or linear. That is the price, and it is why databases still keep B-trees around.

Second, collisions arrive far earlier than people expect. Fill a table of m slots and the first collision appears after roughly the square root of m insertions — the birthday-problem count — not after m/2. A table of a million slots typically collides before its thousandth key. Any design that treats collisions as a rare exception is mistaken about its own arithmetic.

Third, a good hash function is not the same as a cryptographic one. What a table needs is that the outputs spread and that an adversary cannot predict which keys land together; it does not need irreversibility. Cryptographic digests provide both but cost far more per byte, which is why keyed non-cryptographic functions became the norm for tables.

e

A picture of it

THE PICTURE #
Finding without searching
Finding without searching follow the arrows downward in time. The only comparisons in the whole exchange happen in the last message inside a single slot; the jump from the hash function to slot 73 is arithmetic, not search, which is why the note holds for a table of any size. {"generator":"[email protected]","source":"../Socrates/.diagram-cache/_src/finding-without-searching.md","sourceIndex":1,"sourceLine":4,"sourceHash":"3121a1461d2168b3e342108128f353e36e483a3a0ff79f4225b8d73b1308f4b9","diagramType":"sequence","layoutVariant":"source","repairedDuplicateIds":[],"motion":"entrance-with-reduced-motion-fallback","presentation":"editorial","attempt":1,"viewBox":{"x":0,"y":0,"width":929,"height":720},"qa":{"passed":true,"findings":[]}} Slot 73 chain 01 Slot array 02 Hash function 03 Caller 04 the other 999,999 slots were never touched look up this key mix every byte of the key index 73 hand over this slot only compare against the few keys here the matching record
KINDSlifelineparticipantmessage

How to readfollow the arrows downward in time. The only comparisons in the whole exchange happen in the last message inside a single slot; the jump from the hash function to slot 73 is arithmetic, not search, which is why the note holds for a table of any size.

f

What became clearer

WHAT CLEARED #
WHAT CLEARED

Hashing does not search faster — it converts the key into an address and then searches a region small enough not to matter. Everything else follows from that one move: collisions are forced by counting, the constant-time claim is an expectation rather than a guarantee, adversaries attack the expectation, and ordering is the thing sold to pay for it.

g

Where to go next

ONWARD #
  • Open addressing versus chaining, and why probe sequences (linear, quadratic, Robin Hood, cuckoo) behave so differently under high load.
  • Perfect hashing, where a static key set is known in advance and collisions can genuinely be eliminated.
  • Consistent hashing, which changes the question from "which slot" to "which machine" and cares about what happens when the number of slots changes.
h

Key terms

TERMS #
TermWhat it means
Direct addressingusing the key itself as an array index; possible only when the universe of keys is small enough to allocate a slot for each.
Load factorthe ratio of stored keys to slots; the quantity a table resizes to keep bounded.
Pigeonhole principleif there are more keys than slots, some slots must receive more than one key.
Universal hashingchoosing a hash function at random from a family, so that the expected collision count is bounded for every input rather than for typical ones.
Hash floodinga denial-of-service attack that supplies deliberately colliding keys to force worst-case behaviour.

Every term the collection defines is gathered in the glossary.

Nearby on the shelf

4