Debugging & Tooling

a heisenbug

/ heisenbug -> HY-zen-bug /

There is a special, maddening kind of bug that disappears the moment you try to look at it. You add a print to see what is happening - and the crash stops. You run it under the debugger to catch it - and it behaves perfectly. You build a debug version - and the problem evaporates, only to return the instant you ship the optimized build. This is a heisenbug: a bug whose behavior changes, often vanishing, precisely because you observed it. The name is a pun on Heisenberg's uncertainty principle, the physics idea that measuring a system can disturb it.

Concretely, the bug does not really care that you are watching - what changed is something your act of observing altered as a side effect. Common mechanisms: adding a print or compiling with -g changes timing or memory layout, so a data race between threads now interleaves differently and the corruption no longer happens on this run. A debugger pauses one thread, hiding a race. An uninitialized variable read garbage in the optimized build but the debug build happens to zero that stack slot. Undefined behavior that the optimizer exploited under -O2 is left alone under -O0, so the symptom disappears with optimization off. In every case the real bug is still there - a race, an uninitialized read, undefined behavior - but it only manifests under the exact timing or memory conditions that observing perturbs.

Why it matters: a heisenbug is a strong diagnostic signal, not just frustration. If a bug vanishes when you add a print or attach a debugger, it almost always points to one of a short list of culprits - a data race, use of uninitialized memory, or undefined behavior the optimizer is exploiting - so the disappearance itself narrows the search. The practical move is to switch from observation that perturbs to tools that detect the underlying class without relying on timing: ThreadSanitizer for races, MemorySanitizer or Valgrind for uninitialized reads, UBSan for undefined behavior. The mistake is to 'fix' a heisenbug by adding the very print that hides it and declaring victory - you have only quieted the symptom, and the bug will return under different conditions.

A multithreaded counter gives wrong totals - but only in release. You add printf("%d\n", count); to investigate and the totals become correct. The print did not fix anything; it slowed one thread just enough to hide a data race. TSan then flags the race directly, regardless of timing.

A print 'fixes' the symptom by perturbing timing; the data race is still there, and TSan finds it.

A bug that vanishes under observation is not fixed - the print or debugger only changed the timing or memory layout that lets it hide. Treat the disappearance as a clue pointing at a data race, an uninitialized read, or optimizer-exploited undefined behavior, and reach for a sanitizer rather than declaring victory.

Also called
observer-effect bugvanishing bug觀測效應臭蟲