BugMojoBugMojoBugMojo
FeaturesPricingBlogHelpAbout
Add to ChromeLog inGet started
BugMojoBugMojo

Bug reports that actually help fix bugs — capture, replay, share.

A product of Softech Infra.

Product

  • Features
  • Pricing
  • Browser extension
  • Get started
  • Log in

Resources

  • Help & guides
  • Blog
  • Compare
  • Glossary

Company

  • About
  • Contact
  • Security
  • Privacy
  • Terms
  • Sitemap
© 2026 BugMojo. All rights reserved.
AllGuidesEngineeringPlaybooksCompareGlossaryAlternativesBy roleBug tracking by framework
  1. Home
  2. Blog
  3. Glossary
  4. What Is a Heisenbug? (And How to Catch One)
Glossary

What Is a Heisenbug? (And How to Catch One)

A heisenbug is a bug that changes its behavior or disappears the moment you try to observe it. Here is why that happens, the mechanisms behind the vanishing act, and how to catch one without collapsing it.

Hrishikesh BaidyaHrishikesh Baidya·Jul 16, 2026·10 min read
Glossary
Isometric line-art timeline of a bug that appears, then vanishes the moment a console log observes it, then returns when the log is removed, lime on a dark canvas
TL;DR
  • A heisenbug is a bug that changes behavior or disappears the moment you try to observe it — named after the Heisenberg uncertainty principle.
  • The cause is the observer effect: adding a log, a breakpoint, or a debug build changes timing, memory, or instruction order enough to hide the fault.
  • The most common underlying fault is a race condition; concurrency, uninitialized memory, and compiler optimizations are the usual suspects.
  • They are among the hardest bugs because they resist the exact tool — instrumentation — you would normally reach for.
  • Catch one with low-overhead capture (record and replay, not pause), always-on logging, race detectors, and a preserved environment.

Definition

A heisenbug is a software bug that changes its behavior or disappears when you try to observe it. The name puns on the Heisenberg uncertainty principle: the act of measuring alters the result. Debugging tools shift timing, memory, or execution order just enough to hide the fault.

The word is a joke that turned into jargon. A heisenbug is named after physicist Werner Heisenberg, whose uncertainty principle is popularly (if loosely) summarized as 'the act of observing a system changes it.' Programmers borrowed the idea to describe a maddening class of defect: a bug that is real and reproducible right up until you try to look at it, at which point it politely vanishes. Add a console.log, and the crash stops. Remove the log, and it comes back. The bug appears to be shy.

It is not shy — it is timing- or state-sensitive, and your act of observing changed the timing or the state. That is the whole definition, and it is worth stating precisely because it inverts your instincts. With an ordinary bug, more instrumentation gets you closer to the truth. With a heisenbug, more instrumentation pushes the truth further away. The physics analogy is close enough to be useful: the observer effect describes how measuring a system necessarily disturbs it, and a heisenbug is that principle applied to a running program.

Why it happens: the observation changes the program

The key thing to internalize is that your debugging tools are not passive windows onto the program — they are edits to it. Every probe you insert has a cost in time, memory, or scheduling, and for a bug that lives inside a narrow window, that cost is enough to move the window. Here are the mechanisms that produce heisenbugs, roughly in order of how often you will hit them.

A log or debugger changes timing enough to hide a race condition. This is the classic. A race condition depends on the exact interleaving of two or more concurrent operations — which thread reads the shared variable first, whether the network response arrives before or after the component unmounts. MDN defines a race condition as a situation where the system's behavior depends on the sequence or timing of uncontrollable events. Adding a console.log forces synchronous I/O and buffer flushing that can shift one thread by milliseconds — and that is often enough to change who wins the race. Suddenly the 'correct' ordering happens every time, and the bug is 'gone.'

A debugger pauses execution and masks a concurrency issue. Breakpoints are worse than logs for this, because pausing one thread while others keep running (or stopping the world entirely) completely rewrites the concurrency picture. Code that was genuinely parallel becomes effectively serialized while you single-step through it, so the data race you were hunting cannot occur under the very tool you are using to hunt it. You watch the variables, everything looks fine, you detach — and the crash returns on the next run.

Uninitialized memory that happens to be zeroed in a debug build. In many languages a variable read before it is written contains whatever garbage was previously in that memory. Debug builds often zero-fill memory (and add guard bytes), so the uninitialized read silently returns 0 and behaves 'correctly.' Compile in release mode, where memory is left as-is, and the same read returns garbage that crashes or corrupts state. The bug exists in both builds; only one of them lets you see it.

Compiler optimizations differ between debug and release. Optimizing compilers reorder instructions, keep values in registers instead of memory, inline functions, and elide 'redundant' reads — all legal under the language's memory model, all invisible in an unoptimized debug build. A bug that only manifests under -O2 instruction reordering will refuse to reproduce the moment you switch to -O0 to attach your debugger. Same source, different machine code, different bug surface.

Probe effects: logging changes buffering and flushing. Even setting concurrency aside, the mere act of writing output has side effects. A log statement can force a stream to flush, change how a buffer fills, alter memory pressure and garbage-collection timing, or serialize access to a resource that was previously contended. The general term for this in performance and systems work is the probe effect: the instrument perturbs the measurement. A heisenbug is what happens when the probe effect is large enough to cross the threshold between 'fails' and 'passes.'

The -bug family: heisenbug's relatives

Folklore names several cousins, catalogued under unusual software bugs. A Bohrbug is the opposite of a heisenbug — a solid, deterministic bug that reliably reproduces under the same conditions (named after the stable Bohr atom). A mandelbug has causes so complex and chaotic its behavior looks non-deterministic (after the Mandelbrot set). And a schrödinbug is a bug that nobody notices until someone reads the code, realizes it should never have worked, and then it promptly stops working. Heisenbugs are the ones that flee observation; Bohrbugs are the ones you wish you had.

Why heisenbugs are among the hardest bugs

Most debugging is a search, and the search normally converges: you add a log, learn something, add a more specific log, and narrow in. Heisenbugs break that loop because instrumentation is anti-convergent — every probe you add to learn more makes the bug less likely to appear. You are being asked to study something that ceases to exist under study. That is why they earn a special name and a reputation: they resist the exact reflex a good engineer has, which is 'let me add some visibility and watch it happen.'

They are also demoralizing in a way ordinary bugs are not, because the failure looks intermittent and 'unreproducible,' which invites the worst outcomes: the ticket gets closed as cannot reproduce, or someone adds the log 'to catch it next time,' the bug hides, everyone concludes it is fixed, and it resurfaces in production a week later. The heisenbug's tendency to appear flaky is what lets it survive triage. It is not that the bug is rare; it is that your net has holes shaped exactly like the fish.

The reproduction paradox

Here is the trap in one sentence: the classic move — add a log to see what's happening — is exactly what makes a heisenbug vanish. The instinct that solves nearly every other bug is the instinct that hides this one. So the first discipline when you suspect a heisenbug is to stop adding reactive instrumentation and switch to observation methods that do not change the program you are trying to observe.

How to catch one anyway

You cannot beat a heisenbug by looking harder with the same tools. You beat it by changing how you observe, so that observing costs almost nothing. Five practical moves, in order:

1. Use low-overhead observation — capture, don't pause. The single most important shift is from interactive, pausing tools (breakpoints, single-stepping) to passive recording. If you can capture what the program did and replay it afterward, you never stop the threads, never serialize the concurrency, and never insert the millisecond delay that hides the race. Record-and-replay preserves the timing you are trying to study instead of destroying it.

2. Make logging always-on, not reactive. If instrumentation is a permanent, lightweight fixture of the build — structured logs, ring buffers, tracing that ships in production — then the instrumented build is the build the bug lives in. You are not perturbing anything by adding a probe, because the probe was always there. Reactively adding a heavy log the moment you suspect a bug is what creates the perturbation; a constant, cheap log does not.

3. Reach for sanitizers and race detectors. Purpose-built tools like ThreadSanitizer (TSan), Helgrind, and language-level race detectors are designed to find timing and memory faults with minimal or well-characterized perturbation — often by instrumenting at build time and reasoning about the happens-before relationships rather than relying on the bug to fire during a manual session. They are the closest thing to a microscope that does not disturb the specimen.

4. Reproduce under load. A heisenbug lives in a narrow timing window; realistic concurrency and stress widen that window and raise the odds it fires. Running the failing path under parallel load, with contention on the same resources it hits in production, turns a one-in-ten-thousand event into something you can trigger on demand — without adding a single log line.

5. Preserve the exact environment. Because debug builds, optimization levels, and hardware all change the bug surface, reproduce with the same build flags, the same -O level, the same runtime, and ideally the same machine class as where it failed. A release-only bug simply will not appear in a debug build, so 'I couldn't reproduce it locally' often just means 'I changed the environment that the bug depended on.'

Those five moves share one property: none of them ask the program to behave differently while you watch. That is the entire strategy against a heisenbug. For the adjacent problem of tests that fail intermittently for these same reasons, see what a flaky test is and the deeper playbook on killing 'cannot reproduce' bugs. And because most heisenbug reports die from missing context, it is worth knowing what most bug reports are missing in the first place.

How BugMojo fits: observe without collapsing it

This is where the physics analogy stops being cute and starts being actionable. The reason a heisenbug collapses is that you inserted a probe — a log, a breakpoint — that altered the timing. BugMojo's browser extension records what actually happened using an rrweb session replay, capturing the DOM, console, and network activity as a recording, without you stopping the page to insert anything. You are not pausing execution, not adding reactive log lines, not switching to a debug build. The observation is passive, so it does not move the timing window the bug depends on.

The practical payoff: you observe the heisenbug without collapsing it. Instead of asking a user to reproduce a flaky failure while you attach a debugger — the exact act that hides it — you get the recorded session from the run where it actually fired, with the console output and the network response that fed the race, and you scrub back through it afterward. The bug already happened; you are studying the recording, not perturbing the live system. That is the difference between a measurement that destroys the phenomenon and one that preserves it.

Key takeaway

A heisenbug is the observer effect in software: observing it changes it. The fix is not to look harder with pausing tools — it is to observe passively. Record and replay instead of stepping through; keep logging always-on instead of adding it reactively; use race detectors; reproduce under load; and preserve the exact build. Capture the failure as it happened, and you get to study the heisenbug without making it vanish.

Catch the heisenbug without collapsing it

BugMojo records the failing session — DOM replay, console, and network — as it actually happened, so you observe the bug without inserting a probe that alters the timing. No reactive logs, no debugger pause, no vanishing act.

Install the extension

Frequently asked questions

Frequently asked questions

Sources

  1. Heisenbug — a software bug that alters its behavior when studied — Wikipedia (2026)
  2. Race condition — Glossary — MDN Web Docs (2026)
  3. Unusual software bug — Bohrbug, mandelbug, schrödinbug and the -bug family — Wikipedia (2026)
  4. Observer effect (physics) — measurement disturbs the system observed — Wikipedia (2026)
Share:
Hrishikesh Baidya
Hrishikesh Baidya· Chief Technology Officer

Hrishikesh Baidya is the CTO at Softech Infra. He is drawn to architecture that is invisible — systems that simply work — and leads the engineering behind BugMojo.

On this page

  • Definition
  • Why it happens: the observation changes the program
  • Why heisenbugs are among the hardest bugs
  • How to catch one anyway
  • How BugMojo fits: observe without collapsing it

Get bug-tracking insights, weekly.

Engineering deep-dives, QA playbooks, and honest tool comparisons. No spam — unsubscribe in one click.