Concepts Beginner Free 43/53 in series 15 min

Qubits vs Classical Bits: The Key Differences

A side-by-side comparison of classical bits and qubits: what makes a qubit fundamentally different, what it can and cannot do, and common misconceptions.

What you'll learn

  • qubits
  • classical bits
  • superposition
  • quantum states
  • Bloch sphere

Prerequisites

  • Basic Python (variables, functions, loops)
  • No quantum physics background needed

The qubit is the basic unit of quantum information, just as the classical bit is the basic unit of classical information. But qubits behave in ways that have no classical analogue. This tutorial compares bits and qubits side by side, works through the mathematics of quantum states in detail, and addresses common misconceptions with concrete code examples.

The Classical Bit

A classical bit is the simplest possible memory element. It holds exactly one of two values: 0 or 1. Nothing else. At any given moment, a bit has a definite, readable value. You can copy it, examine it, and overwrite it without constraint.

How Bits Are Physically Implemented

In modern digital electronics, bits are stored as voltage levels in CMOS (Complementary Metal-Oxide-Semiconductor) transistors. A logic “1” corresponds to a voltage near VDD (typically 1.8V or 3.3V, depending on the chip), and a logic “0” corresponds to a voltage near ground (0V).

The key property that makes classical bits robust is the noise margin. A CMOS inverter does not require the voltage to be exactly 0V or exactly 3.3V to register a valid bit. Any voltage below a certain threshold counts as 0, and any voltage above a different threshold counts as 1. Small fluctuations from thermal noise, electromagnetic interference, or manufacturing variations are simply ignored. The circuit “snaps” the analog voltage to the nearest digital value.

This means you can:

  • Read a classical bit billions of times without changing its value.
  • Copy it freely by routing the voltage signal to another transistor.
  • Store it indefinitely as long as power is supplied (for SRAM) or charge is retained (for DRAM/Flash).

Contrast this with a quantum system, where even a single stray photon interacting with a qubit can irreversibly disturb its quantum state. Classical bits are engineered to be resilient against noise. Qubits are inherently fragile.

PropertyClassical Bit
Possible states0 or 1
Physical encodingVoltage level in a CMOS transistor
Information content1 bit
ReadingDeterministic: always gives the stored value
CopyingUnrestricted
OperationsAND, OR, NOT, and combinations
ReversibilityMost operations are irreversible
Noise toleranceHigh (voltage noise margin)

The Qubit

A qubit is a two-level quantum system. Before measurement, it can be in a superposition of |0> and |1>. The state of a qubit is described by two complex amplitudes:

|psi> = alpha |0> + beta |1>

where |alpha|^2 + |beta|^2 = 1. The values alpha and beta are complex numbers, and this normalization constraint ensures the total probability of all measurement outcomes sums to 1.

import numpy as np

# |0> state: definitely zero
state_0 = np.array([1, 0], dtype=complex)

# |1> state: definitely one
state_1 = np.array([0, 1], dtype=complex)

# Superposition: (|0> + |1>) / sqrt(2)
plus_state = np.array([1/np.sqrt(2), 1/np.sqrt(2)], dtype=complex)

# Verify normalization
print(np.sum(np.abs(plus_state)**2))  # 1.0
PropertyClassical BitQubit
Possible states0 or 1Continuous range of superpositions
Physical encodingVoltage levelQuantum two-level system
Information content1 bit1 qubit (not more classical bits)
ReadingDeterministicProbabilistic; destroys superposition
CopyingUnrestrictedForbidden (no-cloning theorem)
OperationsBoolean logicUnitary matrices (reversible)
ReversibilityMostly irreversibleGates are reversible; measurement is not
Noise toleranceHighVery low (decoherence)

State Space Comparison

For n classical bits, there are exactly 2^n possible states, and the system is always in exactly one of them. For 2 classical bits, the four possibilities are 00, 01, 10, and 11. You can represent this with a simple integer or a short array.

For n qubits, the state is a vector of 2^n complex amplitudes whose squared magnitudes sum to 1. For 2 qubits, the state vector has 4 components, one for each computational basis state. The system is not “in one of” these states; instead, it holds a complex amplitude for every one of them simultaneously.

import numpy as np

# --- Classical: 2 bits ---
# A 2-bit register is in exactly one of these four states at any time.
classical_states = ["00", "01", "10", "11"]
current_state = "10"  # The register holds this specific value

# --- Quantum: 2 qubits ---
# Basis states as vectors in a 4-dimensional complex space
q_00 = np.array([1, 0, 0, 0], dtype=complex)  # |00>
q_01 = np.array([0, 1, 0, 0], dtype=complex)  # |01>
q_10 = np.array([0, 0, 1, 0], dtype=complex)  # |10>
q_11 = np.array([0, 0, 0, 1], dtype=complex)  # |11>

# An equal superposition of all four states
uniform = np.array([0.5, 0.5, 0.5, 0.5], dtype=complex)
print("Uniform superposition:", uniform)
print("Probabilities:", np.abs(uniform)**2)  # [0.25, 0.25, 0.25, 0.25]

# A Bell state: (|00> + |11>) / sqrt(2)
# This state CANNOT be written as a product of two individual qubit states.
bell = np.array([1/np.sqrt(2), 0, 0, 1/np.sqrt(2)], dtype=complex)
print("\nBell state:", bell)
print("Probabilities:", np.abs(bell)**2)  # [0.5, 0.0, 0.0, 0.5]

The Bell state is a good example of the difference in information structure. Classically, if you know the state of each bit, you know the state of the whole register. Quantum mechanically, the Bell state has no well-defined state for either individual qubit. The information lives entirely in the correlations between the two qubits.

The Bloch Sphere

Every valid single-qubit state corresponds to exactly one point on the surface of a unit sphere called the Bloch sphere. The north pole is |0> and the south pole is |1>. Every other point represents a superposition.

          |0>  (north pole)
           |
    |+i> --+-- |-i>
   /        |        \
 |+> -------+------- |->
   \        |        /
           |
          |1>  (south pole)

The two angles that parameterize the Bloch sphere:

  • Theta (0 to pi): controls the mixture of |0> and |1>. At 0, the qubit is |0>. At pi, it is |1>. At pi/2, it is on the equator, in maximum superposition.
  • Phi (0 to 2*pi): the phase angle around the equator. It is invisible to measurement in the computational basis but affects how gates transform the qubit.
import numpy as np

def bloch_to_state(theta, phi):
    """Convert Bloch sphere angles to qubit state vector."""
    return np.array([
        np.cos(theta / 2),
        np.exp(1j * phi) * np.sin(theta / 2)
    ], dtype=complex)

# North pole: |0>
print(bloch_to_state(0, 0))             # [1.+0.j, 0.+0.j]

# South pole: |1>
print(bloch_to_state(np.pi, 0))         # ~[0.+0.j, 1.+0.j]

# Equator: |+>
print(bloch_to_state(np.pi/2, 0))       # [0.707+0.j, 0.707+0.j]

# Equator: |i> = (|0> + i|1>) / sqrt(2)
print(bloch_to_state(np.pi/2, np.pi/2)) # [0.707+0.j, 0.+0.707j]

A classical bit is like being restricted to two points: north pole or south pole. A qubit can be at any point on the entire sphere’s surface. When you measure it, it snaps to one of the two poles.

Complex Amplitude Arithmetic

Quantum gates transform qubit states by matrix multiplication. Working through a concrete example reveals why the phase of a complex amplitude is physically meaningful, not just a mathematical curiosity.

Start with |0> = [1, 0]. Apply the Hadamard gate H:

H = (1/sqrt(2)) * [[1,  1],
                    [1, -1]]

H|0> = [1/sqrt(2), 1/sqrt(2)] = |+>

Now apply the Z gate (phase flip) to |+>:

Z = [[1,  0],
     [0, -1]]

Z|+> = [1/sqrt(2), -1/sqrt(2)] = |->

Now apply H again:

H|-> = H * [1/sqrt(2), -1/sqrt(2)] = [0, 1] = |1>

So the sequence H, then Z, then H produces the same result as the X (NOT) gate. The Z gate only changes the sign of the |1> component, which seems like it should be invisible since measurement probabilities depend on |amplitude|^2. But sandwiching Z between two Hadamards converts that phase change into a measurable bit flip. Phase is real and observable when combined with interference.

import numpy as np

# Define gates
H = (1/np.sqrt(2)) * np.array([[1, 1], [1, -1]], dtype=complex)
Z = np.array([[1, 0], [0, -1]], dtype=complex)
X = np.array([[0, 1], [1, 0]], dtype=complex)

# Start with |0>
state = np.array([1, 0], dtype=complex)

# Step 1: Apply H
state = H @ state
print("After H:  ", state)   # [0.707, 0.707] = |+>

# Step 2: Apply Z
state = Z @ state
print("After Z:  ", state)   # [0.707, -0.707] = |->

# Step 3: Apply H
state = H @ state
print("After H:  ", state)   # [0, 1] = |1>

# Verify: H Z H = X
HZH = H @ Z @ H
print("\nH Z H matrix:")
print(np.round(HZH, 10))
print("\nX matrix:")
print(X)
print("\nAre they equal?", np.allclose(HZH, X))  # True

This identity (HZH = X) is a fundamental example of the Hadamard gate converting between the Z basis and the X basis. It shows that quantum computation relies on manipulating phases that become visible only through interference.

Measurement Statistics in Detail

When you measure a qubit in the computational basis (the Z basis), each outcome has a probability equal to the squared magnitude of its amplitude.

For |+> = [1/sqrt(2), 1/sqrt(2)]:

  • P(0) = |1/sqrt(2)|^2 = 0.5
  • P(1) = |1/sqrt(2)|^2 = 0.5

For |psi> = [sqrt(0.3), sqrt(0.7)]:

  • P(0) = |sqrt(0.3)|^2 = 0.3
  • P(1) = |sqrt(0.7)|^2 = 0.7

You can simulate repeated measurement using random sampling:

import numpy as np

np.random.seed(42)

# Define a state with P(0) = 0.3, P(1) = 0.7
alpha = np.sqrt(0.3)
beta = np.sqrt(0.7)
state = np.array([alpha, beta], dtype=complex)

# Verify normalization
print("Norm:", np.sum(np.abs(state)**2))  # 1.0

# Simulate 1000 measurements
probabilities = np.abs(state)**2
outcomes = np.random.choice([0, 1], size=1000, p=probabilities)

count_0 = np.sum(outcomes == 0)
count_1 = np.sum(outcomes == 1)
print(f"Measured |0>: {count_0}/1000 = {count_0/1000:.3f} (theory: 0.300)")
print(f"Measured |1>: {count_1}/1000 = {count_1/1000:.3f} (theory: 0.700)")

Measurement in Other Bases

Measurement in the computational basis is not the only option. To measure in the X basis (the {|+>, |->} basis), apply a Hadamard gate before measuring in the Z basis.

This has a striking consequence. The state |+> gives outcome 0 with certainty when measured in the X basis (because H|+> = |0>), and the state |-> gives outcome 1 with certainty (because H|-> = |1>). These two states are perfectly distinguishable in the X basis but completely indistinguishable in the Z basis, where both give 50/50 outcomes.

import numpy as np

H = (1/np.sqrt(2)) * np.array([[1, 1], [1, -1]], dtype=complex)

# |+> state
plus = np.array([1/np.sqrt(2), 1/np.sqrt(2)], dtype=complex)

# |-> state
minus = np.array([1/np.sqrt(2), -1/np.sqrt(2)], dtype=complex)

# Measure |+> in X basis: apply H then check probabilities
plus_in_x = H @ plus
print("|+> after H:", np.round(plus_in_x, 10))  # [1, 0] = |0>
print("P(0) =", np.abs(plus_in_x[0])**2)        # 1.0
print("P(1) =", np.abs(plus_in_x[1])**2)        # 0.0

# Measure |-> in X basis: apply H then check probabilities
minus_in_x = H @ minus
print("\n|-> after H:", np.round(minus_in_x, 10))  # [0, 1] = |1>
print("P(0) =", np.abs(minus_in_x[0])**2)          # 0.0
print("P(1) =", np.abs(minus_in_x[1])**2)          # 1.0

The choice of measurement basis determines what information you extract from a qubit. This is fundamentally different from classical bits, where reading a bit always yields the same answer regardless of how you choose to read it.

Why Superposition Is Not Just Probability

A common source of confusion: is a qubit in the state |+> just like a classical coin that is “secretly heads or tails with 50/50 probability”? The answer is no, and you can prove it with a simple experiment.

Consider a classical coin that is secretly in a definite state (either H or T) with equal probability. If you apply a “classical Hadamard” (a fair coin flip), the result is still 50/50 regardless of the secret state. H goes to 50/50, and T goes to 50/50.

Now consider a qubit in the state |+>. Apply the quantum Hadamard:

H|+> = H * (|0> + |1>)/sqrt(2) = (H|0> + H|1>)/sqrt(2) = (|+> + |->)/sqrt(2) = |0>

The result is |0> with certainty. This deterministic outcome is impossible for a classical probability distribution.

import numpy as np

np.random.seed(42)
H = (1/np.sqrt(2)) * np.array([[1, 1], [1, -1]], dtype=complex)

# --- Quantum case: H applied to |+> ---
plus = np.array([1/np.sqrt(2), 1/np.sqrt(2)], dtype=complex)
result_quantum = H @ plus
print("Quantum: H|+> =", np.round(result_quantum, 10))
print("P(0) =", np.round(np.abs(result_quantum[0])**2, 10))  # 1.0
print("P(1) =", np.round(np.abs(result_quantum[1])**2, 10))  # 0.0

# --- Classical case: H applied to a mixture ---
# Simulate: with probability 0.5 the coin is |0>, with 0.5 it is |1>.
# Apply H to each possibility and average the outcome probabilities.
prob_0_given_start_0 = np.abs(H @ np.array([1, 0], dtype=complex))**2
prob_0_given_start_1 = np.abs(H @ np.array([0, 1], dtype=complex))**2

# Classical mixture: average the probabilities
classical_probs = 0.5 * prob_0_given_start_0 + 0.5 * prob_0_given_start_1
print("\nClassical mixture after H:")
print("P(0) =", classical_probs[0])  # 0.5
print("P(1) =", classical_probs[1])  # 0.5

The quantum case gives P(0) = 1.0. The classical mixture gives P(0) = 0.5. The difference is interference: in the quantum case, the amplitudes for |1> from the two paths cancel each other out. In the classical case, probabilities are always non-negative and can never cancel. This is the core distinction between coherent superposition and incoherent mixture.

The Density Matrix Distinction

The density matrix formalism makes the difference between quantum superposition and classical probability mathematically precise.

For a pure state |+> = (|0> + |1>)/sqrt(2), the density matrix is:

rho_pure = |+><+| = [[0.5, 0.5],
                      [0.5, 0.5]]

For a classical mixture (50% chance of |0>, 50% chance of |1>), the density matrix is:

rho_mixed = 0.5 * |0><0| + 0.5 * |1><1| = [[0.5, 0],
                                              [0,   0.5]]

Both have the same diagonal entries, so both give the same measurement statistics in the Z basis: 50/50. But the off-diagonal elements (called “coherences”) are nonzero only for the pure superposition. You can detect these coherences by measuring in a different basis.

The purity of a density matrix, Tr(rho^2), quantifies how “quantum” a state is. A pure state has purity 1.0; a maximally mixed single-qubit state has purity 0.5.

import numpy as np

# Pure state |+>
plus = np.array([[1/np.sqrt(2)], [1/np.sqrt(2)]], dtype=complex)
rho_pure = plus @ plus.conj().T
print("Pure state density matrix:")
print(np.round(rho_pure, 4))
print("Purity:", np.round(np.trace(rho_pure @ rho_pure).real, 4))  # 1.0

# Classical mixture: 50% |0> + 50% |1>
ket_0 = np.array([[1], [0]], dtype=complex)
ket_1 = np.array([[0], [1]], dtype=complex)
rho_mixed = 0.5 * (ket_0 @ ket_0.conj().T) + 0.5 * (ket_1 @ ket_1.conj().T)
print("\nMixed state density matrix:")
print(np.round(rho_mixed, 4))
print("Purity:", np.round(np.trace(rho_mixed @ rho_mixed).real, 4))  # 0.5

# Measurement in Z basis: same for both
print("\nZ-basis measurement probabilities:")
print("Pure:  P(0)={:.2f}, P(1)={:.2f}".format(rho_pure[0,0].real, rho_pure[1,1].real))
print("Mixed: P(0)={:.2f}, P(1)={:.2f}".format(rho_mixed[0,0].real, rho_mixed[1,1].real))

# Measurement in X basis: apply H, then measure in Z
H = (1/np.sqrt(2)) * np.array([[1, 1], [1, -1]], dtype=complex)

rho_pure_x = H @ rho_pure @ H.conj().T
rho_mixed_x = H @ rho_mixed @ H.conj().T

print("\nX-basis measurement probabilities:")
print("Pure:  P(0)={:.2f}, P(1)={:.2f}".format(rho_pure_x[0,0].real, rho_pure_x[1,1].real))
print("Mixed: P(0)={:.2f}, P(1)={:.2f}".format(rho_mixed_x[0,0].real, rho_mixed_x[1,1].real))
# Pure gives P(0)=1.00, P(1)=0.00
# Mixed gives P(0)=0.50, P(1)=0.50

The X-basis measurement distinguishes the two cases perfectly. The pure superposition gives a deterministic outcome; the classical mixture remains random. This is the experimental signature of quantum coherence.

What Qubits Can Do That Bits Cannot

Superposition

A qubit can hold a weighted combination of 0 and 1 simultaneously. Two qubits can represent a superposition over all four two-bit strings at once. Ten qubits can hold a superposition over all 1024 ten-bit strings. This exponential scaling of the state space is one reason quantum computers can be powerful.

But this advantage is subtle. You cannot read all 2^n amplitudes out of n qubits. You can only measure and get one n-bit string each time.

Interference

Quantum amplitudes can cancel each other out (destructive interference) or reinforce each other (constructive interference). Quantum algorithms exploit this to suppress wrong answers and amplify correct answers before measurement.

The H-Z-H = X identity from the section above is a minimal example of interference in action. The Z gate introduces a phase, and the surrounding Hadamards convert that phase difference into a measurable probability difference.

Entanglement

Multiple qubits can be entangled: their joint state cannot be described as independent individual states. Entanglement enables correlations between qubits that have no classical analogue and is a key resource in quantum algorithms, teleportation, and quantum cryptography.

What Qubits Cannot Do

Store More Than 1 Classical Bit of Information (Holevo Bound)

The Holevo bound proves that you cannot extract more than 1 classical bit of information from a single qubit, regardless of how cleverly you encode or measure it. All the amplitude information is not freely accessible: measuring always gives a single classical outcome.

More formally, if you encode a classical message X into a qubit and obtain measurement outcome Y, the mutual information satisfies I(X:Y) <= S(rho) <= 1 bit, where S(rho) is the von Neumann entropy of the average state.

Here is a concrete demonstration. Suppose you try to encode 2 classical bits (4 messages) into a single qubit using four different states. The best measurement strategy can recover at most 1 bit of information on average.

import numpy as np

np.random.seed(42)

# Encode 4 messages as 4 qubit states (evenly spaced on the Bloch equator)
states = []
for k in range(4):
    theta = np.pi / 2  # equator
    phi = k * np.pi / 2  # 0, pi/2, pi, 3pi/2
    state = np.array([
        np.cos(theta/2),
        np.exp(1j * phi) * np.sin(theta/2)
    ], dtype=complex)
    states.append(state)

# Optimal measurement: pick the basis that maximizes information.
# With 4 equidistant equatorial states and a 2-outcome measurement,
# the best strategy is to measure in a basis aligned with two of them.
# We try measuring in the Z basis and the X basis and compute success rates.

n_trials = 100000

for basis_name, basis_vectors in [
    ("Z basis", [np.array([1, 0], dtype=complex), np.array([0, 1], dtype=complex)]),
    ("X basis", [np.array([1, 1], dtype=complex)/np.sqrt(2),
                 np.array([1, -1], dtype=complex)/np.sqrt(2)])
]:
    # For each message, compute measurement probabilities
    correct = 0
    for trial in range(n_trials):
        msg = np.random.randint(0, 4)
        state = states[msg]

        # Measurement probabilities
        p0 = np.abs(np.vdot(basis_vectors[0], state))**2
        p1 = np.abs(np.vdot(basis_vectors[1], state))**2

        # Outcome
        outcome = np.random.choice([0, 1], p=[p0, p1])

        # Best decoding: map each outcome to the most likely pair of messages
        # This gives at most 1 bit of information (distinguishing 2 of 4 messages)
        if outcome == 0:
            guess = 0  # or 1, depending on the mapping
        else:
            guess = 2  # or 3

        if guess == msg:
            correct += 1

    accuracy = correct / n_trials
    bits_recovered = np.log2(4) * accuracy + np.log2(4) * (1 - accuracy) if accuracy > 0 else 0
    print(f"{basis_name}: accuracy = {accuracy:.3f} (chance = 0.250)")
    # Even the best basis achieves ~0.50 accuracy for the matched pair,
    # confirming that at most 1 bit (not 2) can be reliably extracted.

print("\nConclusion: 1 qubit cannot reliably encode more than 1 classical bit.")

A qubit is not a device for storing exponentially many classical bits secretly.

The one exception is superdense coding, where a pre-shared entangled pair allows 2 classical bits to be transmitted using 1 qubit. But this requires the entangled pair as a resource, so the total quantum communication is still 2 qubits (one for each party’s half of the entangled state).

Be Copied (The No-Cloning Theorem)

The no-cloning theorem proves that there is no quantum operation that reliably copies an unknown quantum state. You cannot duplicate a qubit the way you duplicate a classical bit.

Here is why. Suppose a unitary operator U can clone any state:

  • U|0>|0> = |0>|0> (cloning |0> works)
  • U|1>|0> = |1>|1> (cloning |1> works)

Now try cloning |+> = (|0> + |1>)/sqrt(2). If cloning works, the result should be:

U|+>|0> = |+>|+> = (|00> + |01> + |10> + |11>) / 2

But by the linearity of quantum mechanics (U is a linear operator):

U|+>|0> = U * (|0> + |1>)/sqrt(2) * |0> = (U|0>|0> + U|1>|0>) / sqrt(2) = (|00> + |11>) / sqrt(2)

These two results are different states. The first has four nonzero amplitudes; the second has only two. This is a contradiction, so U cannot exist.

import numpy as np

# The state that "correct cloning" would produce
cloned_plus = np.array([1, 1, 1, 1], dtype=complex) / 2.0
print("Cloned |+>|+>:", np.round(cloned_plus, 4))

# The state that linearity actually produces
linear_result = np.array([1, 0, 0, 1], dtype=complex) / np.sqrt(2)
print("Linear result: ", np.round(linear_result, 4))

# These are different states
print("Are they equal?", np.allclose(cloned_plus, linear_result))  # False

# The linear result is actually a Bell state (entangled), not a product state
# This shows that attempting to "clone" a superposition creates entanglement instead
inner_product = np.abs(np.vdot(cloned_plus, linear_result))
print("Overlap:", np.round(inner_product, 4))  # Not 1.0, confirming they differ

The no-cloning theorem has deep practical consequences. It makes quantum error correction fundamentally different from classical error correction (you cannot simply copy qubits for redundancy). It also enables quantum key distribution: an eavesdropper cannot copy a qubit without disturbing it, making interception detectable.

Be Read Without Disturbance

Measuring a qubit changes it. You cannot check the state of a qubit mid-computation and then continue using the original state, as measurement collapses it. Classical bits have no such constraint.

Entanglement as a Resource Beyond Correlation

Quantum entanglement is often described as “spooky correlation,” but it is fundamentally different from any classical correlation.

Classical correlation: two coins are prepared so they always match. Both heads or both tails, each with 50% probability. If you look at one coin, you know the other. But each coin was secretly in a definite state all along; you just did not know which one.

Quantum entanglement: the Bell state (|00> + |11>)/sqrt(2). Each qubit individually looks completely random when measured (50/50 in any basis). But the joint outcomes are perfectly correlated when both are measured in the same basis. The critical difference: no assignment of definite “hidden” states to each qubit can reproduce all the observed correlations. This is what Bell’s theorem proves.

The CHSH inequality provides a quantitative test. Define four measurement settings: Alice measures in basis A or A’, Bob measures in basis B or B’. The CHSH quantity is:

S = E(A,B) + E(A,B’) + E(A’,B) - E(A’,B’)

For any classical (local hidden variable) model: S <= 2.

For entangled qubits with optimal measurement angles: S = 2*sqrt(2) ≈ 2.828.

import numpy as np

# Bell state: (|00> + |11>) / sqrt(2)
bell = np.array([1, 0, 0, 1], dtype=complex) / np.sqrt(2)

def measurement_operator(theta_a, theta_b):
    """
    Compute the tensor product of two single-qubit observables,
    each measuring spin along an axis at angle theta from Z in the XZ plane.
    Observable = cos(theta)*Z + sin(theta)*X
    """
    Z = np.array([[1, 0], [0, -1]], dtype=complex)
    X_gate = np.array([[0, 1], [1, 0]], dtype=complex)

    A = np.cos(theta_a) * Z + np.sin(theta_a) * X_gate
    B = np.cos(theta_b) * Z + np.sin(theta_b) * X_gate

    return np.kron(A, B)

def expectation(state, operator):
    """Compute <state|operator|state>."""
    return np.real(state.conj() @ operator @ state)

# Optimal CHSH angles
# Alice: 0, pi/4; Bob: pi/8, -pi/8
a1, a2 = 0, np.pi/4
b1, b2 = np.pi/8, -np.pi/8

E_AB  = expectation(bell, measurement_operator(a1, b1))
E_AB2 = expectation(bell, measurement_operator(a1, b2))
E_A2B = expectation(bell, measurement_operator(a2, b1))
E_A2B2= expectation(bell, measurement_operator(a2, b2))

S = E_AB + E_AB2 + E_A2B - E_A2B2

print(f"E(A,B)   = {E_AB:.4f}")
print(f"E(A,B')  = {E_AB2:.4f}")
print(f"E(A',B)  = {E_A2B:.4f}")
print(f"E(A',B') = {E_A2B2:.4f}")
print(f"\nCHSH value S = {S:.4f}")
print(f"Classical bound: 2.0000")
print(f"Quantum maximum: {2*np.sqrt(2):.4f}")
print(f"Violation: {S > 2}")

The experimental violation of Bell inequalities has been confirmed in laboratories worldwide, most recently in loophole-free experiments. Entanglement is not just a theoretical curiosity; it is a verified physical phenomenon that enables quantum teleportation, superdense coding, and quantum key distribution.

Building Physical Qubits

A qubit can be physically realized in many ways. Each technology encodes the two-level system differently, uses different control mechanisms, and faces different engineering challenges.

Superconducting Qubits (IBM, Google)

Physical encoding: The qubit state is encoded in the quantized energy levels of a superconducting circuit containing a Josephson junction. The two lowest energy levels of this anharmonic oscillator serve as |0> and |1>. The circuit must be cooled to approximately 15 millikelvin (colder than outer space) to eliminate thermal excitations.

Gate operations: Single-qubit gates are implemented with carefully shaped microwave pulses at the qubit’s resonant frequency, typically around 5 GHz. Two-qubit gates use capacitive coupling or tunable couplers between neighboring qubits.

Coherence times: T1 (energy relaxation) and T2 (dephasing) are both approximately 100 microseconds in state-of-the-art devices. Gate times are approximately 20-50 nanoseconds for single-qubit gates and 100-300 nanoseconds for two-qubit gates, allowing roughly 1000 gate operations before decoherence dominates.

Who uses it: IBM (Eagle, Heron processors), Google (Sycamore, Willow), Rigetti, and many academic labs.

Trapped Ions (IonQ, Quantinuum)

Physical encoding: Individual ions (typically Ca-40+ or Yb-171+) are held in place by oscillating electric fields in a linear Paul trap. The qubit is encoded in two electronic energy levels of the ion, such as two hyperfine ground states of Yb-171+.

Gate operations: Single-qubit gates use focused laser beams or microwave fields to drive transitions between the two qubit levels. Two-qubit gates use the shared motional modes of the ion chain: a laser pulse on one ion excites the collective vibration, and a second pulse on another ion picks up this vibration, creating an entangling interaction.

Coherence times: T1 can exceed minutes (the qubit states are extremely stable). T2 ranges from seconds to minutes depending on the encoding. Gate times are slower than superconducting qubits: microseconds for single-qubit gates and tens to hundreds of microseconds for two-qubit gates. The advantage is very high gate fidelity (above 99.9% for single-qubit, above 99.5% for two-qubit).

Who uses it: IonQ, Quantinuum (formerly Honeywell Quantum Solutions), and university research groups worldwide.

Photonic Qubits (Xanadu, PsiQuantum)

Physical encoding: The qubit is encoded in a property of a single photon, most commonly its polarization (horizontal |H> = |0>, vertical |V> = |1>) or its path through an interferometer.

Gate operations: Single-qubit gates are implemented with waveplates (half-wave plates for rotations, quarter-wave plates for phase shifts) or beam splitters. Two-qubit gates are the primary challenge for photonic systems, as photons do not naturally interact with each other. Measurement-based schemes and nonlinear optical effects are used to create effective two-photon gates.

Coherence times: Photons do not decohere in the same way as matter qubits (they do not couple strongly to thermal environments). The main source of error is photon loss in optical components. Photonic qubits are naturally suited for communication (photons travel at the speed of light) and are the basis of quantum networking.

Who uses it: Xanadu (using squeezed light and photonic chips), PsiQuantum, and many quantum communication research labs.

Neutral Atoms (QuEra, Pasqal)

Physical encoding: Individual neutral atoms (typically Rubidium-87) are trapped in arrays of tightly focused laser beams called optical tweezers. The qubit is encoded in two hyperfine ground states of the atom.

Gate operations: Single-qubit gates use microwave or Raman laser pulses. Two-qubit gates exploit Rydberg excitation: when an atom is excited to a very high energy level (a Rydberg state), it creates a strong interaction with nearby atoms that prevents them from being simultaneously excited. This “Rydberg blockade” mechanism enables controlled entangling operations.

Coherence times: T2 is typically hundreds of milliseconds to seconds. A distinctive advantage of neutral atom platforms is the ability to rearrange atoms dynamically, creating flexible qubit connectivity. Arrays of hundreds of atoms have been demonstrated.

Who uses it: QuEra, Pasqal, and several academic groups (Harvard, MIT, Caltech).

Comparison Table

TechnologyQubit basisGate mechanismT2Gate fidelity (2Q)Scale (2025)
SuperconductingCircuit energy levelsMicrowave pulses~100 us~99.5%~1000 qubits
Trapped ionElectronic levelsLaser/microwave~seconds~99.5%~50 qubits
PhotonicPolarization/pathBeam splittersN/A (loss-limited)~95%~200 modes
Neutral atomHyperfine levelsLaser/Rydberg~seconds~99%~200 qubits

All of these map a two-level quantum system to the |0> and |1> states of a qubit. The physics differs; the mathematics is the same.

Quantum Advantage: Where and Why

Quantum computers are not universally faster than classical computers. They provide speedups for specific, structured problems where quantum interference and entanglement can be exploited algorithmically. Here is what the landscape looks like.

Problems with Known Quantum Speedup

Integer factoring (Shor’s algorithm): Given a large composite integer N, find its prime factors. The best known classical algorithm (general number field sieve) runs in sub-exponential time. Shor’s algorithm runs in polynomial time, an exponential speedup. This is the result that motivates post-quantum cryptography, since RSA encryption relies on the difficulty of factoring.

Unstructured search (Grover’s algorithm): Given a black-box function on N items, find the unique input that produces a marked output. Classically, this requires O(N) evaluations. Grover’s algorithm requires O(sqrt(N)), a quadratic speedup. This is provably optimal for quantum computers, so the speedup is real but modest.

Quantum simulation (Feynman’s original motivation): Simulating the behavior of quantum systems (molecules, materials, chemical reactions) requires exponential resources on classical computers because the state space grows as 2^n for n particles. A quantum computer can simulate these systems in polynomial time. This is expected to be the first area of practical quantum advantage.

Linear systems (HHL algorithm): Solving a system of linear equations Ax = b. Under certain conditions (sparse matrix, low condition number, and the answer needed is a property of x rather than x itself), the HHL algorithm provides an exponential speedup. The conditions are restrictive, and practical advantage depends heavily on the problem structure.

Problems with No Known Quantum Speedup

  • Sorting: Quantum computers can search unsorted data faster (Grover), but sorting n items still requires O(n log n) comparisons. No quantum speedup is known.
  • Graph traversal: Breadth-first search, depth-first search, Dijkstra’s algorithm. No quantum speedup is known for general graph problems.
  • NP-complete problems (e.g., SAT, traveling salesman): Grover’s algorithm provides a quadratic speedup for brute-force search, but no exponential quantum speedup is known. The complexity class NP is not believed to be contained in BQP (the class of problems efficiently solvable by quantum computers).

Not Expected to Benefit

Most everyday computational tasks do not benefit from quantum computers: web browsing, word processing, video encoding, machine learning inference on trained models, database queries. These tasks are either already fast on classical hardware or do not have the mathematical structure that quantum algorithms exploit.

Common Misconceptions

“A qubit is 0 and 1 at the same time.” This is a loose way of saying a qubit is in superposition. More precisely, a qubit holds complex amplitudes for 0 and 1, and measurement probabilistically selects one outcome. The state before measurement is not “both values at once” in any operational sense; it is a single quantum state that happens to have nonzero amplitude for both basis states.

“Quantum computers try all possibilities simultaneously.” Quantum computers evolve superpositions over many inputs, but they cannot read off all results at once. The art of quantum algorithm design is using interference to make the right answer likely to be the one that is measured. A quantum computer that simply creates a uniform superposition and measures it is no better than a random guess.

“Qubits store exponentially more information than bits.” The number of amplitudes in an n-qubit state grows as 2^n, but you can only extract n classical bits from a measurement. The accessible classical information does not grow exponentially. The Holevo bound makes this precise.

“Any problem is faster on a quantum computer.” Quantum speedup exists for specific problem types. Many classical algorithms have no known quantum speedup. Quantum computers are specialized tools, not universal accelerators. For most problems people solve daily, a classical computer is faster, cheaper, and more reliable.

“Quantum computers are always faster.” Even for problems where quantum algorithms have better asymptotic scaling, current quantum hardware has enormous overhead from error correction, gate times, and qubit control. A fault-tolerant quantum computer running Shor’s algorithm on a 2048-bit RSA key would require thousands of logical qubits and millions of physical qubits. For small problem sizes, classical computers win handily. Quantum advantage emerges only at sufficient scale, and for most algorithms, that scale has not yet been reached.

“Larger superposition means more computing power.” What matters is the algorithm’s ability to use interference constructively, not merely the dimension of the Hilbert space. A random unitary circuit produces a superposition over all 2^n basis states, but measuring it yields a random string with no useful structure. The superposition is useless without a carefully designed interference pattern that concentrates amplitude on the correct answer. Grover’s algorithm, for instance, works not because it creates a big superposition, but because it iteratively rotates the state vector toward the target.

Next Steps

Was this tutorial helpful?