Skip to content

Bridges: DSA to LLMs

Hash tables to vector search and ANN

Fast lookup, from exact keys to nearest neighbors

A hash table gives up a little exactness to buy near-constant lookup. Vector search makes the same bargain in a thousand dimensions - and that bargain is the engine under every RAG system.

In Hashing and hash tables you learned the great trick of hashing: instead of scanning a list to find a key, you compute where it lives. A hash function maps the key straight to a bucket, and lookup drops from to on average. The price is subtle but real - you give up the ordered, exact structure of an array and trust a scramble to land like things together. Now suppose your “keys” are not strings but embeddings: 1536-dimensional vectors, one per document chunk, and you want the closest one to a query vector out of ten million. Comparing the query to all ten million is the same scan hashing was invented to kill. So we play the same card - a hash that sends nearby vectors to the same bucket - and search only that bucket. This lesson connects the two moves and builds the intuition for approximate nearest-neighbor (ANN) search.

The one-line idea

A hash table trades exactness of layout for lookup. Vector search trades exactness of the answer for sublinear search: a locality-sensitive hash drops each vector into a bucket so that close vectors collide, and a query only ever has to measure the handful of vectors sitting in its own bucket - not the whole corpus.

Recall: a hash table is an exactness-for-speed trade

A hash table stores key-value pairs in an array of buckets. To insert or look up a key , you compute a bucket index (for integer keys) and jump straight there. No scanning. When two different keys land in the same bucket - a collision - you resolve it locally (chaining or probing), and as long as collisions stay rare the average cost is . What you surrendered to get that speed is the array's ordered, exact structure: a hash table has no notion of “next key” or “nearest key,” only “same bucket or not.” That is a perfect deal when your question is exact-match - “is this key present?” It is useless the moment your question becomes nearest - “which stored vector is most similar to this one?” - because an ordinary hash function is designed to destroy locality: it scatters similar inputs to unrelated buckets on purpose, so the table fills evenly.

Locality-sensitive hashing: keep the locality on purpose

Semantic search flips the requirement. We want similar inputs to collide. A locality-sensitive hash (LSH) is a family of hash functions engineered so that the probability two vectors share a bucket grows as they get closer. The classic construction for angle/cosine similarity is a random hyperplane: pick a random direction and hash a vector by which side of that plane it falls on, . Two vectors get the same sign bit exactly when the random plane does not slip between them - which is more likely the smaller the angle between them.

Nearby vectors collide - provably

For a single random-hyperplane bit, the collision probability is a clean function of the angle between the two vectors:

Read it off the extremes: identical direction () collides with probability ; opposite direction () never collides. Concatenate independent bits and you get a -bit signature - a bucket key - where close vectors agree on most bits and so land in the same or an adjacent bucket, while far vectors scatter. That single monotone relationship between distance and collision is the entire foundation of LSH: the hash preserves neighborhoods instead of shredding them.

Hashing your query then narrows ten million candidates to the few dozen sharing its bucket - a sublinear shortlist you can afford to measure exactly. You have swapped a guarantee (the true nearest) for a probability (very likely the nearest, or close to it). That is approximate nearest-neighbor search. Drag the query below and watch the trade happen in real numbers.

Exact scan vs. LSH buckets drag the query · flip the mode

querynearest returnedtrue nearest (if missed)scanned bucket

Search mode:

Exact scan. The query is measured against all 48 points; nearest is #34. Every query pays for the whole corpus - that is the O(n) scan LSH is built to avoid.

This query
48
every point, O(n)
Nearest returned
#34
the exact hit
Full-scan total
48
48 × 1 queries
LSH total
5
saved 43 · 90%

The aha: the grid is the hash function

Each cell of the grid is a bucket, and a point's cell is its hash - a 2-D cartoon of the real thing (real LSH uses random hyperplanes, not axis-aligned squares). In Exact mode every query fires a line at all 48 points; the full-scan total rockets up by 48 each time you place the query. In LSH mode only the query's own cell is scanned - usually a handful of points - and the LSH total barely moves. Now drag the query to a cell boundary: sometimes the true nearest sits just across the line in the next bucket (dashed red), and LSH misses it. That miss is not a bug - it is the exactness you sold for speed.

From buckets to graphs: HNSW and the recall dial

Grid- and hyperplane-LSH have a weakness you just saw: a neighbor one cell over is invisible. Production vector databases (FAISS, Pgvector, Pinecone, Weaviate) more often reach for HNSW - a Hierarchical Navigable Small-World graph. Build a multi-layer graph where each vector links to its nearest neighbors, with sparse “express lanes” on the upper layers. To search, drop in at the top, greedily walk toward the query - always stepping to the neighbor that gets you closer - then descend a layer and repeat. Each query touches roughly nodes instead of all , and because the walk can cross the whole space in a few hops, it rarely gets stranded next to the wrong bucket. Same bargain as LSH, better geometry: give up the guarantee, keep almost all of the accuracy.

You pay in recall, and you get to pick the price

Exact search costs per query - all vectors, each numbers long. LSH and HNSW cut the candidate set so expected cost falls to a few buckets (LSH) or about hops (HNSW). The currency you spend is recall@ - the fraction of the true top- neighbors the index actually returns:

A real index is tuned to, say, : it misses a true neighbor about of the time, runs orders of magnitude faster, and for retrieval-augmented generation nobody notices - the LLM had ten decent chunks and only needed a few. Turn the index's knobs (more hyperplanes, more HNSW links, more probes) and you slide smoothly along the speed ↔ recall curve.

import numpy as np

# A signature = the sign bits of the vector against b random hyperplanes.
# Nearby vectors agree on most bits, so they land in the same bucket.
def signature(X, planes):          # X:(n,d) unit vectors, planes:(b,d)
    return (X @ planes.T) > 0       # (n,b) booleans -> a b-bit hash

planes = np.random.randn(16, d)     # 16 planes -> up to 2**16 buckets
buckets = {}
for i, sig in enumerate(signature(corpus, planes)):
    buckets.setdefault(sig.tobytes(), []).append(i)   # index by signature

# Query: hash it, then measure ONLY its bucket -- a shortlist, not millions.
key = signature(query[None], planes)[0].tobytes()
candidates = buckets.get(key, [])                    # sublinear shortlist
nearest = min(candidates, key=lambda i: np.linalg.norm(corpus[i] - query))
Random-hyperplane LSH, in NumPy

Check yourself

What must a hash for nearest-neighbor search do that an ordinary hash table hash must not?

Approximate nearest-neighbor search buys its sublinear speed by giving up the:

Why is vector search “the same trade as a hash table, generalized to high dimensions”?

Tie the two moves together. Try to state it, then check.

Lock it in

  • A hash table trades exact layout for lookup; vector search makes the same bargain in high dimensions, trading an exact answer for sublinear search.
  • An ordinary hash scatters similar keys on purpose; a locality-sensitive hash does the opposite - close vectors collide, with probability per random-hyperplane bit.
  • Hashing the query narrows millions of candidates to the handful in its bucket - a sublinear shortlist you measure exactly. That is approximate nearest-neighbor (ANN) search.
  • The price is recall@: you may miss a neighbor across a bucket boundary. HNSW plays the same bargain on a navigable graph walked in hops.

Primary source

Sedgewick & Wayne, Algorithms - hashing chapter (algs4.cs.princeton.edu)

For hash functions, buckets, collisions, and the average guarantee that this lesson generalizes, work through the hashing chapter of Sedgewick & Wayne's algorithms booksite. For the system this whole trick powers - retrieval-augmented generation, where an approximate-nearest-neighbor index over embedded chunks feeds an LLM - read Lewis et al., Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks (arxiv.org/abs/2005.11401).

Ask your teacher

This is the engine under RAG. When RAG “retrieves relevant chunks,” it embeds your question, then runs an approximate-nearest-neighbor query against a vector database - which is hashing generalized from integer keys to high-dimensional embeddings, so it can find the closest passages out of millions in milliseconds instead of scanning them all. Ask me how random-hyperplane LSH stacks multiple hash tables to fix the missed-neighbor problem, why HNSW usually wins in practice, how the speed↔recall knob is tuned for a real RAG pipeline, or where the attention wall and this sublinear-retrieval trick sit in the same system.