Quantum Measurement Explained
How quantum measurement works: the Born rule, wavefunction collapse, measurement bases, and why measurement is irreversible in quantum computing.
Measurement is one of the most important and counterintuitive aspects of quantum computing. Every quantum computation ends with a measurement, and the rules governing measurement are fundamentally different from anything in classical computing. This tutorial explains how measurement works, what the Born rule says, and why measurement irreversibility shapes the design of quantum algorithms. It also covers advanced topics: generalized measurements (POVMs), weak measurement, mid-circuit measurement, the quantum Zeno effect, state tomography, Bell inequality violations, and common mistakes that trip up beginners.
The Core Problem: You Cannot Observe a Superposition Directly
A qubit in a superposition holds complex amplitudes for both |0> and |1> simultaneously. But when you measure it, you only ever get one definite outcome: 0 or 1. The superposition is not preserved during measurement; the act of measuring forces the qubit into a definite classical state.
This is not a limitation of measurement technology. It is a fundamental feature of quantum mechanics. The output of any quantum computation is always classical bits, extracted by measurement from a quantum state.
The Born Rule
The Born rule tells you the probability of each outcome. For a single qubit in state:
|psi> = alpha |0> + beta |1>
the probability of measuring 0 is |alpha|^2, and the probability of measuring 1 is |beta|^2:
import numpy as np
alpha = 1 / np.sqrt(3)
beta = np.sqrt(2 / 3)
prob_0 = abs(alpha)**2
prob_1 = abs(beta)**2
print(f"P(0) = {prob_0:.3f}") # 0.333
print(f"P(1) = {prob_1:.3f}") # 0.667
print(f"Sum = {prob_0 + prob_1:.3f}") # 1.000
The probabilities always sum to 1 because the state is normalized. Quantum mechanics requires states to be normalized precisely so that Born rule probabilities are consistent.
Wavefunction Collapse
After measurement, the qubit collapses to the state corresponding to the outcome. If you measured 0, the qubit is now in state |0>. If you measured 1, it is in state |1>. The original superposition is destroyed.
from qiskit import QuantumCircuit
from qiskit_aer import AerSimulator
qc = QuantumCircuit(1, 1)
qc.h(0) # Create |+> = (|0> + |1>) / sqrt(2)
qc.measure(0, 0)
sim = AerSimulator()
result = sim.run(qc, shots=1000).result()
print(result.get_counts()) # {'0': ~500, '1': ~500}
If you measure the same qubit again immediately after the first measurement, you always get the same result. The collapse is real: the qubit is in a definite state after measurement.
Why Measurement Is Irreversible
Every quantum gate is a reversible operation; you can always apply the inverse gate to undo it. Measurement is different. Once a qubit collapses to |0> or |1>, there is no way to recover the original superposition from the measurement result alone. The information in the amplitudes is lost.
This has a profound consequence for algorithm design: you cannot measure in the middle of a computation and continue using the quantum state as if nothing happened. Mid-circuit measurements can be useful for specific purposes (like quantum error correction or teleportation), but they fundamentally alter the computation.
This irreversibility is also why quantum computing is not the same as running all possible classical computations in parallel. The superposition exists, but you can only extract one classical outcome from it.
Measurement Bases
By default, qubits are measured in the computational basis (the Z basis): the outcomes are labeled 0 and 1, corresponding to basis states |0> and |1>. But you can measure in any basis by applying a gate that rotates the state before measuring.
Z Basis (Computational Basis)
No rotation needed. The measure() gate in Qiskit always performs a Z-basis measurement.
X Basis
Measuring in the X basis (the {|+>, |->} basis) requires applying a Hadamard gate before the measurement:
qc = QuantumCircuit(1, 1)
# Prepare the |+> state
qc.h(0)
# Measure in X basis: apply H then measure in computational basis
qc.h(0)
qc.measure(0, 0)
result = sim.run(qc, shots=1000).result()
print(result.get_counts()) # {'0': 1000} -- |+> is an eigenstate of X
The |+> state is an eigenstate of X with eigenvalue +1. Measuring it in the X basis always gives 0 (the +1 outcome). Measuring |+> in the Z basis (computational basis) gives random results.
Y Basis
Measuring in the Y basis requires an S-dagger gate followed by a Hadamard before the measurement:
qc = QuantumCircuit(1, 1)
qc.h(0) # Prepare |+>
qc.sdg(0) # S-dagger rotates to Y basis
qc.h(0)
qc.measure(0, 0)
Basis Rotation Summary
| Target basis | Gates before measure() | Why it works |
|---|---|---|
| Z | (none) | Default measurement basis |
| X | H | Maps |+> to |0>, |-> to |1> |
| Y | Sdg, H | Maps |i+> to |0>, |i-> to |1> |
This table is worth memorizing. Every time you want to measure in a non-Z basis, you apply the basis rotation gates and then call measure().
Measuring a Bell State in All Three Bases
Measuring the same entangled state in different bases reveals different correlations. The Bell state (|00> + |11>) / sqrt(2) has perfect correlations in Z, perfect correlations in X, and perfect anti-correlations in Y.
from qiskit import QuantumCircuit
from qiskit_aer import AerSimulator
sim = AerSimulator()
def make_bell():
"""Create a Bell state (|00> + |11>) / sqrt(2)."""
qc = QuantumCircuit(2, 2)
qc.h(0)
qc.cx(0, 1)
return qc
# Z-basis measurement: no rotation needed
qc_z = make_bell()
qc_z.measure([0, 1], [0, 1])
counts_z = sim.run(qc_z, shots=4000).result().get_counts()
print("Z basis:", counts_z) # {'00': ~2000, '11': ~2000}
# X-basis measurement: apply H to both qubits
qc_x = make_bell()
qc_x.h(0)
qc_x.h(1)
qc_x.measure([0, 1], [0, 1])
counts_x = sim.run(qc_x, shots=4000).result().get_counts()
print("X basis:", counts_x) # {'00': ~2000, '11': ~2000}
# Y-basis measurement: apply Sdg then H to both qubits
qc_y = make_bell()
qc_y.sdg(0)
qc_y.sdg(1)
qc_y.h(0)
qc_y.h(1)
qc_y.measure([0, 1], [0, 1])
counts_y = sim.run(qc_y, shots=4000).result().get_counts()
print("Y basis:", counts_y) # {'01': ~2000, '10': ~2000}
In the Z basis, both qubits always agree (00 or 11). In the X basis, they also always agree. In the Y basis, they always disagree (01 or 10). This pattern of correlations is a signature of entanglement and plays a central role in Bell inequality tests.
Multi-Qubit Measurement
For multi-qubit systems, measuring all qubits simultaneously collapses the entire state to one of the basis vectors. The probability of each n-bit outcome is the squared magnitude of the corresponding amplitude:
# Prepare a Bell state: (|00> + |11>) / sqrt(2)
qc = QuantumCircuit(2, 2)
qc.h(0)
qc.cx(0, 1)
qc.measure([0, 1], [0, 1])
result = sim.run(qc, shots=1000).result()
print(result.get_counts())
# {'00': ~500, '11': ~500}
# Never '01' or '10' -- Bell state correlations
You can also measure only a subset of qubits (partial measurement). The unmeasured qubits remain in a quantum state that depends on the measurement outcome:
# Measure only qubit 0
qc = QuantumCircuit(2, 1)
qc.h(0)
qc.cx(0, 1)
qc.measure(0, 0) # Measure qubit 0 only
# After measuring qubit 0:
# - If outcome is 0, qubit 1 is in state |0>
# - If outcome is 1, qubit 1 is in state |1>
The Measurement Operators (Projective Measurement)
Formally, measurements correspond to sets of projectors. In the computational basis, the projectors are:
P0 = |0><0| = [[1, 0], [0, 0]]
P1 = |1><1| = [[0, 0], [0, 1]]
import numpy as np
ket_0 = np.array([1, 0], dtype=complex)
ket_1 = np.array([0, 1], dtype=complex)
P0 = np.outer(ket_0, ket_0)
P1 = np.outer(ket_1, ket_1)
# Completeness: P0 + P1 = I
print(P0 + P1) # [[1, 0], [0, 1]]
# Probability of outcome 0: <psi|P0|psi>
psi = np.array([1/np.sqrt(3), np.sqrt(2/3)], dtype=complex)
prob_0 = np.real(psi.conj() @ P0 @ psi)
print(f"P(0) = {prob_0:.3f}") # 0.333
# Post-measurement state after outcome 0: P0|psi> / sqrt(P(0))
post_state = P0 @ psi / np.sqrt(prob_0)
print(post_state) # [1.+0.j, 0.+0.j] = |0>
These projectors satisfy two key properties: they are idempotent (P0^2 = P0), and they are complete (P0 + P1 = I). Any set of orthogonal projectors that sums to the identity defines a valid projective measurement.
Generalized Measurement (POVM)
Quantum mechanics allows measurements more general than projective ones. A POVM (Positive Operator-Valued Measure) is a set of positive semi-definite operators {E_k} satisfying:
sum_k E_k = I
The probability of outcome k is:
P(k) = <psi| E_k |psi> = Tr(E_k |psi><psi|)
Projective measurements are a special case where each E_k is a projector (E_k^2 = E_k). But POVMs can have more outcomes than the dimension of the Hilbert space. This extra freedom is essential for tasks like optimal state discrimination.
Why POVMs Matter: State Discrimination
Suppose you receive a qubit prepared in one of three non-orthogonal states, each with equal probability:
|psi_0> = |0>
|psi_1> = -1/2 |0> + sqrt(3)/2 |1>
|psi_2> = -1/2 |0> - sqrt(3)/2 |1>
These are the “trine states,” separated by 120 degrees on the Bloch sphere equator. No projective measurement can optimally distinguish all three, because projective measurements on a qubit have at most 2 outcomes. But a 3-outcome POVM can:
import numpy as np
# Define the three trine states
psi_0 = np.array([1, 0], dtype=complex)
psi_1 = np.array([-0.5, np.sqrt(3)/2], dtype=complex)
psi_2 = np.array([-0.5, -np.sqrt(3)/2], dtype=complex)
# The optimal POVM elements: E_k = (2/3) |psi_k_perp><psi_k_perp|
# where |psi_k_perp> is the state orthogonal to |psi_k>
def perp(state):
"""Return the state orthogonal to the given qubit state."""
return np.array([-np.conj(state[1]), np.conj(state[0])], dtype=complex)
psi_0_perp = perp(psi_0)
psi_1_perp = perp(psi_1)
psi_2_perp = perp(psi_2)
E0 = (2/3) * np.outer(psi_0_perp, psi_0_perp.conj())
E1 = (2/3) * np.outer(psi_1_perp, psi_1_perp.conj())
E2 = (2/3) * np.outer(psi_2_perp, psi_2_perp.conj())
# Verify completeness: E0 + E1 + E2 = I
print("Sum of POVM elements:")
print(np.round(E0 + E1 + E2, 10)) # [[1, 0], [0, 1]]
# Compute discrimination probabilities
# P(correct | state k) = <psi_k| E_k |psi_k>
# The trick: E_k is built from the state ORTHOGONAL to psi_k,
# so P(outcome k | state k) = 0 for the wrong state.
# When we get outcome k, we KNOW the state was NOT psi_k.
# With 3 equally likely states, this gives conclusive identification
# with probability 2/3 per trial (the remaining 1/3 is inconclusive).
for k, (psi, E) in enumerate([(psi_0, E0), (psi_1, E1), (psi_2, E2)]):
probs = [np.real(p.conj() @ E @ p) for p in [psi_0, psi_1, psi_2]]
print(f"P(outcome {k} | state 0,1,2) = "
f"{probs[0]:.3f}, {probs[1]:.3f}, {probs[2]:.3f}")
# Outcome 0 never fires for state 0, etc.
# This means outcome 0 rules out state 0, leaving states 1 or 2.
The POVM achieves the theoretical optimum for unambiguous discrimination of equally likely states. No projective measurement can match this performance.
Weak Measurement
Standard projective measurement fully collapses the quantum state. Weak measurement extracts partial information while causing minimal disturbance. The idea is to couple the system to an ancilla qubit with a small interaction strength and then measure the ancilla instead of the system.
How Weak Measurement Works
Consider measuring the Z observable on a qubit in state rho. A strong (projective) measurement produces the post-measurement state:
rho_strong = P0 rho P0 + P1 rho P1
This completely destroys coherence between |0> and |1>. A weak measurement with strength epsilon (0 <= epsilon <= 1) produces:
rho_weak = (1 - epsilon) * rho + epsilon * (P0 rho P0 + P1 rho P1)
When epsilon = 0, the state is undisturbed and you learn nothing. When epsilon = 1, it is a full projective measurement. For small epsilon, the state barely changes, but you gain a small amount of information from the ancilla measurement.
Weak Measurement Circuit
import numpy as np
from qiskit import QuantumCircuit
from qiskit_aer import AerSimulator
sim = AerSimulator()
def weak_measurement_circuit(theta_system, epsilon):
"""
Prepare a system qubit and perform a weak measurement.
theta_system: Ry rotation angle to prepare the system state
epsilon: measurement strength (0 = no disturbance, pi/2 = strong)
The weak measurement is implemented by a controlled-Ry on an ancilla.
A small epsilon means a small rotation, extracting little information.
"""
qc = QuantumCircuit(2, 1) # qubit 0 = system, qubit 1 = ancilla
# Prepare the system qubit
qc.ry(theta_system, 0)
# Weak coupling: controlled rotation of the ancilla
# A small angle means weak measurement
qc.cry(2 * epsilon, 0, 1)
# Measure the ancilla only (system is barely disturbed)
qc.measure(1, 0)
return qc
# Compare weak vs. strong measurement
# Prepare |+> state (theta = pi/2)
for eps_label, eps_val in [("Weak (eps=0.1)", 0.1),
("Medium (eps=0.5)", 0.5),
("Strong (eps=pi/2)", np.pi/2)]:
qc = weak_measurement_circuit(np.pi / 2, eps_val)
counts = sim.run(qc, shots=4000).result().get_counts()
p0 = counts.get('0', 0) / 4000
p1 = counts.get('1', 0) / 4000
print(f"{eps_label}: P(ancilla=0)={p0:.3f}, P(ancilla=1)={p1:.3f}")
# Weak measurement: ancilla is nearly always 0 (little information gained)
# Strong measurement: ancilla outcome correlates fully with system state
Weak measurement is used in practice for quantum feedback control, continuous monitoring of quantum systems, and experiments that probe quantum foundations (like direct measurement of the wavefunction).
The Measurement Problem and Interpretations
The Born rule and collapse postulate tell you how to calculate measurement outcomes. But they leave open a deeper question: what physically happens during a measurement? Different interpretations of quantum mechanics give different answers.
Copenhagen Interpretation
The quantum state is a mathematical tool for computing probabilities, not a description of physical reality. “Collapse” is Bayesian updating: when you learn the outcome, you update your description of the system. There is no physical process of collapse; the question “what is the state between preparation and measurement?” is considered meaningless.
Many-Worlds Interpretation
There is no collapse. The measurement entangles the observer with the system, and the combined wavefunction evolves unitarily. All outcomes occur, each in a different branch of the universal wavefunction. When you measure a qubit in state (|0> + |1>) / sqrt(2), the universe branches: in one branch you see 0, in another you see 1. Both branches are equally real.
QBism (Quantum Bayesianism)
Quantum states represent an agent’s personal beliefs about future measurement outcomes, not properties of the physical world. Collapse is a belief update. Two agents can assign different quantum states to the same system without contradiction, just as two people can hold different Bayesian priors.
Relational Quantum Mechanics
Quantum states are defined relative to an observer. The state of a system is not absolute; it depends on who is asking. A measurement establishes a relation between the observer and the system. Different observers may give different (but consistent) accounts of the same events.
Which Interpretation Matters for Quantum Computing?
All these interpretations are experimentally indistinguishable. They agree on every prediction the Born rule makes. For quantum computing practice, the Copenhagen viewpoint is the most pragmatic: prepare a state, apply gates, measure, use the probabilities. The other interpretations provide philosophical context but do not change how you write or run quantum circuits.
Mid-Circuit Measurement in Qiskit
Modern quantum hardware and simulators support measurement in the middle of a circuit, with classical feed-forward: subsequent gates can depend on measurement results. This is essential for quantum error correction, teleportation, and repeat-until-success protocols.
from qiskit import QuantumCircuit
from qiskit.circuit import ClassicalRegister
from qiskit_aer import AerSimulator
sim = AerSimulator()
# Quantum teleportation correction step:
# Prepare a Bell pair, measure qubit 0, conditionally correct qubit 1
qc = QuantumCircuit(3, 2)
# Prepare the state to teleport on qubit 0
qc.ry(1.2, 0) # Some arbitrary state
# Create Bell pair between qubits 1 and 2
qc.h(1)
qc.cx(1, 2)
# Bell measurement on qubits 0 and 1
qc.cx(0, 1)
qc.h(0)
qc.measure(0, 0)
qc.measure(1, 1)
# Classical feed-forward: correct qubit 2 based on measurement results
with qc.if_test((1, 1)): # If classical bit 1 is 1
qc.x(2) # Apply X correction
with qc.if_test((0, 1)): # If classical bit 0 is 1
qc.z(2) # Apply Z correction
print(qc.draw())
On real hardware, mid-circuit measurement introduces latency. The classical processing loop (measure, decide, apply correction) typically takes 1 to 10 microseconds on superconducting hardware. This is comparable to the T2 coherence time of many qubits, which limits how many rounds of conditional logic are practical before decoherence degrades the state.
The Deferred Measurement Principle
Any circuit with mid-circuit measurement and classical feed-forward can be replaced by an equivalent circuit that defers all measurements to the end. The trick is to replace each mid-circuit measurement with a CNOT to an ancilla, and replace each classically controlled gate with a quantum-controlled gate from the ancilla.
Example: Two Equivalent Circuits
Circuit A uses mid-circuit measurement. Circuit B defers the measurement.
from qiskit import QuantumCircuit
from qiskit_aer import AerSimulator
sim = AerSimulator()
# Circuit A: Mid-circuit measurement with classical feed-forward
qc_a = QuantumCircuit(2, 2)
qc_a.h(0)
qc_a.measure(0, 0)
with qc_a.if_test((0, 1)): # If qubit 0 measured 1
qc_a.x(1) # Flip qubit 1
qc_a.measure(1, 1)
counts_a = sim.run(qc_a, shots=4000).result().get_counts()
print("Circuit A (mid-circuit measurement):", counts_a)
# Outcomes: '00' (~50%) and '11' (~50%)
# Qubit 1 always matches qubit 0's measurement result
# Circuit B: Deferred measurement (no mid-circuit measurement)
qc_b = QuantumCircuit(2, 2)
qc_b.h(0)
qc_b.cx(0, 1) # Replace measure + conditional-X with a CNOT
qc_b.measure([0, 1], [0, 1])
counts_b = sim.run(qc_b, shots=4000).result().get_counts()
print("Circuit B (deferred measurement):", counts_b)
# Same outcomes: '00' (~50%) and '11' (~50%)
Both circuits produce identical output distributions. The deferred measurement principle is useful because:
- It simplifies circuit analysis (all measurements at the end).
- Some simulators and hardware backends do not support mid-circuit measurement.
- It proves that mid-circuit measurement never adds computational power; it is a convenience, not a necessity.
The Quantum Zeno Effect
Repeated frequent measurement of a quantum system can freeze its evolution. This is the quantum Zeno effect, named after Zeno’s paradox of the arrow.
The intuition: a small unitary rotation moves the state slightly away from |0>. If you measure immediately after, the probability of finding |0> is very high (the rotation was tiny), so the state collapses back to |0> almost every time. Frequent measurement keeps “resetting” the state before it can evolve.
import numpy as np
from qiskit import QuantumCircuit
from qiskit_aer import AerSimulator
sim = AerSimulator()
def zeno_experiment(n_rotations, measure_every_k):
"""
Apply n_rotations small Ry rotations to a qubit starting in |0>.
Measure in the Z basis every measure_every_k rotations.
Return the probability of finding the qubit still in |0>.
Each small rotation is Ry(pi / n_rotations), so n_rotations of them
would take |0> to |1> without any measurements (a full pi rotation).
"""
angle = np.pi / n_rotations # Small rotation angle
qc = QuantumCircuit(1, 1)
for step in range(n_rotations):
qc.ry(angle, 0)
# Measure every k steps (using measure + reset pattern)
if (step + 1) % measure_every_k == 0 and step < n_rotations - 1:
qc.measure(0, 0)
# Reset to |0> if measured |1> (simulates post-selection on |0>)
with qc.if_test((0, 1)):
qc.x(0) # Flip back to |0> (conditional reset)
# Final measurement
qc.measure(0, 0)
counts = sim.run(qc, shots=4000).result().get_counts()
prob_0 = counts.get('0', 0) / 4000
return prob_0
n = 100 # Total number of small rotations
# No intermediate measurements: the state rotates fully to |1>
p_no_measure = zeno_experiment(n, measure_every_k=n + 1)
print(f"No measurement: P(|0>) = {p_no_measure:.3f}") # ~0.000
# Measure every 10 steps
p_every_10 = zeno_experiment(n, measure_every_k=10)
print(f"Measure every 10 steps: P(|0>) = {p_every_10:.3f}") # ~0.73
# Measure every step (strong Zeno effect)
p_every_1 = zeno_experiment(n, measure_every_k=1)
print(f"Measure every step: P(|0>) = {p_every_1:.3f}") # ~0.97
The math behind this: each small rotation by angle theta = pi/n changes the state from |0> to cos(theta/2)|0> + sin(theta/2)|1>. The probability of measuring |0> after one rotation is cos^2(theta/2). After n measurements (one per rotation), the probability of staying in |0> the entire time is:
P(stay in |0>) = cos^2(pi / 2n)^n --> 1 as n --> infinity
The more frequently you measure, the more likely the state is to remain frozen. This effect is experimentally verified and has practical applications in quantum error suppression.
Expectation Value Estimation
Quantum algorithms often need to estimate expectation values like <Z>, not just sample individual outcomes. The expectation value of Z for a qubit state is:
<Z> = P(0) * (+1) + P(1) * (-1) = P(0) - P(1)
Given n shots with n0 counts of outcome 0 and n1 counts of outcome 1:
<Z>_estimated = (n0 - n1) / n
The statistical uncertainty (standard error) is:
sigma = sqrt(1 - <Z>^2) / sqrt(n)
For a maximally uncertain state (<Z> = 0): sigma = 1/sqrt(n). For a nearly deterministic state (<Z> = 0.9): sigma = sqrt(1 - 0.81) / sqrt(n) = 0.44 / sqrt(n).
import numpy as np
from qiskit import QuantumCircuit
from qiskit_aer import AerSimulator
sim = AerSimulator()
# Prepare a state with known <Z>
# Ry(theta)|0> = cos(theta/2)|0> + sin(theta/2)|1>
# <Z> = cos^2(theta/2) - sin^2(theta/2) = cos(theta)
theta = 1.2 # radians
expected_z = np.cos(theta)
qc = QuantumCircuit(1, 1)
qc.ry(theta, 0)
qc.measure(0, 0)
n_shots = 4000
counts = sim.run(qc, shots=n_shots).result().get_counts()
n0 = counts.get('0', 0)
n1 = counts.get('1', 0)
z_est = (n0 - n1) / n_shots
sigma = np.sqrt(1 - z_est**2) / np.sqrt(n_shots)
print(f"True <Z>: {expected_z:.4f}")
print(f"Estimated <Z>: {z_est:.4f}")
print(f"Uncertainty: {sigma:.4f}")
print(f"Within 2 sigma: {abs(z_est - expected_z) < 2 * sigma}")
This procedure generalizes to any Pauli observable. To estimate <X>, apply H before measuring. To estimate <Y>, apply Sdg then H. For multi-qubit Paulis like <Z0 Z1>, measure both qubits and compute the parity: the eigenvalue is (+1) if the outcomes agree and (-1) if they disagree.
Joint Measurement and the CHSH Bell Inequality
The CHSH inequality is the most famous test of quantum entanglement. It bounds the correlations achievable by any local hidden variable theory. Quantum mechanics violates this bound.
Setup
For a Bell state, choose two measurement settings for each qubit:
- Alice measures Z or X on qubit 0
- Bob measures (Z + X) / sqrt(2) or (Z - X) / sqrt(2) on qubit 1
The correlation for a pair of measurement settings (A, B) is:
C(A, B) = P(same outcome) - P(different outcome)
The CHSH parameter S combines four correlations:
S = C(a0, b0) + C(a0, b1) + C(a1, b0) - C(a1, b1)
Classical physics requires |S| <= 2. Quantum mechanics allows S = 2 * sqrt(2) = 2.83.
import numpy as np
from qiskit import QuantumCircuit
from qiskit_aer import AerSimulator
sim = AerSimulator()
def measure_correlation(angle_a, angle_b, n_shots=8000):
"""
Prepare a Bell state and measure qubit 0 at angle_a,
qubit 1 at angle_b (both in the ZX plane).
Return the correlation <A * B>.
Rotation Ry(-2*angle) maps the measurement axis to Z.
"""
qc = QuantumCircuit(2, 2)
# Prepare Bell state (|00> + |11>) / sqrt(2)
qc.h(0)
qc.cx(0, 1)
# Rotate measurement bases
qc.ry(-2 * angle_a, 0)
qc.ry(-2 * angle_b, 1)
qc.measure([0, 1], [0, 1])
counts = sim.run(qc, shots=n_shots).result().get_counts()
# Compute correlation: +1 for same outcome, -1 for different
corr = 0
for bitstring, count in counts.items():
b0 = int(bitstring[0]) # qubit 1 (Qiskit little-endian)
b1 = int(bitstring[1]) # qubit 0
sign = 1 if b0 == b1 else -1
corr += sign * count
return corr / n_shots
# CHSH measurement angles (optimal for maximum violation)
a0 = 0 # Alice setting 0: Z
a1 = np.pi / 4 # Alice setting 1: (Z+X)/sqrt(2), i.e. pi/4
b0 = np.pi / 8 # Bob setting 0: pi/8
b1 = -np.pi / 8 # Bob setting 1: -pi/8
# Four correlations
C00 = measure_correlation(a0, b0)
C01 = measure_correlation(a0, b1)
C10 = measure_correlation(a1, b0)
C11 = measure_correlation(a1, b1)
S = C00 + C01 + C10 - C11
print(f"C(a0,b0) = {C00:.3f}")
print(f"C(a0,b1) = {C01:.3f}")
print(f"C(a1,b0) = {C10:.3f}")
print(f"C(a1,b1) = {C11:.3f}")
print(f"S = {S:.3f}")
print(f"Classical bound: |S| <= 2")
print(f"Quantum prediction: S = 2*sqrt(2) = {2*np.sqrt(2):.3f}")
print(f"Violation: {abs(S) > 2}")
The measured S is approximately 2.83, well above the classical limit of 2. This demonstrates that entangled qubits exhibit correlations that no classical system can reproduce.
Quantum State Tomography from Measurements
State tomography reconstructs the full density matrix of a qubit from measurements in multiple bases. For a single qubit, the density matrix is:
rho = (I + <X> X + <Y> Y + <Z> Z) / 2
where <X>, <Y>, <Z> are the expectation values of the three Pauli operators. You estimate each one by measuring in the corresponding basis with many shots.
import numpy as np
from qiskit import QuantumCircuit
from qiskit_aer import AerSimulator
sim = AerSimulator()
def estimate_expectation(state_prep_fn, basis, n_shots=4000):
"""
Estimate the expectation value of a Pauli operator.
state_prep_fn: function that adds state preparation gates to a circuit
basis: 'Z', 'X', or 'Y'
"""
qc = QuantumCircuit(1, 1)
state_prep_fn(qc)
# Basis rotation
if basis == 'X':
qc.h(0)
elif basis == 'Y':
qc.sdg(0)
qc.h(0)
# Z: no rotation needed
qc.measure(0, 0)
counts = sim.run(qc, shots=n_shots).result().get_counts()
n0 = counts.get('0', 0)
n1 = counts.get('1', 0)
return (n0 - n1) / n_shots
# Prepare a known state: Ry(0.8) Rz(1.5) |0>
def prep(qc):
qc.rz(1.5, 0)
qc.ry(0.8, 0)
# Estimate Bloch vector components
exp_x = estimate_expectation(prep, 'X')
exp_y = estimate_expectation(prep, 'Y')
exp_z = estimate_expectation(prep, 'Z')
print(f"<X> = {exp_x:.3f}")
print(f"<Y> = {exp_y:.3f}")
print(f"<Z> = {exp_z:.3f}")
# Reconstruct density matrix
I = np.eye(2, dtype=complex)
X = np.array([[0, 1], [1, 0]], dtype=complex)
Y = np.array([[0, -1j], [1j, 0]], dtype=complex)
Z = np.array([[1, 0], [0, -1]], dtype=complex)
rho_reconstructed = (I + exp_x * X + exp_y * Y + exp_z * Z) / 2
print("\nReconstructed density matrix:")
print(np.round(rho_reconstructed, 3))
# Compare with the true state vector
from qiskit.quantum_info import Statevector
qc_true = QuantumCircuit(1)
prep(qc_true)
psi_true = Statevector.from_instruction(qc_true)
rho_true = np.outer(psi_true.data, psi_true.data.conj())
print("\nTrue density matrix:")
print(np.round(rho_true, 3))
# Compute fidelity: F = Tr(rho_true @ rho_reconstructed) for pure states
fidelity = np.real(np.trace(rho_true @ rho_reconstructed))
print(f"\nFidelity: {fidelity:.4f}")
On real hardware, this procedure is how experimentalists verify that their qubits are in the intended state. The fidelity metric quantifies how close the reconstructed state is to the target, with 1.0 being a perfect match. With 4000 shots per basis, you can expect fidelities above 0.99 for ideal simulation and 0.90 to 0.99 on current hardware, depending on error rates.
Ancilla-Assisted Measurement: The Hadamard Test
Many quantum algorithms use ancilla qubits to estimate properties of a unitary operator without directly measuring the main register. The Hadamard test is the simplest example.
To estimate Re(<psi|U|psi>) for a unitary U and state |psi>:
- Prepare an ancilla qubit in |+> = (|0> + |1>) / sqrt(2).
- Apply controlled-U: if the ancilla is |1>, apply U to |psi>.
- Apply H to the ancilla.
- Measure the ancilla.
The probability of measuring 0 on the ancilla is:
P(0) = (1 + Re(<psi|U|psi>)) / 2
So Re(<psi|U|psi>) = 2 * P(0) - 1.
from qiskit import QuantumCircuit
from qiskit_aer import AerSimulator
sim = AerSimulator()
# Hadamard test: estimate Re(<+|Z|+>)
# True value: <+|Z|+> = 0 (the |+> state has equal weight on |0> and |1>)
qc = QuantumCircuit(2, 1)
# Prepare system qubit (qubit 1) in |+>
qc.h(1)
# Prepare ancilla (qubit 0) in |+>
qc.h(0)
# Controlled-Z: apply Z to system qubit if ancilla is |1>
qc.cz(0, 1)
# Apply H to ancilla
qc.h(0)
# Measure ancilla
qc.measure(0, 0)
counts = sim.run(qc, shots=4000).result().get_counts()
p0 = counts.get('0', 0) / 4000
re_expectation = 2 * p0 - 1
print(f"P(0) = {p0:.3f}")
print(f"Re(<+|Z|+>) estimated = {re_expectation:.3f}") # ~0.000
print(f"True value = 0.000")
To also estimate Im(<psi|U|psi>), replace the initial H on the ancilla with H followed by Sdg (preparing |0> - i|1>) / sqrt(2)). The ancilla measurement then gives:
P(0) = (1 + Im(<psi|U|psi>)) / 2
The Hadamard test is the building block for quantum phase estimation, quantum chemistry algorithms, and many variational methods.
Designing Algorithms Around Measurement
Because measurement is irreversible and random, quantum algorithms must be structured so that the correct answer has high probability before measurement:
- Start with all qubits in a known state (usually |0>).
- Apply gates to create a superposition over many possibilities.
- Use interference to amplify amplitudes of correct answers.
- Measure and use the result.
Grover’s algorithm, for example, applies a sequence of reflections that iteratively rotate the state toward the target answer. After O(sqrt(N)) iterations, the target answer has amplitude close to 1, so measurement almost certainly gives the correct result.
Common Mistakes with Quantum Measurement
1. Measuring mid-circuit to “debug” the state
A common beginner mistake is inserting measurements in the middle of a circuit to check intermediate states. This collapses the superposition and ruins all subsequent quantum operations. If you need to inspect intermediate states during development, use the statevector simulator instead:
from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector
qc = QuantumCircuit(2)
qc.h(0)
qc.cx(0, 1)
# Inspect without collapsing (simulation only)
sv = Statevector.from_instruction(qc)
print(sv) # Shows the full quantum state
2. Qiskit’s little-endian qubit ordering
In Qiskit, qubit 0 is the rightmost bit in the output bitstring. A measurement result of '01' means qubit 0 = 1 and qubit 1 = 0. This is the opposite of what many beginners expect.
from qiskit import QuantumCircuit
from qiskit_aer import AerSimulator
sim = AerSimulator()
qc = QuantumCircuit(2, 2)
qc.x(0) # Flip qubit 0 to |1>
# Qubit 0 = |1>, Qubit 1 = |0>
qc.measure([0, 1], [0, 1])
counts = sim.run(qc, shots=1).result().get_counts()
print(counts) # {'01'} -- rightmost bit is qubit 0
# bitstring '01': qubit 1 = 0 (left), qubit 0 = 1 (right)
3. Assuming two measurements always agree
Measuring the same qubit twice gives the same result only if no gates are applied between measurements. If you apply a gate between the two measurements, the second measurement depends on both the first result and the gate.
from qiskit import QuantumCircuit
from qiskit_aer import AerSimulator
sim = AerSimulator()
# Two measurements with a gate in between
qc = QuantumCircuit(1, 2)
qc.h(0)
qc.measure(0, 0) # First measurement: random 0 or 1
qc.h(0) # Apply H after collapse
qc.measure(0, 1) # Second measurement: can differ from the first
counts = sim.run(qc, shots=4000).result().get_counts()
print(counts)
# All four outcomes {00, 01, 10, 11} are possible
4. Confusing expectation value with most probable outcome
The expectation value <Z> is NOT the same as the most probable measurement outcome. For a state with P(0) = 0.6 and P(1) = 0.4:
<Z> = 0.6 * (+1) + 0.4 * (-1) = 0.2
The most probable outcome is 0 (which gives Z eigenvalue +1), but the expectation value is 0.2, not 1. This distinction matters when using measurement results in variational algorithms, where the cost function is an expectation value.
5. Forgetting basis rotations
Measurement in Qiskit always happens in the Z basis. To measure in another basis, you must apply the appropriate rotation gates before calling measure(). Forgetting this is a common source of wrong results:
# WRONG: This measures in Z basis, not X basis
qc_wrong = QuantumCircuit(1, 1)
qc_wrong.h(0) # Prepare |+>
qc_wrong.measure(0, 0) # Z-basis measurement: random result
# CORRECT: Apply H to rotate X basis to Z basis, then measure
qc_right = QuantumCircuit(1, 1)
qc_right.h(0) # Prepare |+>
qc_right.h(0) # Rotate X to Z basis
qc_right.measure(0, 0) # Always gives 0 (|+> is X eigenstate)
Next Steps
- Bra-Ket Notation Explained, the formal notation for states and measurement operators
- How Quantum Algorithms Work, showing how interference sets up useful measurement outcomes
- Quantum Entanglement Explained, explaining how measurement of one qubit affects another
- Bell Inequalities and the CHSH Test, a full tutorial on testing quantum nonlocality
- Quantum State Tomography in Qiskit, detailed state reconstruction on real hardware
- Dynamic Circuits in Qiskit, mid-circuit measurement and classical feed-forward
Was this tutorial helpful?