A chunking recursion consumes two characters per call and bases only on n == 0 — which inputs never terminate?
answer
- walk the counter down from an odd start
- which values does a stride of two visit
- equality tests are one point wide
- the step is two, the base is one value
- count the residues, not just zero
basics
~20 sEvery odd-length input. The remaining count steps by two, so from an odd start it runs 5, 3, 1, -1 and never equals zero. A stride of two needs two base cases: one at n == 0 and one at n == 1.
solid answer
~50 sOdd lengths run forever. The counter descends in strides of two, so it visits only one parity class: from an even start it reaches 0 and stops, from an odd start it passes through 1 straight into the negatives and the `n == 0` equality is never true. The measure decreased on every call, which is why this looks correct at a glance — the base condition was simply never on the path. There are actually two defects at `n == 1`: the runaway, and a read one position before the start of the document that happens *before* any guard would fire. The fix is a base case for every value the stride can land on: `n <= 0` returns empty, `n == 1` emits the single leftover character. As a rule, a stride of `k` needs the base to cover all `k` values below it, not just zero.
code
pseudocode · 5 linesCHUNK(doc, n): // n characters of doc still to emit
if n == 0:
return ""
pair = doc[n-2] + doc[n-1]
return CHUNK(doc, n - 2) + pair + "|"go deeper
Practise walking the counter down by hand from an odd start and saying which values it hits. Being able to show 5, 3, 1, -1 and note that none equals zero is the whole answer at this level.
Explain why a stride larger than one plus an equality base condition is the defect, and name both fixes: an inequality guard for the undershoot and a real semantic answer for the single-item remainder.
Show the review habit rather than the trace: ask which values the step can land on, demand the degenerate sizes as tests, and separate the termination fix from the correctness fix so the half-fix does not ship.
Frame it as a class of defect rather than a bug. Recursions that consume fixed-size units appear all over parsing and chunking code, so a team convention — inequality guards plus a degenerate-size test row — buys more than reviewing each one on its merits.
## What the counter actually visits The recursion moves `n` to `n - 2` and stops only when `n` is exactly `0`. Subtraction by two preserves parity, so a call chain visits one parity class and only that one. From `n = 6` the chain is 6, 4, 2, 0 — the base matches and the recursion unwinds. From `n = 5` it is 5, 3, 1, -1, -3, -5, … The equality `n == 0` is false at every one of those values, forever. Nothing about the *measure* was wrong: it strictly decreased on every call, exactly as a termination argument requires. The base condition was simply never on the path the measure travels. This is the concrete answer to the belief that one base case is always enough. One base case is enough when the step size is one, because then the measure visits every intermediate value and cannot miss a single point. The moment the step is larger than one — subtract two, halve and subtract, drop a fixed-size header, skip a delimiter pair — the measure jumps over values, and a point-shaped base condition can be jumped over with it. ## The second bug hiding at n == 1 At `n == 1` the fragment computes `doc[n-2]`, which is `doc[-1]`: a read one position before the start of the document. Depending on how the underlying data is represented this either fails loudly or quietly returns something meaningless, and it happens *before* the recursive call, so a guard placed only on the recursive step would not prevent it. Two distinct defects therefore live at the same untested input, which is a good argument for the habit of writing the degenerate cases as tests rather than reasoning about them once. ## Fixing it properly The fix is to make the base cover every value the stride can land on: ``` if n <= 0: return "" if n == 1: return doc[0] + "|" ``` Two things are worth noticing. First, the guard on the runaway side is an inequality, not an equality: `n <= 0` catches any accidental undershoot instead of relying on the counter landing exactly on the point. Second, `n == 1` is a genuine *semantic* base case, not defensive padding — a one-character document has a correct answer, and someone has to produce it. Changing `n == 0` to `n <= 0` alone converts an infinite recursion into a wrong result plus the out-of-range read: the leftover character is silently dropped. Fixing termination without fixing the answer is a common half-fix. ## Generalising the rule For a recursion whose step subtracts `k`, the base must cover the whole remainder range `0 .. k-1`, because that is what the counter can land on when the input size is not a multiple of `k`. The same shape appears well beyond arithmetic strides: - Consuming a fixed-size record per call: the trailing partial record is the missed base case. - Splitting into two halves: sizes 0 and 1 are both degenerate and both need answers. - Peeling matched delimiters from both ends: zero and one remaining are distinct cases. ## The test set that catches it Random mid-sized inputs are almost useless here, because half of them terminate and the other half look like a hang rather than a wrong answer. The inputs that find base-case coverage bugs are always the small and degenerate ones: empty, one element, two elements, and the smallest input above the stride in each residue class. Writing those four as tests costs less than reasoning about parity in a review, and it catches the whole family — missing base case, off-by-one boundary read, and silently dropped remainder — in one pass.
- Does changing the test from n == 0 to n <= 0 fix the bug?It stops the runaway but not the defect. With a single character left, the code still reads one position before the start of the document before any guard applies, and the leftover character is silently dropped from the output. The recursion now terminates with a wrong answer, which is harder to notice than a hang. You still need an explicit branch for one remaining character.
- How does the rule generalise to a recursion whose step subtracts k?The counter descends through one residue class modulo k, so it can land anywhere in 0..k-1 depending on the input size. The base must answer for all k of those values: typically an inequality guard for the undershoot plus explicit handling of each partial remainder. One base case suffices only when the step is exactly one.
- Which test inputs would have caught this before review?The degenerate sizes: empty, one character, two characters, and one small odd input. Base-case coverage bugs live entirely at the boundary, so mid-sized random inputs mostly miss them — and when they do hit, half the failures look like a hang rather than an assertion failure, which sends people debugging the wrong thing.
saying these in an interview costs you the question
- Says one base case is always enough
- Assumes an equality test is reached by any decreasing counter
- Verifies only even-length inputs and calls it covered
- Adds a negative guard and leaves the leftover character unhandled
- Blames the out-of-range read instead of the missing base case