Case Study: Debugging a Quantum Program That Returns Plausible Garbage
Executive Summary
Classical programs fail loudly. Quantum programs fail by returning a distribution that looks entirely reasonable and is wrong — no exception, no stack trace, no obvious symptom.
This case study is the debugging methodology for that situation. A Grover implementation returns the wrong marked state with 61% confidence, and the fault turns out to be three independent bugs, each of which alone would have produced plausible output. The technique — bisect by classical verifiability — is the most broadly useful practical skill in the book.
Skills applied
- Isolating faults in quantum circuits by classical simulation at each stage.
- Detecting endianness errors (§33.9).
- Verifying oracles independently of the algorithm.
- Distinguishing implementation bugs from hardware noise.
The symptom
Grover on 4 qubits, marked state |1011⟩, 8,192 shots:
| Outcome | Counts | Fraction |
|---|---|---|
1101 |
4,998 | 0.610 |
1011 |
892 | 0.109 |
| others | 2,302 | 0.281 |
A sharp peak — amplification is clearly working — on the wrong state. The reversed bit string is suspicious but not conclusive, since 1101 is also just a plausible wrong answer.
This is the characteristic quantum bug. The algorithm ran, the interference worked, and the output is confidently incorrect.
Step 1: Remove the hardware
Run identically on AerSimulator with no noise.
Result: 1101 at 0.961. The bug reproduces exactly, and now at ideal fidelity.
Rule one of quantum debugging: reproduce on a noiseless simulator first. If it reproduces, hardware is irrelevant and you have a deterministic, fast, fully inspectable environment. If it does not reproduce, the problem is noise and requires a completely different investigation.
Hardware eliminated in one step.
Step 2: Verify the oracle alone
The oracle is a self-contained component with a classically checkable specification: it should flip the phase of |1011⟩ and nothing else.
from qiskit.quantum_info import Statevector
import numpy as np
for i in range(16):
basis = format(i, '04b')
sv = Statevector.from_label(basis).evolve(oracle)
phase = np.real(sv.data[i])
if phase < 0:
print(f"oracle flips {basis}")
Output: oracle flips 1101.
The oracle marks the wrong state. Inspecting its construction:
marked = '1011'
for i, bit in enumerate(marked): # BUG
if bit == '0':
qc.x(i)
The string is iterated left to right and applied to qubits 0,1,2,3 — but Qiskit is little-endian, so marked[0] should map to qubit 3. The fix:
for i, bit in enumerate(reversed(marked)):
Bug 1 found: endianness in the oracle. This is the single most common quantum implementation error, and it produces exactly this symptom — a confident peak on the bit-reversed answer.
Step 3: Verify the diffusion operator
With the oracle fixed, rerun: 1011 now appears at 0.472 — better, and still short of the expected 0.961.
Check the diffusion operator independently. It should implement $2|s\rangle\langle s| - I$, whose defining property is that the uniform superposition is a fixed point:
uniform = Statevector.from_label('0000').evolve(hadamards)
after = uniform.evolve(diffusion)
print(np.allclose(uniform.data, after.data)) # expect True
Output: False. The diffusion operator is wrong.
Inspecting: the multi-controlled Z was implemented with mcx sandwiched between Hadamards on the wrong target qubit — qubit 0 instead of qubit $n-1$, another endianness casualty.
Bug 2 found: diffusion operator target.
Step 4: Check the iteration count
Rerun: 1011 at 0.782. Closer, still not 0.961.
Expected optimum for $N=16$, $M=1$:
$$k_{\text{opt}} = \left\lfloor\frac{\pi}{4}\sqrt{16}\right\rfloor = \lfloor 3.14\rfloor = 3$$
The code used int(np.pi/4 * np.sqrt(N)), which gives 3 — correct. But a sweep tells the real story:
| Iterations | $P(1011)$ |
|---|---|
| 1 | 0.472 |
| 2 | 0.908 |
| 3 | 0.782 |
| 4 | 0.348 |
The peak is at 2, not 3. The floor-based formula is an approximation; the exact optimum is
$$k_{\text{opt}} = \text{round}\left(\frac{\arccos\sqrt{M/N}}{\theta}\right), \quad \theta = 2\arcsin\sqrt{M/N}$$
which gives 2.35 → 2 for $N=16$. The floor formula over-rotates slightly at small $N$.
Bug 3 found: iteration count off by one at small $N$.
Final result: 1011 at 0.908, matching theory for $k=2$.
Step 5: Return to hardware
With all three bugs fixed, the hardware run gives 1011 at 0.702 against the noiseless 0.908 — a gap fully explained by the 20 two-qubit gates at $7\times10^{-3}$ error plus readout.
Now the hardware discrepancy is interpretable, because the algorithm is known correct.
The methodology
| Step | Question | Tool |
|---|---|---|
| 1 | Is it noise or logic? | Noiseless simulator |
| 2 | Is the oracle right? | Statevector per basis state |
| 3 | Is each component right? | Component-specific invariants |
| 4 | Are the parameters right? | Sweep and compare to theory |
| 5 | Is the hardware gap explained? | Error budget (Ch. 22) |
The organizing principle: bisect by classical verifiability. Every component of a quantum circuit has a classically checkable specification — the oracle marks a known state, diffusion fixes the uniform superposition, the QFT matches numpy.fft, an encoder satisfies its stabilizers. Test each in isolation before testing the composition.
The endianness checklist
Two of three bugs were endianness. It is worth a dedicated checklist:
- [ ] Bit strings indexed with
reversed()when mapping to qubits - [ ] Multi-controlled gates target qubit $n-1$, not 0
- [ ] Result keys read right-to-left ($q_0$ is rightmost)
- [ ]
qc.draw()puts $q_0$ on top; result strings put it on the right — mirror images - [ ] Circuit-library components checked for their own convention
- [ ] Every basis-state test written with an explicit label, never an integer index
Discussion Questions
- Three independent bugs each produced plausible output. Why is that more likely in quantum programs than classical ones?
- The noiseless simulator eliminated hardware in one step. Why is that always the correct first move?
- Every component had a classically checkable invariant. Construct one for a QPE circuit.
- The floor formula for Grover iterations is standard and wrong at small $N$. What does that suggest about textbook formulas at boundary cases?
Your Turn: Extensions
- Plant an endianness bug in a working Grover implementation and confirm the bit-reversed peak.
- Write component tests for oracle, diffusion, and encoder that run in CI.
- Sweep Grover iterations for $N = 8, 16, 32, 64$ and compare floor against exact-round formulas.
- Build the same bisect-by-verifiability methodology for a VQE implementation.
Key Takeaways
- Quantum programs fail silently, returning confident wrong answers rather than errors — so debugging must be systematic rather than symptom-driven.
- Always reproduce on a noiseless simulator first; it separates logic bugs from noise in one step.
- Bisect by classical verifiability: every component has a checkable invariant, so test components before compositions.
- Endianness is the dominant source of quantum implementation bugs and produces the characteristic bit-reversed confident peak.
- Only after the algorithm is verified correct is the hardware discrepancy interpretable as noise.