Quantum Gates and Circuits
The unitary building blocks of quantum algorithms — from Pauli flips and Hadamard superpositions to CNOT entanglement and fault-tolerant gate sets, worked through with matrices, circuit diagrams, and a live simulator you can run in your browser.
1. From classical logic gates to quantum gates
Classical computers process bits through logic gates: AND, OR, NOT, NAND, XOR. These gates are the physical instantiation of Boolean algebra, and they work perfectly — but most of them are irreversible. Feed a 1 and a 0 into an AND gate and you get 0. Feed a 0 and a 0 and you also get 0. Two different inputs produce the same output, so you cannot run the gate backwards and recover what went in.
This irreversibility has a physical consequence known as Landauer's principle: erasing one bit of information releases at least $k_B T \ln 2$ of heat into the environment (roughly $3 \times 10^{-21}$ J at room temperature). Modern CPUs erase trillions of bits per second; the heat they generate is not just an engineering inconvenience — it is thermodynamically mandated by the irreversible logic they compute.
Every quantum gate is a unitary transformation $U$ on the state space $\mathbb{C}^{2^n}$. Unitary means $U U^\dagger = U^\dagger U = I$ — the gate has an inverse $U^\dagger$ (the conjugate transpose), so it is reversible by construction. Quantum computation is always reversible until measurement.
A state $|\psi\rangle$ transforms as $|\psi'\rangle = U|\psi\rangle$. Because $U$ is unitary, it preserves the inner product: $\langle\psi'|\psi'\rangle = \langle\psi|U^\dagger U|\psi\rangle = \langle\psi|\psi\rangle = 1$. The norm is always preserved — probabilities always sum to 1.
The bridge: Toffoli gate
Before quantum, there was classical reversible computing. The Toffoli gate (CCNOT) is a 3-bit reversible gate: it flips the target bit if and only if both control bits are 1. It can simulate AND (set target to 0 before the gate; it comes out as $c_1 \wedge c_2$) and NOT (set both controls to 1). Since AND and NOT suffice for all Boolean logic, Toffoli is classically universal — and it is reversible because the output uniquely determines the input.
Adding the Hadamard gate $H$ (which creates superpositions) to the Toffoli set makes it quantum-universal. Every quantum algorithm is built from combinations of gates like these.
2. Single-qubit gates
A single qubit lives in $\mathbb{C}^2$. A single-qubit gate is a $2 \times 2$ unitary matrix. Below is the complete catalog of the gates you will encounter in essentially every quantum algorithm.
Pauli gates — $\pi$ rotations on the Bloch sphere
The three Pauli matrices $\{X, Y, Z\}$ are the quantum analogs of classical bit-flips and phase-flips. Each squares to the identity ($X^2 = Y^2 = Z^2 = I$) and is its own inverse ($X = X^\dagger$, etc.).
| Gate | Matrix | Action on basis | Bloch rotation |
|---|---|---|---|
| X (NOT) | $\begin{pmatrix}0&1\\1&0\end{pmatrix}$ | $|0\rangle \leftrightarrow |1\rangle$ | 180° around X-axis |
| Y | $\begin{pmatrix}0&-i\\i&0\end{pmatrix}$ | $|0\rangle \to i|1\rangle,\; |1\rangle \to -i|0\rangle$ | 180° around Y-axis |
| Z | $\begin{pmatrix}1&0\\0&-1\end{pmatrix}$ | $|0\rangle \to |0\rangle,\; |1\rangle \to -|1\rangle$ | 180° around Z-axis |
| H (Hadamard) | $\frac{1}{\sqrt{2}}\begin{pmatrix}1&1\\1&-1\end{pmatrix}$ | $|0\rangle \to |{+}\rangle,\; |1\rangle \to |{-}\rangle$ | 180° around $(X{+}Z)/\sqrt{2}$ |
| S ($\sqrt{Z}$) | $\begin{pmatrix}1&0\\0&i\end{pmatrix}$ | $|0\rangle \to |0\rangle,\; |1\rangle \to i|1\rangle$ | 90° around Z-axis |
| T ($\sqrt{S}$) | $\begin{pmatrix}1&0\\0&e^{i\pi/4}\end{pmatrix}$ | $|0\rangle \to |0\rangle,\; |1\rangle \to e^{i\pi/4}|1\rangle$ | 45° around Z-axis |
Hadamard in detail
The Hadamard gate is the workhorse of quantum computing. It creates equal superpositions from computational basis states:
Applying $H$ twice returns to the original state: $H^2 = I$. You can verify this by matrix multiplication: $\frac{1}{\sqrt{2}}\begin{pmatrix}1&1\\1&-1\end{pmatrix} \cdot \frac{1}{\sqrt{2}}\begin{pmatrix}1&1\\1&-1\end{pmatrix} = \begin{pmatrix}1&0\\0&1\end{pmatrix}$.
On the Bloch sphere, $H$ is a 180° rotation around the axis $(X+Z)/\sqrt{2}$ — the axis pointing diagonally between the $+X$ and $+Z$ poles. This swaps the $X$ and $Z$ axes, which is why $HXH = Z$ and $HZH = X$ (see §7).
Phase gates: S, T, and the general $P(\lambda)$
Phase gates leave $|0\rangle$ alone and multiply $|1\rangle$ by a phase factor. They don't change measurement probabilities in the computational basis, but they affect interference patterns and are essential for quantum algorithms.
Setting $\lambda = \pi/2$ gives $S$; $\lambda = \pi/4$ gives $T$; $\lambda = \pi$ gives $Z$. The $T$ gate is the most expensive gate in fault-tolerant quantum computing because it requires magic state distillation — a resource-intensive error-correction protocol. The number of $T$ gates in a circuit (the T-count) is the dominant cost metric for fault-tolerant algorithms.
In fault-tolerant quantum computing (using the surface code or similar), the Clifford gates $\{H, S, \text{CNOT}\}$ are "cheap" — they can be implemented transversally. The $T$ gate is "expensive" — it requires a distilled magic state $|m\rangle \propto |0\rangle + e^{i\pi/4}|1\rangle$ and gate teleportation. A fault-tolerant $T$ gate may cost 100× more than a Clifford gate. Circuit optimization largely means reducing T-count.
3. Rotation gates and parameterized circuits
Every $2\times 2$ unitary (up to global phase) can be written as a rotation on the Bloch sphere. The primitive rotation gates around each axis are:
The exponential form $e^{-i\theta P/2}$ for a Pauli $P$ is computed using the identity $e^{i\alpha P} = \cos\alpha \cdot I + i\sin\alpha \cdot P$, which holds because $P^2 = I$ for all Paulis.
Euler decomposition
Any single-qubit gate $U$ (up to global phase) can be decomposed into three rotations:
This is the quantum analog of Euler angles in 3D. Given any target gate, you can find $\alpha, \beta, \gamma, \delta$ explicitly. This decomposition is used by circuit compilers to express arbitrary single-qubit gates in terms of hardware-native rotations.
Parameterized circuits and variational algorithms
Variational quantum algorithms (VQE, QAOA) use circuits where some rotation angles $\theta$ are free parameters, optimized by a classical outer loop. The continuous parameterization means the gradient of the circuit output with respect to $\theta$ can be computed via the parameter-shift rule:
This lets you compute exact gradients on real quantum hardware by running the circuit at shifted parameter values — no finite differences required.
4. Two-qubit gates
Two-qubit gates are $4 \times 4$ unitary matrices acting on the composite Hilbert space $\mathbb{C}^2 \otimes \mathbb{C}^2 = \mathbb{C}^4$. The computational basis for two qubits is $\{|00\rangle, |01\rangle, |10\rangle, |11\rangle\}$ (first qubit is the most significant bit by convention).
CNOT (Controlled-NOT)
The CNOT gate is the fundamental entangling gate. The control qubit's state determines whether the target qubit is flipped:
Reading the matrix column by column: $|00\rangle\to|00\rangle$, $|01\rangle\to|01\rangle$, $|10\rangle\to|11\rangle$, $|11\rangle\to|10\rangle$. The bottom-right $2\times 2$ block is the Pauli $X$ gate, applied to the target only when the control is $|1\rangle$.
Bell state creation
Applying $H$ to the first qubit, then CNOT, starting from $|00\rangle$:
The result $|\Phi^+\rangle$ is maximally entangled — measuring qubit 0 immediately determines qubit 1's state, regardless of distance. This is the fundamental resource for quantum teleportation and superdense coding.
CZ (Controlled-Z)
The CZ gate applies a $-1$ phase to $|11\rangle$ and leaves all other basis states unchanged. Unlike CNOT, it is symmetric: control and target are interchangeable (the matrix is symmetric). CZ is the native two-qubit gate on many superconducting platforms (e.g., Google Sycamore). Any CNOT can be implemented as $H \cdot \text{CZ} \cdot H$ on the target qubit.
SWAP and iSWAP
The SWAP gate exchanges the states of two qubits: $\text{SWAP}|ab\rangle = |ba\rangle$. Its matrix is:
SWAP can be decomposed as three CNOTs: $\text{SWAP} = \text{CNOT}_{01} \cdot \text{CNOT}_{10} \cdot \text{CNOT}_{01}$. This decomposition is important for routing circuits onto hardware with limited connectivity.
The iSWAP gate is native on some superconducting and trapped-ion platforms: $\text{iSWAP}|01\rangle = i|10\rangle$ and $\text{iSWAP}|10\rangle = i|01\rangle$. The extra $i$ phase matters for interference patterns in larger circuits.
Controlled-$U$ gates
For any single-qubit unitary $U$, the controlled-$U$ gate applies $U$ to the target if the control is $|1\rangle$:
Setting $U = X$ gives CNOT; $U = Z$ gives CZ; $U = H$ gives CH. Multi-controlled versions generalize further: the Toffoli gate is $\text{CCX}$ (doubly-controlled $X$).
| Gate | Qubits | Key property | Native hardware |
|---|---|---|---|
| CNOT | 2 | Fundamental entangler; universal with single-qubit gates | Superconducting, trapped ion |
| CZ | 2 | Symmetric; $\text{CNOT} = (I \otimes H)\,\text{CZ}\,(I \otimes H)$ | Google Sycamore, Rigetti |
| iSWAP | 2 | Native exchange interaction; adds $i$ phase | Superconducting (flux tunable) |
| SWAP | 2 | Routing qubits; = 3 CNOTs | Compiled from CNOT |
| Toffoli (CCX) | 3 | Universal reversible classical; = 6 CNOTs | Compiled |
| Fredkin (CSWAP) | 3 | Controlled swap; useful in arithmetic circuits | Compiled |
5. Universal gate sets
A gate set is universal if any $n$-qubit unitary can be approximated to any precision $\epsilon$ using a finite sequence of gates from the set. The key word is approximated — you generally cannot implement an arbitrary unitary exactly with a finite gate set, but you can get arbitrarily close.
Solovay-Kitaev theorem
The Solovay-Kitaev theorem (1995, independently by Kitaev and Solovay) provides an explicit efficiency guarantee: any universal gate set can approximate any single-qubit gate to precision $\epsilon$ using only $O(\log^c(1/\epsilon))$ gates, where $c \approx 3.97$ (and can be reduced with better algorithms). The theorem is constructive — it gives an algorithm to find the approximating sequence.
To achieve $\epsilon = 10^{-6}$ precision, you need roughly $\log^4(10^6) \approx 2000$ gates from $\{H, T, \text{CNOT}\}$. In practice, compilers find much shorter sequences for specific targets — the theorem is a worst-case bound.
Standard universal gate sets
Clifford group and the Gottesman-Knill theorem
The Clifford gates are those that map Pauli operators to Pauli operators under conjugation: $U P U^\dagger$ is a Pauli (up to phase) for all Paulis $P$. The Clifford group is generated by $\{H, S, \text{CNOT}\}$.
The Gottesman-Knill theorem states: any quantum circuit consisting entirely of Clifford gates, computational basis preparation, and computational basis measurement can be simulated efficiently on a classical computer (polynomial time and space in the number of qubits).
This means Clifford circuits have no quantum advantage — they are "classically easy." The $T$ gate breaks this simulability. Adding even a single $T$ gate to a Clifford circuit can make it classically hard to simulate. The T-count is therefore the metric of quantum hardness in fault-tolerant settings.
6. Quantum circuit notation
Quantum circuits are drawn with horizontal wires (one per qubit, time flowing left to right), boxes or symbols for gates, and measurement symbols. Classical bits use double wires.
Notation conventions
- Wire: horizontal line, represents a qubit (or classical bit with double line)
- Gate: box with gate name, or standard symbol (circle for phase, X-in-circle for CNOT target)
- Control: filled circle (•) connected by vertical line to the gate it controls
- CNOT target: ⊕ (XOR symbol, also called "positive controlled target")
- CZ target: filled circle on both wires — both ends look like controls
- Measurement: meter-box symbol (M), outputs a classical bit on a double wire
- SWAP: two crossed X symbols (×) on the two wires being swapped
Circuit metrics
- Width: number of qubits (qubit count)
- Depth: length of the critical path (maximum number of sequential gate layers)
- Gate count: total number of gate operations
- T-count: number of $T$ gates (fault-tolerant cost)
- T-depth: depth of the T-gate critical path
Example circuits as ASCII diagrams
Bell state preparation — $|\Phi^+\rangle = \frac{1}{\sqrt{2}}(|00\rangle + |11\rangle)$:
q0: ─── H ───•───
│
q1: ─────── ⊕ ───
3-qubit GHZ state — $|GHZ\rangle = \frac{1}{\sqrt{2}}(|000\rangle + |111\rangle)$:
q0: ─── H ───•───•───
│ │
q1: ─────── ⊕ ──┼───
│
q2: ─────────── ⊕ ──
Quantum teleportation circuit (teleport $|\psi\rangle$ from Alice to Bob):
┌───────┐
q0: │ |ψ⟩ ├─── •─── H ─── M ──╥────────────
└───────┘ │ ║
q1: ─ H ─── ⊕ ──•─── M ────╥──╫────────────
║ ║
q2: ─────────────────── X ──╨──Z ─── |ψ⟩
│ │
(classical feedforward)
3-qubit QFT (Quantum Fourier Transform, unrolled):
q0: ─── H ─── R2 ─── R3 ───────────────────── ×─────
│ │ │
q1: ──────────•── H ─── R2 ────────────────── ×│────
│ ││
q2: ────────────────────•─── H ─────────────── ──×──
(SWAP)
R2 = P(π/2), R3 = P(π/4)
7. Circuit identities and simplification
Circuit identities let you rewrite circuits in equivalent forms — simplifying gate counts, reducing T-count, or adapting to hardware connectivity constraints. The following are among the most useful.
Single-qubit identities
The $H$-conjugation identities $HXH = Z$ and $HZH = X$ are the quantum analog of the fact that $H$ swaps the $X$ and $Z$ axes on the Bloch sphere. They are constantly used to change the basis in which an operation acts: if you want a $Z$-basis rotation but only have $X$-basis tools, sandwich with $H$.
Two-qubit identities
The second identity says you can swap the control and target of a CNOT by conjugating both qubits with $H$. This is critical for hardware with directed connectivity — if only one CNOT direction is native, you can implement the other with 2 extra Hadamards.
$Z$ commutes with CNOT on the control qubit: $(Z \otimes I)\,\text{CNOT} = \text{CNOT}\,(Z \otimes I)$.
$X$ commutes with CNOT on the target qubit: $(I \otimes X)\,\text{CNOT} = \text{CNOT}\,(I \otimes X)$.
These rules allow $Z$ errors on the control and $X$ errors on the target to "pass through" a CNOT, which is why they are called stabilizer propagation rules in error correction.
Useful simplifications
- $U \cdot U^\dagger = I$ — any gate followed by its inverse cancels
- $H \cdot H = I$, $X \cdot X = I$, $Z \cdot Z = I$ — all Pauli gates and $H$ are their own inverses
- $S \cdot S = Z$, $S^\dagger \cdot S = I$ — watch the adjoint vs. the gate
- Two adjacent CNOTs with same control and target cancel: $\text{CNOT}^2 = I$
- $ZX = iY$, $XZ = -iY$ — products of Paulis are Paulis (up to phase)
8. Measurement and classical feedback
Measuring a qubit in the computational basis collapses the state $|\psi\rangle = \alpha|0\rangle + \beta|1\rangle$ to $|0\rangle$ with probability $|\alpha|^2$ or $|1\rangle$ with probability $|\beta|^2$. The post-measurement state is the collapsed eigenstate — the superposition is destroyed.
Deferred measurement principle
The deferred measurement principle states: any mid-circuit measurement followed by a classically-controlled gate can be replaced by a fully coherent (quantum-controlled) circuit with the measurement deferred to the end, without changing the output distribution. Formally, replacing a classical if-statement with a quantum controlled gate and moving the measurement to the end gives an equivalent circuit for all observables.
This means that, in principle, you never need to measure mid-circuit. But in practice, mid-circuit measurement is essential for:
- Quantum error correction: syndrome measurements detect errors without destroying logical information
- Quantum teleportation: Bell measurement collapses the state and the outcome determines classical corrections
- Magic state preparation: post-selection on measurement outcomes
- Adaptive circuits: circuits where later gates depend on earlier measurement results (dynamic circuits)
Dynamic circuits and classical feedback
A dynamic circuit contains mid-circuit measurements and classically conditioned gates: measure qubits $q_0, q_1$ to get classical bits $c_0, c_1$, then apply $X^{c_0}$ and $Z^{c_1}$ to qubit $q_2$. This is exactly the classical correction step in quantum teleportation.
Dynamic circuits are strictly more powerful than static (measurement-free) circuits for some tasks — quantum teleportation requires classical communication and cannot be done with a fixed circuit. They are also used in lattice surgery for fault-tolerant quantum computation.
1. Alice and Bob share a Bell pair $|\Phi^+\rangle_{12}$.
2. Alice has $|\psi\rangle_0 = \alpha|0\rangle + \beta|1\rangle$ to teleport.
3. Alice applies CNOT$(q_0 \to q_1)$ then $H(q_0)$.
4. Alice measures $q_0, q_1$ to get bits $m_0, m_1$.
5. Alice sends $m_0, m_1$ to Bob via classical channel.
6. Bob applies $X^{m_1} Z^{m_0}$ to $q_2$. Now $q_2 = |\psi\rangle$.
No information is transmitted faster than light — step 5 requires classical communication.
9. Interactive: quantum circuit builder
Build a 2-qubit circuit by selecting a gate from the palette and clicking a cell on the grid. Press Simulate to run the circuit on $|00\rangle$ and see the output state vector. The pre-loaded circuit creates a Bell state.
| State | Amplitude (re + im·i) | Probability | Bar |
|---|---|---|---|
| Press Simulate to see output. | |||
10. Source code
Source — Quantum Gates and Circuit Simulation
// State vector: array of 2^n complex amplitudes
// |psi> = sum_i a_i |i> where |a_i|^2 = probability of |i>
StateVector:
n_qubits: int
amplitudes: complex[2^n] // indexed by bitstring
function init_zero_state(n):
sv = StateVector(n)
sv.amplitudes[0] = 1 + 0i // |00...0>
sv.amplitudes[i] = 0 for i > 0
return sv
function apply_single_qubit_gate(sv, gate_matrix[2][2], qubit):
// Iterate over all 2^n basis states
for each pair (|...0...>, |...1...>) differing only at 'qubit':
i0 = bitstring with 0 at qubit position
i1 = bitstring with 1 at qubit position
a0 = sv.amplitudes[i0]
a1 = sv.amplitudes[i1]
sv.amplitudes[i0] = gate[0][0]*a0 + gate[0][1]*a1
sv.amplitudes[i1] = gate[1][0]*a0 + gate[1][1]*a1
function apply_cnot(sv, control, target):
for each basis state i where bit at 'control' is 1:
j = i XOR (1 << target) // flip target bit
swap sv.amplitudes[i], sv.amplitudes[j]
function measure(sv, qubit):
prob_0 = sum |sv.amplitudes[i]|^2 for all i with bit qubit = 0
result = 0 with probability prob_0, else 1
// Collapse and renormalize
norm = sqrt(prob_result)
for each i where bit at qubit != result:
sv.amplitudes[i] = 0
for each i where bit at qubit == result:
sv.amplitudes[i] /= norm
return result
// Bell state example:
sv = init_zero_state(2)
apply_single_qubit_gate(sv, H, qubit=0)
apply_cnot(sv, control=0, target=1)
// sv.amplitudes = [0.707, 0, 0, 0.707] -> |00> and |11>"""Quantum gate simulation with NumPy and Qiskit."""
import numpy as np
from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector
# ── Gate matrices ───────────────────────────────────────────────
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)
H = np.array([[1, 1], [1, -1]], dtype=complex) / np.sqrt(2)
S = np.array([[1, 0], [0, 1j]], dtype=complex)
T = np.array([[1, 0], [0, np.exp(1j * np.pi / 4)]], dtype=complex)
def Rx(theta):
c, s = np.cos(theta/2), np.sin(theta/2)
return np.array([[c, -1j*s], [-1j*s, c]], dtype=complex)
def Ry(theta):
c, s = np.cos(theta/2), np.sin(theta/2)
return np.array([[c, -s], [s, c]], dtype=complex)
def Rz(theta):
return np.array([[np.exp(-1j*theta/2), 0],
[0, np.exp(1j*theta/2)]], dtype=complex)
CNOT = np.array([[1,0,0,0],[0,1,0,0],[0,0,0,1],[0,0,1,0]], dtype=complex)
CZ = np.array([[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,-1]], dtype=complex)
# ── Manual state vector simulation ──────────────────────────────
def apply_gate(state, U, qubit, n_qubits):
"""Apply 2x2 unitary U to 'qubit' in an n-qubit state vector."""
state = state.copy()
for idx in range(1 << n_qubits):
if (idx >> qubit) & 1 == 0: # bit at 'qubit' is 0
partner = idx | (1 << qubit)
a0, a1 = state[idx], state[partner]
state[idx] = U[0,0]*a0 + U[0,1]*a1
state[partner] = U[1,0]*a0 + U[1,1]*a1
return state
def apply_cnot(state, control, target, n_qubits):
"""Apply CNOT with given control and target qubit indices."""
state = state.copy()
for idx in range(1 << n_qubits):
if (idx >> control) & 1 == 1: # control is 1
partner = idx ^ (1 << target)
if idx < partner:
state[idx], state[partner] = state[partner], state[idx]
return state
# ── Bell state preparation ───────────────────────────────────────
def bell_state():
n = 2
psi = np.zeros(1 << n, dtype=complex)
psi[0] = 1.0 # |00>
psi = apply_gate(psi, H, qubit=0, n_qubits=n)
psi = apply_cnot(psi, control=0, target=1, n_qubits=n)
return psi # [0.707, 0, 0, 0.707]
psi = bell_state()
print("Bell state amplitudes:", psi.round(3))
# ── Using Qiskit ─────────────────────────────────────────────────
qc = QuantumCircuit(2)
qc.h(0)
qc.cx(0, 1)
sv = Statevector(qc)
print("Qiskit Statevector:", sv.data.round(3))
# ── GHZ state ────────────────────────────────────────────────────
qc3 = QuantumCircuit(3)
qc3.h(0)
qc3.cx(0, 1)
qc3.cx(0, 2)
sv3 = Statevector(qc3)
print("GHZ state:", sv3.data.round(3))
# [0.707, 0, 0, 0, 0, 0, 0, 0.707] -> |000> + |111>/**
* 2-qubit quantum statevector simulator.
* State: [a00, a01, a10, a11] where index = q0*2 + q1 (q0 = MSB).
*/
// Complex number helpers
const C = {
add: ([r1,i1],[r2,i2]) => [r1+r2, i1+i2],
mul: ([r1,i1],[r2,i2]) => [r1*r2 - i1*i2, r1*i2 + i1*r2],
scale:([r,i], s) => [r*s, i*s],
norm: ([r,i]) => r*r + i*i,
fmt: ([r,i], d=4) => `${r.toFixed(d)}${i>=0?'+':'-'}${Math.abs(i).toFixed(d)}i`
};
// Gate matrices as flat arrays of [re,im] pairs: [a00,a01,a10,a11]
const GATES = {
H: [[1/Math.SQRT2,0],[1/Math.SQRT2,0],[1/Math.SQRT2,0],[-1/Math.SQRT2,0]],
X: [[0,0],[1,0],[1,0],[0,0]],
Y: [[0,0],[0,-1],[0,1],[0,0]],
Z: [[1,0],[0,0],[0,0],[-1,0]],
S: [[1,0],[0,0],[0,0],[0,1]],
T: [[1,0],[0,0],[0,0],[Math.cos(Math.PI/4), Math.sin(Math.PI/4)]],
};
// Initial state |00>
function zeroState() {
return [[1,0],[0,0],[0,0],[0,0]];
}
// Apply 2x2 gate to qubit 0 (MSB) or qubit 1 (LSB) of 2-qubit state
function applySingle(state, gate, qubit) {
const out = state.map(a => [a[0], a[1]]);
// qubit 0: pairs (0,2) and (1,3) — differ in bit 1 (MSB)
// qubit 1: pairs (0,1) and (2,3) — differ in bit 0 (LSB)
const pairs = qubit === 0 ? [[0,2],[1,3]] : [[0,1],[2,3]];
for (const [i0, i1] of pairs) {
const a0 = state[i0], a1 = state[i1];
out[i0] = C.add(C.mul(gate[0], a0), C.mul(gate[1], a1));
out[i1] = C.add(C.mul(gate[2], a0), C.mul(gate[3], a1));
}
return out;
}
// CNOT: control=qubit 0 (MSB), target=qubit 1 (LSB)
function applyCNOT(state) {
const out = state.map(a => [a[0], a[1]]);
// When control (q0) = 1: indices 2 (|10>) and 3 (|11>) get target flipped
out[2] = state[3];
out[3] = state[2];
return out;
}
// Run a circuit described as [{gate, qubit}] on |00>
function simulate(ops) {
let state = zeroState();
for (const op of ops) {
if (op.gate === 'CNOT') {
state = applyCNOT(state);
} else {
state = applySingle(state, GATES[op.gate], op.qubit);
}
}
return state;
}
// Bell state: H on q0, CNOT
const bellOps = [{ gate:'H', qubit:0 }, { gate:'CNOT' }];
const bell = simulate(bellOps);
console.log('Bell state:');
['|00>','|01>','|10>','|11>'].forEach((label, i) => {
const prob = (C.norm(bell[i]) * 100).toFixed(1);
console.log(` ${label}: ${C.fmt(bell[i])} (${prob}%)`);
});
// |00>: 0.7071+0.0000i (50.0%)
// |11>: 0.7071+0.0000i (50.0%)/*
* 2-qubit quantum statevector simulation in C.
* State: complex[4] indexed by q0*2 + q1.
*/
#include <stdio.h>
#include <math.h>
#include <complex.h> /* C99 complex */
typedef double complex cx;
/* Gate as 4-element array [a00, a01, a10, a11] */
static const cx H_mat[4] = {
1.0/M_SQRT2, 1.0/M_SQRT2,
1.0/M_SQRT2,-1.0/M_SQRT2
};
static const cx X_mat[4] = {0, 1, 1, 0};
static const cx Z_mat[4] = {1, 0, 0, -1};
void apply_single(cx state[4], const cx gate[4], int qubit)
{
/* qubit 0 (MSB): pairs (0,2) and (1,3)
qubit 1 (LSB): pairs (0,1) and (2,3) */
int pairs[2][2];
if (qubit == 0) { pairs[0][0]=0; pairs[0][1]=2; pairs[1][0]=1; pairs[1][1]=3; }
else { pairs[0][0]=0; pairs[0][1]=1; pairs[1][0]=2; pairs[1][1]=3; }
for (int p = 0; p < 2; p++) {
int i0 = pairs[p][0], i1 = pairs[p][1];
cx a0 = state[i0], a1 = state[i1];
state[i0] = gate[0]*a0 + gate[1]*a1;
state[i1] = gate[2]*a0 + gate[3]*a1;
}
}
void apply_cnot(cx state[4])
{
/* Swap |10> and |11> (indices 2 and 3) */
cx tmp = state[2];
state[2] = state[3];
state[3] = tmp;
}
void print_state(const cx state[4])
{
const char *labels[] = {"|00>", "|01>", "|10>", "|11>"};
for (int i = 0; i < 4; i++) {
double re = creal(state[i]), im = cimag(state[i]);
double prob = re*re + im*im;
printf(" %s %+.4f %+.4fi prob=%.2f%%\n",
labels[i], re, im, prob*100.0);
}
}
int main(void)
{
cx state[4] = {1, 0, 0, 0}; /* |00> */
printf("Initial state:\n"); print_state(state);
apply_single(state, H_mat, 0); /* H on q0 */
apply_cnot(state); /* CNOT q0->q1 */
printf("\nBell state (H then CNOT):\n");
print_state(state);
/* Expected: |00>=0.707, |11>=0.707 */
return 0;
}/**
* Templated n-qubit quantum simulator in C++20.
* State vector size: 2^N complex amplitudes.
*/
#include <cmath>
#include <complex>
#include <vector>
#include <array>
#include <iostream>
#include <stdexcept>
#include <numbers>
using cx = std::complex<double>;
using Mat2 = std::array<cx, 4>; /* row-major 2x2 */
template <int N>
class QuantumSim {
public:
static constexpr int DIM = 1 << N;
std::vector<cx> state;
QuantumSim() : state(DIM, 0.0) { state[0] = 1.0; }
/* Apply 2x2 gate to the given qubit index (0 = MSB). */
void applyGate(const Mat2& U, int qubit) {
int step = 1 << (N - 1 - qubit);
for (int i = 0; i < DIM; i += 2*step) {
for (int j = i; j < i + step; j++) {
cx a0 = state[j], a1 = state[j + step];
state[j] = U[0]*a0 + U[1]*a1;
state[j + step] = U[2]*a0 + U[3]*a1;
}
}
}
/* CNOT with given control and target qubit indices. */
void applyCNOT(int ctrl, int tgt) {
int ctrlBit = 1 << (N - 1 - ctrl);
int tgtBit = 1 << (N - 1 - tgt);
for (int i = 0; i < DIM; i++) {
if ((i & ctrlBit) && !(i & tgtBit)) {
std::swap(state[i], state[i | tgtBit]);
}
}
}
void print() const {
for (int i = 0; i < DIM; i++) {
double re = state[i].real(), im = state[i].imag();
double prob = re*re + im*im;
if (prob > 1e-9) {
std::cout << " |" << std::bitset<N>(i) << "> "
<< std::showpos << re << " " << im << "i "
<< std::noshowpos << "p=" << prob*100 << "%\n";
}
}
}
};
/* Standard gate matrices */
const Mat2 H_gate = {
1.0/std::numbers::sqrt2, 1.0/std::numbers::sqrt2,
1.0/std::numbers::sqrt2, -1.0/std::numbers::sqrt2
};
const Mat2 X_gate = { 0, 1, 1, 0 };
const Mat2 Z_gate = { 1, 0, 0, -1 };
inline Mat2 Rz(double theta) {
cx emi = std::exp(cx{0, -theta/2});
cx epi = std::exp(cx{0, theta/2});
return { emi, 0, 0, epi };
}
inline Mat2 Ry(double theta) {
double c = std::cos(theta/2), s = std::sin(theta/2);
return { c, -s, s, c };
}
int main() {
QuantumSim<2> sim;
std::cout << "=== Bell state ===\n";
sim.applyGate(H_gate, 0);
sim.applyCNOT(0, 1);
sim.print();
QuantumSim<3> sim3;
std::cout << "\n=== GHZ state (3 qubits) ===\n";
sim3.applyGate(H_gate, 0);
sim3.applyCNOT(0, 1);
sim3.applyCNOT(0, 2);
sim3.print();
}/**
* QubitSystem: 2-qubit statevector simulator.
* Demonstrates gate matrices, Bell state, and measurement.
*/
public class QubitSystem {
private final int n; // number of qubits
private final int dim; // 2^n
private double[] re, im; // real and imaginary parts of amplitude
public QubitSystem(int n) {
this.n = n;
this.dim = 1 << n;
re = new double[dim];
im = new double[dim];
re[0] = 1.0; // start in |00...0>
}
/** Apply 2x2 gate (given as [re00,im00, re01,im01, re10,im10, re11,im11])
* to the specified qubit (0 = MSB). */
public void applyGate(double[] ure, double[] uim, int qubit) {
int step = 1 << (n - 1 - qubit);
for (int i = 0; i < dim; i += 2 * step) {
for (int j = i; j < i + step; j++) {
double r0 = re[j], i0 = im[j];
double r1 = re[j+step], i1 = im[j+step];
re[j] = ure[0]*r0 - uim[0]*i0 + ure[1]*r1 - uim[1]*i1;
im[j] = ure[0]*i0 + uim[0]*r0 + ure[1]*i1 + uim[1]*r1;
re[j+step] = ure[2]*r0 - uim[2]*i0 + ure[3]*r1 - uim[3]*i1;
im[j+step] = ure[2]*i0 + uim[2]*r0 + ure[3]*i1 + uim[3]*r1;
}
}
}
/** CNOT with control and target qubit indices. */
public void applyCNOT(int ctrl, int tgt) {
int ctrlBit = 1 << (n - 1 - ctrl);
int tgtBit = 1 << (n - 1 - tgt);
for (int i = 0; i < dim; i++) {
if ((i & ctrlBit) != 0 && (i & tgtBit) == 0) {
int partner = i | tgtBit;
double tr = re[i]; re[i] = re[partner]; re[partner] = tr;
double ti = im[i]; im[i] = im[partner]; im[partner] = ti;
}
}
}
public void printState() {
String[] labels = {"|00>","|01>","|10>","|11>"};
for (int i = 0; i < dim; i++) {
double prob = re[i]*re[i] + im[i]*im[i];
if (prob > 1e-9) {
System.out.printf(" %s %+.4f %+.4fi p=%.1f%%\n",
labels[i], re[i], im[i], prob*100);
}
}
}
public static void main(String[] args) {
double s = 1.0 / Math.sqrt(2);
double[] Hre = {s, s, s, -s};
double[] Him = {0, 0, 0, 0};
System.out.println("=== Bell state ===");
QubitSystem qs = new QubitSystem(2);
qs.applyGate(Hre, Him, 0); // H on q0
qs.applyCNOT(0, 1); // CNOT q0 -> q1
qs.printState();
// |00>: +0.7071 +0.0000i p=50.0%
// |11>: +0.7071 +0.0000i p=50.0%
}
}// Quantum statevector simulation using complex128.
// Demonstrates H, CNOT, Bell state, and GHZ state.
package main
import (
"fmt"
"math"
"math/cmplx"
)
type State []complex128
// NewState returns |00...0> for n qubits.
func NewState(n int) State {
s := make(State, 1<<n)
s[0] = 1
return s
}
// ApplyGate applies a 2x2 unitary to the specified qubit (0 = MSB).
func ApplyGate(s State, u [4]complex128, qubit, nQubits int) {
step := 1 << (nQubits - 1 - qubit)
dim := len(s)
for i := 0; i < dim; i += 2 * step {
for j := i; j < i+step; j++ {
a0, a1 := s[j], s[j+step]
s[j] = u[0]*a0 + u[1]*a1
s[j+step] = u[2]*a0 + u[3]*a1
}
}
}
// ApplyCNOT applies CNOT with given control and target qubit indices.
func ApplyCNOT(s State, ctrl, tgt, nQubits int) {
ctrlBit := 1 << (nQubits - 1 - ctrl)
tgtBit := 1 << (nQubits - 1 - tgt)
for i := range s {
if (i&ctrlBit) != 0 && (i&tgtBit) == 0 {
partner := i | tgtBit
s[i], s[partner] = s[partner], s[i]
}
}
}
func PrintState(s State, labels []string) {
for i, amp := range s {
p := real(amp)*real(amp) + imag(amp)*imag(amp)
if p > 1e-9 {
fmt.Printf(" %s %+.4f %+.4fi p=%.1f%%\n",
labels[i], real(amp), imag(amp), p*100)
}
}
}
func main() {
sq2 := complex(1.0/math.Sqrt2, 0)
H := [4]complex128{sq2, sq2, sq2, -sq2}
X := [4]complex128{0, 1, 1, 0}
_ = X // available for use
// Bell state: H|0> then CNOT
fmt.Println("=== Bell state |Φ+> ===")
s2 := NewState(2)
ApplyGate(s2, H, 0, 2)
ApplyCNOT(s2, 0, 1, 2)
PrintState(s2, []string{"|00>", "|01>", "|10>", "|11>"})
// GHZ state: H on q0, CNOT(0,1), CNOT(0,2)
fmt.Println("\n=== GHZ state ===")
s3 := NewState(3)
ApplyGate(s3, H, 0, 3)
ApplyCNOT(s3, 0, 1, 3)
ApplyCNOT(s3, 0, 2, 3)
labels3 := []string{"|000>","|001>","|010>","|011>",
"|100>","|101>","|110>","|111>"}
PrintState(s3, labels3)
// |000>: +0.7071 p=50.0%
// |111>: +0.7071 p=50.0%
// Verify Rz rotation
theta := math.Pi / 4 // T gate
Rz := [4]complex128{
cmplx.Exp(complex(0, -theta/2)), 0,
0, cmplx.Exp(complex(0, theta/2)),
}
st := NewState(1)
ApplyGate(st, H[:], 0, 1) // Need 1-qubit ApplyGate - see full version
_ = Rz
fmt.Println("\nDone.")
}11. Summary
Quantum gates are unitary transformations on qubit state spaces — reversible by construction, unlike classical logic gates. Single-qubit gates ($X$, $Y$, $Z$, $H$, $S$, $T$, and the rotation family $R_x, R_y, R_z$) correspond to rotations on the Bloch sphere; any single-qubit gate decomposes into three rotations via Euler angles. Two-qubit gates (CNOT, CZ, SWAP, iSWAP) create entanglement and are the primitive operations that distinguish quantum from classical computation. The gate set $\{H, T, \text{CNOT}\}$ is universal: any $n$-qubit unitary can be approximated to precision $\epsilon$ in $O(\log^c 1/\epsilon)$ gates (Solovay-Kitaev). The Clifford gates are classically simulable (Gottesman-Knill), so the $T$ gate is the resource that makes circuits classically hard; T-count is the dominant cost in fault-tolerant quantum computing. Circuits are read left-to-right; their complexity is measured by depth, gate count, and T-count. Measurement collapses superpositions, and classical feedback on measurement outcomes enables dynamic circuits essential for error correction and teleportation.
Key numbers to remember
| Fact | Value |
|---|---|
| Landauer's erasure cost at 300 K | $k_B T \ln 2 \approx 2.9 \times 10^{-21}$ J |
| Euler angles for any single-qubit gate | 3 rotations suffice: $R_z R_y R_z$ |
| SWAP = how many CNOTs? | 3 CNOTs |
| Toffoli = how many CNOTs? | 6 CNOTs (with ancilla: 4) |
| Clifford + T: what breaks simulability? | First $T$ gate |
| Solovay-Kitaev exponent $c$ | $\approx 3.97$ (best known: near 2) |
| T gates to achieve precision $\epsilon = 10^{-4}$ | $\sim O(\log^4 10^4) \approx 860$ |
| Bell states (maximally entangled 2-qubit states) | 4: $|\Phi^\pm\rangle$, $|\Psi^\pm\rangle$ |
Where to go next
- → Quantum Computing overview — the big picture of quantum algorithms and hardware platforms
- Quantum Computing: An Applied Approach (Hidary) — chapter on circuits and universality with worked examples
- 3Blue1Brown: Quantum computing basics — visual introduction to gates on the Bloch sphere
- Solovay-Kitaev theorem (original paper) — the universality result with full proof
- Gottesman-Knill theorem — why Clifford circuits are efficiently simulable classically
- Qiskit Textbook — hands-on circuit building with IBM's open-source quantum framework