The Caching Bug That Only Appears After Lunch
A student builds a program that looks up whether a username is already taken. On the first run each day it behaves correctly. After roughly 40 lookups, though, every new username is reported as 'taken' even when it is clearly not. The student's code appends each searched name to a list called checked, then uses a loop that scans checked to decide whether the name was seen before. The lookup function is called once per keystroke in a search box, and the program never removes anything from checked. The student insists the logic is correct because 'the loop checks the whole list every time.' The teacher asks the student to explain why correctness of the loop does not guarantee correctness of the program.
1. Identify the root cause of the failure and explain why the symptom appears only after many lookups rather than immediately.
Read the worked answer
The list checked is state that persists across calls and is only ever appended to. The loop is logically correct for the question it asks ('is this name already in checked?'), but the program's real question is 'is this name taken in the actual database?'. Because every keystroke adds partial names to checked, the list fills with fragments like a, al, ale. Once a fragment matches a prefix of many names, subsequent full names collide with those stored fragments and are reported as taken.
The delay is the tell: the failure is data-dependent, not logic-dependent. It surfaces only after enough entries accumulate that a collision becomes likely. A correct fix is to stop using a growing list as a proxy for the database and query the actual source of truth.
2. Explain how the student could have detected this bug with a test rather than by waiting for it to appear in normal use.
Read the worked answer
The bug is reachable with a tiny, deliberate input sequence: search a, then search alice. A good test targets state carried between calls, not just a single invocation. The student should have written tests that (1) call the function repeatedly with related inputs, and (2) assert the result against the real database, not against the internal list.
A useful principle: when a function reads or writes module-level state, unit tests that call it once are weak. Testing sequences - and resetting state between tests - exposes accumulation bugs immediately.