GO TO A SECTION

↑ ↓ to navigate · Enter to openExplore all sections →
FDEInterviews logo
← 💻 Coding & Engineering Craft
Foundational

Big-O That Actually Matters

On a deployment, Big-O is not a whiteboard puzzle; it is the one calculation that tells you whether the customer's data fits in the approach you picked. The skill is spotting the term that dominates at their scale, knowing when brute force dies and you need an index or ANN, and recognizing when constant factors and memory decide the outcome instead of the exponent.

WATCH THE EXPLAINER3:22
Transcript

In the interview, you label a snippet O(n log n). On a deployment the question is harder. At the data size this customer has, which line of code decides whether the feature is feasible at all?

The method is short. Estimate N. Find the dominant term. Check it against your latency and memory budget. Everything else is noise, so ignore it.

The case that comes up constantly is brute-force similarity search. The obvious code compares the query to every vector: N dot products over d dimensions, so O(N * d) per query. Multiply it out. At 10,000 vectors that's 15 million multiply-adds. At 50 million, with 1,536 dimensions, it's 76.8 billion. On one CPU core, a third of a millisecond against about 1.5 seconds, for a single query. That throughput is an order-of-magnitude estimate. A GPU moves the number, but not the shape.

And you run one of these per user request. Brute force is fine until N crosses the line where it blows your latency budget. Past it, it doesn't get slow. It stops being viable. That's the signal to reach for an index. An approximate nearest neighbor structure, like HNSW or IVF, trades exact answers for sub-linear query time, turning a dead feature into a 10 ms lookup. You give up a little recall, and tune it to around 95 percent, an operating point you choose.

The other workhorse is the hash lookup. For each order, scan every customer. With N orders and M customers, that's O(N * M), the classic accidental quadratic. Build a set of the M side first, and each lookup drops from O(M) to O(1). The whole join becomes O(N + M). Both return the same result. The second one ships.

The page timed it on one laptop core, 20,000 orders against 20,000 customers. The nested loop took 5.9 seconds. The set lookup took 1.4 milliseconds, a factor of about 4,000. Double both sizes and the first takes four times as long, while the second takes twice as long. Timings and the speedup ratio depend on hardware and data. The growth rates are what matter.

Big-O describes growth, not absolute speed. When n is a few thousand, an O(n^2) pass that stays in L2 cache beats an O(n log n) sort that touches disk. An O(N) algorithm that loads a 200 GB dataset onto a box with 32 GB of RAM swaps or crashes, so the O(N log N) streaming pass wins. And a network round trip per item is the real O(N) killer, and it dwarfs the arithmetic.

Interviewers hold back one follow-up. It's fast in your test and slow in prod. Why? Almost always, N in prod crossed the point where a quadratic, or a network call per item, dominates. Or the test data fit in cache and the prod data doesn't.

So in the room, estimate N out loud before you optimize anything. Then name the line that dominates. The worked numbers are on fdeinterviews.com.

TL;DR: Use Big-O to find the term that blows up at the customer's actual N, then attack it: turn linear scans into hash or index lookups, and replace brute-force pairwise work with an index or approximate nearest neighbor before it becomes quadratic. At small N, stop optimizing the exponent and look at constant factors and memory instead.

What Big-O is for here

The interview version asks you to label a snippet O(n log n). The deployment version asks a harder question: at the data size this customer has, which line of code decides whether the feature is feasible at all? You estimate N, find the dominant term, and check it against your latency and memory budget. Everything else is noise.

The move that comes up constantly is brute-force similarity search. You have a query vector and a corpus of vectors, and you want the closest matches. The obvious code compares the query to every vector: N dot products, each over d dimensions, so O(N * d) per query. At N = 10,000 that is instant. At N = 50 million with d = 1,536, each query is tens of billions of multiply-adds, hundreds of milliseconds to seconds on a CPU, and you have one of these per user request. Brute force does not "get slow" here, it stops being viable. The arithmetic: 50 million vectors times 1,536 dimensions is 76.8 billion multiply-adds per query, and at a sustained 100 billion floating-point operations a second on a CPU core, two per multiply-add, that is about 1.5 seconds for one query (computed; the throughput figure is an order-of-magnitude estimate, and a GPU or a well-vectorized library moves it, not the shape). The same query over 10,000 vectors is 15 million multiply-adds, a third of a millisecond. That is the signal to reach for an index: an approximate nearest neighbor structure (HNSW, IVF) trades exact answers for sub-linear query time (HNSW is roughly logarithmic in practice; IVF scales with how many lists you probe, not the full corpus), turning a dead feature into a 10 ms lookup. You accept slightly imperfect recall because exact correctness was never worth a 1000x latency cost.

Roughly how scale picks the approach for similarity search:

Data scaleWorkable approachWhy
N = 10,000Brute-force O(N * d) per queryInstant at this size
N = 50 million, d = 1,536Index / ANN (HNSW, IVF)Brute force is hundreds of ms to seconds per query, not viable
For each of N, scan MHash set/dict of M sideDrops O(N * M) join to O(N + M)

The other workhorse is hashing and indexing. A nested loop that, for each of N records, scans a list of M to find a match is O(N * M), the classic accidental quadratic. Build a hash set or dict of the M side first, and each lookup drops from O(M) to O(1), so the whole join becomes O(N + M). This single change is the difference between a script that finishes in seconds and one that the customer kills after an hour.

python
# O(N * M): for each order, scan all customers
matched = [o for o in orders if any(c.id == o.cust_id for c in customers)]

# O(N + M): index once, then O(1) lookups
cust_ids = {c.id for c in customers}
matched = [o for o in orders if o.cust_id in cust_ids]

Both lines return the same result. The second one is the one that ships. Timed at 20,000 orders against 20,000 customers: the nested loop took 5.9 seconds and the set lookup 1.4 milliseconds, the same matches, a factor of about four thousand (executed on one laptop core; the timings and speedup ratio depend on hardware and data; the growth rates are what matter). Double both sizes and the first line takes four times as long while the second takes twice as long, which is what the two complexity classes mean in practice.

When the exponent stops mattering

Big-O describes growth, not absolute speed, and on real jobs the constant factors and memory often decide. An O(n log n) sort that touches disk loses to an O(n^2) pass that stays in L2 cache when n is a few thousand. An O(N) algorithm that materializes the whole dataset in memory is worse than an O(N log N) streaming pass if the dataset is 200 GB and the box has 32 GB of RAM, because the "faster" one swaps or crashes. The questions that actually move the needle: what is N here, does it fit in memory, how many times per second does this run, and where is the data (cache, RAM, disk, network)? A network round trip per item is the real O(N) killer, not the CPU work.

Why interviewers probe this

They are checking for judgment, not memorized complexity classes. The tell is whether you estimate N before optimizing and whether you can name the line that dominates. A strong candidate says "at 50 million vectors, brute-force cosine is seconds per query, so we index with HNSW and tune it to ~95 percent recall, which is an operating point we choose, not a fixed property." A weak one micro-optimizes an inner loop that runs on 200 rows. The follow-up they hold back: "it is fast in your test and slow in prod, why?" The answer is almost always that prod N crossed the point where a quadratic or a per-item network call dominates, or that test data fit in cache and prod data does not.

Common misconceptions

  • "Lower Big-O always wins." Only as N grows. For small or fixed N, constant factors, cache behavior, and memory footprint decide; profile before assuming the exponent is the cost.
  • "Brute force is fine, it is simple." Fine until N crosses the line where O(N * d) per query exceeds your latency budget. Know roughly where that line is for your data.
  • "Big-O counts CPU operations." It counts whatever dominates. If each step makes a network or disk call, that I/O is your real complexity, and it dwarfs arithmetic.
  • "Approximate search is a hack." ANN trades a few points of recall for orders of magnitude in latency. For retrieval at scale that trade is correct, not a compromise.

Key takeaways

  • Estimate N first, then find and attack the dominant term; ignore the rest.
  • Replace linear scans with hash/dict lookups (O(N * M) to O(N + M)) and brute-force pairwise search with an index or ANN before it goes quadratic.
  • At small or fixed N, constant factors, cache, and memory matter more than the exponent.
  • Per-item network or disk calls are the hidden O(N) that actually sinks real systems.

Related concepts

Practise this in questions