skip to content

Aligning two 100k-character documents needs 10^10 table cells. What do you change before writing the loop?

level: seniorimportance: should knowfreq 38%

answer

  1. count the cells before counting the bytes
  2. quadratic time bites before memory does
  3. change what one cell compares
  4. optimal paths stay near the diagonal
  5. linear space still costs quadratic time

basics

~20 s

Attack the cell count, not the memory: quadratic time bites before space does. Trim the shared prefix and suffix, compare coarser units such as fingerprinted lines, and if only k edits matter, fill just the band where |i-j| <= k, which is O(n*k).

solid answer

~40 s

Say first that quadratic *time* bites before memory does: 10^10 cell updates is minutes, so collapsing the table to one row fixes the wrong problem. Cheap structural wins come first — strip the common prefix and suffix, and raise the unit of comparison so `n` counts lines rather than characters, each reduced to a fixed-size fingerprint so a cell comparison stays constant time. Then exploit the shape of the answer: if you only care whether the documents are within `k` edits, every useful path stays within `k` of the main diagonal, so fill only that band for `O(n*k)`, doubling `k` if it turns out too small. Hirschberg's linear-space divide and conquer belongs only where you need a full alignment and memory alone is the constraint — it still pays `O(m*n)` time.

go deeper

for a junior

Recognize that the plain table is quadratic in both time and space, so multiplying two large lengths tells you immediately whether it is feasible. Doing that arithmetic before writing the loop is the habit being tested.

for a middle

Explain the difference between the memory fix and the time fix: keeping one row shrinks space to linear but leaves the same number of cell updates. Be ready to name the coarser comparison unit as the biggest practical win.

for a senior

Show the reasoning that off-diagonal distance equals operations spent, so a bounded-edit question needs only a band. Sequence the cheap wins first — trim shared prefix and suffix, fingerprint the units — and reach for a linear-space method only for the constraint it actually addresses.

for a principal

Own the framing: decide whether the product needs an exact distance, a bounded yes-or-no, or a readable diff, because each admits a different algorithm and a different budget. Then commit to a threshold and a fallback for the pathological pair rather than letting one request stall a service.

## Read the number correctly Two documents of 100,000 characters give a table of about 10^10 cells. Two things follow, and candidates usually only see the second. 1. **Time.** Ten billion cell updates is the dominant problem. Even at a very optimistic billion updates per second that is on the order of ten seconds, and realistically minutes. 2. **Memory.** At a few bytes per cell that is tens of gigabytes, which is simply unavailable. The reflex answer — "keep one row instead of the whole table" — fixes only the second. It is a correct optimization and it is not an answer to this question. Say that out loud; it is the discriminating remark. ## Step 1: change what a cell compares For documents, characters are rarely the right unit. Comparing whole lines cuts `n` from 100,000 characters to perhaps 3,000 lines, and 10^10 becomes 10^7 — a table you can simply fill. Reduce each line to a fixed-size fingerprint once, up front, so each cell's comparison is constant time instead of proportional to line length. This single decision usually ends the conversation, and it is the one real systems make. Also strip the common prefix and the common suffix before building anything. Edits are almost never uniformly distributed across a document; a config file that gained a section in the middle shares tens of thousands of identical leading and trailing characters, and neither contributes anything to the alignment. ## Step 2: exploit the shape of the answer The key structural insight is about *paths*. An alignment is a monotone path from one corner to the other; a diagonal step costs nothing when the units agree, while every off-diagonal step corresponds to an insertion or a deletion. So a path that ends up `d` columns away from the main diagonal has spent at least `d` insert-or-delete operations. If you only need to know whether the distance is at most `k`, every path that could possibly answer yes stays inside the band `|i - j| <= k`. Fill only that band and treat cells outside it as infinite: - cost becomes `O(n * k)` time and `O(n * k)` space (or `O(k)` if you keep one banded row) - the answer is exact when the true distance is `<= k`, and otherwise you learn only that it exceeds `k` When `k` is unknown you can start small and double it, re-running until the reported distance is comfortably inside the band; the geometric growth keeps the total work proportional to the final run. This is the standard threshold trick, and it is what makes near-identical inputs cheap: for a typo-scale difference, `k` is single digits and `n*k` is linear for practical purposes. The same insight has a difference-driven form used by real diff tools: search by increasing number of differences `d`, so the work scales with `(n + d) * d`. Two near-identical documents have a tiny `d` and finish almost instantly; two unrelated documents degrade back toward quadratic, which is the honest tradeoff. ## Step 3: only then, the space-only fix If you genuinely need a full alignment over inputs where the quadratic time is acceptable but the table is not, Hirschberg's divide and conquer recovers the alignment in `O(min(m,n))` space: compute forward scores over the first half and backward scores over the second half, find the column where they meet, and recurse on the two halves. The time stays `O(m*n)` — roughly doubled by the recursion, with the halving series summing to a constant factor. Name it as the tool for that specific constraint, and do not offer it as an answer to a time problem. ## Step 4: when it is a search, prefilter If the real task is finding near matches among many candidates rather than aligning one pair, the alignment should run last and rarely. Two cheap filters do most of the work: the length bound, since the distance is at least `|m - n|`, so any candidate whose length differs by more than `k` is rejected without a table; and a cheap overlap test on short character groups, since inputs within `k` edits must share most of their short groups. Only survivors get a table, and each of those tables is banded. ## What a good answer sounds like "Ten billion cells is a time problem before it's a memory problem, so a rolling row isn't the answer. I'd trim the shared prefix and suffix, compare fingerprinted lines instead of characters — that alone drops it by orders of magnitude — and if the question is really 'within k edits?', fill only the `|i-j| <= k` band for `O(n*k)`, doubling `k` if needed. Full alignment with a memory ceiling is where a linear-space divide-and-conquer belongs, but it doesn't buy back any time."

  • Why is a path that answers 'within k edits' confined to a band around the diagonal?
    Every step off the diagonal advances one input without the other, which is an insertion or a deletion costing one. So a path reaching a cell where the indexes differ by d has already spent at least d operations. If the budget is k, no useful path can stray more than k from the diagonal, and cells outside that band can be treated as unreachable.
  • You do not know k in advance. Does the banded approach still help?
    Yes — start with a small k, run the band, and double k whenever the computed distance touches the band's edge. Because the work grows geometrically, the total is dominated by the final run, so you pay roughly the cost of the band you actually needed rather than the full table. If the inputs turn out to be unrelated, you eventually fall back to quadratic, which is the honest worst case.
  • Does a linear-space divide-and-conquer alignment make it faster?
    No. Hirschberg's method reduces the space to O(min(m,n)) while still doing O(m*n) work — the recursion roughly doubles the constant factor. It is the right tool when you need the full alignment and memory is the binding constraint, and the wrong answer when ten billion cell updates are the problem.

saying these in an interview costs you the question

  • Says a rolling row solves it, ignoring the quadratic time
  • Proposes parallelizing without reducing the cell count
  • Claims linear-space alignment also cuts the running time
  • Believes a within-k check still requires the full table
  • Compares raw characters when whole lines are the real unit

context