Where is the bug in this variable-size window pseudocode that reports the shortest stretch of tokens containing all required keywords?
answer
- what is true the moment the while exits?
- the loop condition was just falsified
- the measured window was already broken
- record while valid, before evicting
basics
~20 sThe answer is recorded after the shrink loop, when the window has just been shrunk past validity. It stores the length of an invalid window — and does so even on iterations where no valid window ever existed. Record inside the loop, before each eviction.
solid answer
~50 sThe shrink loop runs *while* the window is valid, so the moment it exits, the last eviction has just destroyed validity: `[left..right]` no longer contains all keywords. Recording `right - left + 1` at that point stores the length of an invalid window — one less than the smallest valid window ending at `right`, which was `[left-1..right]`. Worse, the update runs on every outer iteration, including those where the window was never valid at all, so `best` absorbs meaningless lengths and can end up smaller than any real answer. The fix is to measure before you break the thing you are measuring: move `best = min(best, right - left + 1)` to the top of the while body, before the eviction. Then every recorded length belongs to a window verified valid, and nothing is recorded when validity is never reached.
code
pseudocode · 8 linesbest = infinity
left = 0
for right in 0..n-1:
admit(tokens[right])
while windowHasAllKeywords():
evict(tokens[left])
left = left + 1
best = min(best, right - left + 1)go deeper
Be ready to trace the fragment on a five-token example, and to notice what the loop condition tells you at the exact moment the while exits.
An interviewer expects you to name both defects — the invalid-window length and the never-valid iterations polluting the answer — and to state the fix in one sentence: record while valid, before evicting.
Treat this as review instinct: whenever a measurement sits next to a mutation, ask what invariant holds at that exact line. Be ready to explain why the correct record point flips between shortest-goal and longest-goal variants.
The lesson to institutionalize is invariant-anchored placement: reviewers who check what is provably true at the line where a result is captured catch this entire bug family before it ships, without needing to know the specific pattern.
## The discipline being tested Finding the **shortest** contiguous stretch that satisfies a containment requirement uses the variable-size window with the shrink condition inverted: grow the right edge until the window qualifies, then — because a qualifying window might be carrying slack on its left — shrink *while it still qualifies*, tightening it as far as possible. Each moment during that shrink, the window is a verified candidate. The subtlety is entirely about **where the answer is captured**, because the shrink loop, by design, runs until it has gone one step too far. ## Tracing the broken fragment Take a support-chat transcript of tokens with required keywords `refund` and `orderid`, and the token stream: `hello refund please orderid thanks` (indices 0–4). Walk the fragment: - `right = 0..2`: the window never contains both keywords. The while never runs — but `best` is still updated each time, with lengths 1, 2, 3 of windows that satisfy nothing. `best` is already garbage. - `right = 3`: window `[0..3]` contains both keywords. The while evicts `hello` (still valid: `[1..3]`), then evicts `refund` — now `orderid` alone remains and validity is gone, so the loop exits with `left = 2`. Only *now* does the fragment record `right - left + 1 = 2`, the length of `[2..3]` = `please orderid` — an **invalid** window. The true smallest valid window ending at 3 was `[1..3]`, length 3, and it was never recorded. Two distinct defects, one placement error: 1. **Off-by-one on valid iterations.** At loop exit, the last valid window was `[left-1..right]`, length `right - left + 2`. Recording `right - left + 1` under-reports by exactly one. 2. **Garbage on never-valid iterations.** When the while condition is false from the start, the post-loop update still fires, folding lengths of non-qualifying windows into `best`. The final answer can be shorter than any window that satisfies the requirement — a silently wrong result, not a crash. ## The fix Move the record to the point where the invariant guarantees what you are measuring: ``` for right in 0..n-1: admit(tokens[right]) while windowHasAllKeywords(): best = min(best, right - left + 1) // window verified valid HERE evict(tokens[left]) left = left + 1 ``` Every recorded length now belongs to a window that the loop condition has just verified. If no window is ever valid, the update never runs and `best` keeps its `infinity` sentinel — which is itself the correct "no answer exists" signal and should be checked before reporting. A patch that keeps the record after the loop and compensates with `right - left + 2` fixes only defect 1; it still needs a guard for the never-valid case, which is why the in-loop placement is the version worth memorizing — it needs no guard at all. ## Why the record point flips with the goal The mirrored **longest-valid-window** shape shrinks while the window is *invalid*, so there the loop's exit means validity was just *restored* — and recording after the loop is exactly right. The rule that generalizes is not "record inside" or "record outside"; it is: **capture the answer at the point where the loop invariant proves the window is in the state you want to measure.** For shrink-while-valid, that point is inside the loop before the eviction; for shrink-while-invalid, it is immediately after the loop. Transplanting the record point from one variant to the other is precisely how this bug gets written by people who have seen both shapes.
- What is the minimal correct fix?Move the best-update to the top of the while body, before the eviction. Every measured window is then verified valid by the loop condition, and on iterations where the window never qualifies the update never runs, so best keeps its infinity sentinel — correctly signaling that no qualifying window exists if that is still its value at the end.
- Could you keep the record after the loop and compensate with right - left + 2 instead?Only half-fixes it. That repairs the off-by-one for iterations where the shrink loop ran, but on never-valid iterations the loop body never executed and the compensated formula still records a meaningless length — so you would additionally need a flag that some valid window was seen. The in-loop record needs no guard, which is why it is the safer idiom.
- Why does the mirrored longest-valid-window code correctly record after its shrink loop?Because there the loop shrinks while the window is invalid, so loop exit means validity was just restored — exactly the state worth measuring. The general rule: place the record where the loop invariant proves the window is in the state you intend to measure. That point flips between the two goal directions, which is why transplanting code between them produces this bug.
It is like weighing a parcel after you have already cut a piece off to see if it still qualifies for the cheap rate: the number on the scale describes the parcel you ruined, not the one you could have shipped.
saying these in an interview costs you the question
- After the shrink loop, the window is the smallest valid one
- Recording after the loop is safe because shrinking only removes redundant elements
- Adding one to the recorded length fixes it, with no guard for never-valid iterations
- Longest-valid and shortest-containing variants record the answer at the same point