Quantum Algorithms

Harnessing interference for exponential advantage — from Deutsch's one-query miracle to Shor's factoring breakthrough and Grover's optimal search, built up from first principles with full derivations.

Prereq: linear algebra, complex numbers, basic quantum mechanics Time to read: ~45 min Interactive figures: 1

1. What makes a quantum algorithm fast?

A quantum computer manipulates a state vector of $2^n$ complex amplitudes using unitary operations on $n$ qubits. That sounds like exponential memory — and classically, simulating it costs exactly that. But a quantum computer doesn't store all $2^n$ amplitudes explicitly in separate registers. Instead, a single quantum state is that vector, and a single unitary gate transforms all $2^n$ components in one physical step.

Three quantum resources conspire to produce algorithmic speedups:

  1. Superposition — a qubit can be $\alpha|0\rangle + \beta|1\rangle$ with $|\alpha|^2 + |\beta|^2 = 1$. Apply Hadamard to $n$ qubits: $H^{\otimes n}|0\rangle^n = \frac{1}{\sqrt{2^n}}\sum_{x \in \{0,1\}^n}|x\rangle$. Now a single oracle call evaluates $f$ on all $2^n$ inputs simultaneously. This is quantum parallelism.
  2. Entanglement — multi-qubit correlations that cannot be described by independent single-qubit states. Entanglement lets $n$ qubits encode correlations requiring exponentially many classical bits.
  3. Interference — amplitudes are complex numbers; they can cancel (destructive interference) or reinforce (constructive interference). The art of quantum algorithm design is engineering interference patterns that cancel amplitudes of wrong answers and boost amplitudes of right ones.
THE KEY CAVEAT

Quantum parallelism alone gives you nothing. After one oracle call on a uniform superposition, measuring gives a single uniformly random function value — no better than a single classical query. The entire difficulty of quantum algorithm design is arranging interference so that the right answer has high amplitude before measurement. Most functions have no useful interference structure. The algorithms on this page are the rare cases where such structure exists and can be exploited.

Proven quantum speedups

Problem Classical Quantum Speedup type
Unstructured search ($N$ items) $O(N)$ $O(\sqrt{N})$ Quadratic (optimal)
Integer factoring ($N$-bit) sub-exp $L[1/3]$ $O((\log N)^3)$ Exponential (conjectured)
Period finding Exponential Polynomial Exponential
Simulating quantum systems Exponential Polynomial Exponential
Solving linear systems (HHL) $O(N)$ $O(\log N)$ Exponential (with caveats)
Deutsch-Jozsa constant/balanced $N/2 + 1$ queries $1$ query Exponential (query)

2. Query complexity and oracle models

To compare quantum and classical algorithms fairly, we use the oracle model (also called the black-box model or query model). The algorithm is given access to a function $f: \{0,1\}^n \to \{0,1\}^m$ only through a black-box oracle. We count the number of queries to $f$, not the total computation time.

A classical deterministic oracle call evaluates $f(x)$ for one chosen input $x$. A quantum oracle acts as a unitary:

$$O_f |x\rangle|y\rangle = |x\rangle|y \oplus f(x)\rangle$$

where $\oplus$ denotes bitwise XOR and $|x\rangle|y\rangle$ is a tensor product of an $n$-qubit input register and an $m$-qubit output register. Because $O_f$ is linear, a superposition state gets all function values simultaneously:

$$O_f \left(\sum_x \alpha_x |x\rangle\right)|0\rangle = \sum_x \alpha_x |x\rangle|f(x)\rangle$$

The query complexity model cleanly separates algorithmic structure from implementation details. Quantum advantage in query complexity is proven — not conjectured — for many problems. Whether that translates to real computational speedup depends on the time to implement the oracle, but the query separations are mathematically rigorous.

Oracle model key terms

$f: \{0,1\}^n \to \{0,1\}^m$
The black-box function. In a concrete algorithm this might be $f(x) = a^x \bmod N$ (Shor) or $f(x) = [x = x^*]$ (Grover), but the oracle model treats it opaquely.
$O_f$
The quantum phase oracle. The $\oplus$ ensures $O_f$ is its own inverse: $O_f^2 = I$ (XOR twice restores the original). This makes $O_f$ a valid unitary (in fact, a reflection).
Phase kickback
If the output register is initialized to $\frac{|0\rangle - |1\rangle}{\sqrt{2}}$ (eigenstate of $X$ with eigenvalue $-1$), then $O_f|x\rangle\frac{|0\rangle-|1\rangle}{\sqrt{2}} = (-1)^{f(x)}|x\rangle\frac{|0\rangle-|1\rangle}{\sqrt{2}}$. The function value kicks back as a phase onto the input register. Phase kickback is the key trick in Deutsch-Jozsa, Bernstein-Vazirani, Simon, Grover, and more. The ancilla qubit is left unchanged, so it can be ignored. All the information is in the relative phases of $|x\rangle$.
$D(f)$
Deterministic query complexity — minimum queries needed by any deterministic classical algorithm.
$Q(f)$
Quantum query complexity — minimum queries needed by any bounded-error quantum algorithm.

3. Deutsch-Jozsa Algorithm (1992)

Problem: You are given oracle access to $f: \{0,1\}^n \to \{0,1\}$ with the promise that $f$ is either constant (outputs the same value for all inputs) or balanced (outputs 0 for exactly half the inputs and 1 for the other half). Determine which.

Classical complexity: In the worst case, a deterministic algorithm needs $N/2 + 1 = 2^{n-1} + 1$ queries. You might query $2^{n-1}$ inputs that all return 0, and only the next query distinguishes a function that is constant-zero from one that has exactly $2^{n-1}$ zeros and $2^{n-1}$ ones. A randomized algorithm can decide with high probability in $O(1)$ queries, but Deutsch-Jozsa is the canonical example of a deterministic quantum algorithm that is exponentially faster than its deterministic classical counterpart.

Quantum complexity: exactly 1 oracle query.

The circuit

  1. Initialize: $|0\rangle^{\otimes n}|1\rangle$
  2. Apply $H^{\otimes n} \otimes H$: creates uniform superposition on first $n$ qubits, puts ancilla in $\frac{|0\rangle - |1\rangle}{\sqrt{2}}$
  3. Apply oracle $O_f$ (one query)
  4. Apply $H^{\otimes n}$ to first $n$ qubits
  5. Measure first $n$ qubits: all-zeros $\Rightarrow$ constant; any nonzero outcome $\Rightarrow$ balanced

Full derivation

After step 2 (applying $H^{\otimes n+1}$):

$$|\psi_1\rangle = \frac{1}{\sqrt{2^n}}\sum_{x \in \{0,1\}^n}|x\rangle \otimes \frac{|0\rangle - |1\rangle}{\sqrt{2}}$$

After oracle (phase kickback gives $(-1)^{f(x)}$ to each $|x\rangle$ term):

$$|\psi_2\rangle = \frac{1}{\sqrt{2^n}}\sum_{x}(-1)^{f(x)}|x\rangle \otimes \frac{|0\rangle - |1\rangle}{\sqrt{2}}$$

After $H^{\otimes n}$ on the first register. Using the identity $H^{\otimes n}|x\rangle = \frac{1}{\sqrt{2^n}}\sum_z (-1)^{x \cdot z}|z\rangle$:

$$|\psi_3\rangle = \sum_{z \in \{0,1\}^n} \underbrace{\left(\frac{1}{2^n}\sum_{x}(-1)^{f(x)+x\cdot z}\right)}_{\text{amplitude of }|z\rangle}|z\rangle \otimes \frac{|0\rangle - |1\rangle}{\sqrt{2}}$$

The amplitude of $|0\rangle^n$ (i.e., $z = 0\ldots0$) is:

$$A_{0^n} = \frac{1}{2^n}\sum_{x}(-1)^{f(x)}$$

For a constant function ($f(x) = c$ for all $x$): $A_{0^n} = (-1)^c \cdot \frac{1}{2^n} \cdot 2^n = \pm 1$. The state collapses to $|0\rangle^n$ with certainty. For a balanced function: exactly half the terms contribute $+1$ and half contribute $-1$, so $A_{0^n} = 0$. The state has zero overlap with $|0\rangle^n$ — we never measure all zeros.

THE INSIGHT

One oracle query creates a superposition in which $(-1)^{f(x)}$ phases encode the entire truth table of $f$. Applying $H^{\otimes n}$ computes the Walsh-Hadamard transform of those phases. The constant-vs-balanced distinction maps to whether the zero-frequency component is nonzero — a global property that one classical query cannot reveal but that interference extracts perfectly.

4. Bernstein-Vazirani Algorithm (1993)

Problem: $f(x) = s \cdot x \pmod{2}$ for a hidden string $s \in \{0,1\}^n$ (the inner product of $s$ and $x$ over $\mathbb{F}_2$). Find $s$. Classical: $n$ queries (query each unit vector $e_i$ to read off $s_i$). Quantum: 1 query.

The circuit is identical to Deutsch-Jozsa. The difference is what we measure. After the oracle and final Hadamard, the amplitude of $|y\rangle$ is:

$$A_y = \frac{1}{2^n}\sum_{x}(-1)^{f(x) + y \cdot x} = \frac{1}{2^n}\sum_{x}(-1)^{(s \oplus y)\cdot x}$$

By the standard identity $\frac{1}{2^n}\sum_x (-1)^{z \cdot x} = [z = 0^n]$ (it's 1 if $z = 0^n$ and 0 otherwise), we get $A_y = [s = y]$. The measurement outcome is exactly $s$ with probability 1.

In one query, you learn all $n$ bits of $s$ simultaneously. Classically you need $n$ queries, because each query gives only one bit of information about $s$. The quantum algorithm extracts all $n$ bits from the global phase structure of a single superposition evaluation.

5. Simon's Algorithm (1994)

Problem: Given oracle access to $f: \{0,1\}^n \to \{0,1\}^n$ with the promise that there exists a nonzero $s \in \{0,1\}^n$ such that $f(x) = f(y)$ if and only if $y = x$ or $y = x \oplus s$ (i.e., $f$ is 2-to-1 with period $s$). Find $s$. Classical: $\Omega(2^{n/2})$ queries. Quantum: $O(n)$ queries.

Simon's algorithm (1994) was the first proof that quantum computers can be exponentially faster than classical computers in the oracle model, and it directly inspired Shor's factoring algorithm two months later.

The procedure

Repeat $O(n)$ times:

  1. Prepare $|0\rangle^n|0\rangle^n$
  2. Apply $H^{\otimes n}$ to first register: $\frac{1}{\sqrt{2^n}}\sum_x |x\rangle|0\rangle$
  3. Query oracle: $\frac{1}{\sqrt{2^n}}\sum_x |x\rangle|f(x)\rangle$
  4. Measure second register: say we observe $f(x_0)$. The first register collapses to $\frac{|x_0\rangle + |x_0 \oplus s\rangle}{\sqrt{2}}$
  5. Apply $H^{\otimes n}$ to first register. Each outcome $y$ has amplitude $\frac{1}{\sqrt{2^{n-1}}}$ iff $y \cdot s = 0 \pmod 2$, else 0.
  6. Measure first register: get a random $y$ with $y \cdot s = 0 \pmod 2$

After $n-1$ linearly independent equations $\{y_i \cdot s = 0\}$, classical Gaussian elimination over $\mathbb{F}_2$ recovers $s$ uniquely. Expected number of rounds until $n-1$ independent vectors: $O(n)$ (birthday-argument analysis).

Why the classical lower bound is exponential

Any classical algorithm that asks fewer than $2^{n/2}$ queries cannot distinguish the case $s = 0^n$ (injective $f$) from $s \neq 0^n$ with good probability, because with high probability you haven't seen any collision $f(x) = f(x \oplus s)$ yet. The Birthday paradox: you need $\Theta(\sqrt{2^n}) = \Theta(2^{n/2})$ random queries before seeing a collision.

6. The Quantum Fourier Transform

The Discrete Fourier Transform (DFT) of a length-$N$ vector $f_0, \ldots, f_{N-1}$ is:

$$\hat{f}_k = \sum_{j=0}^{N-1} f_j\, \omega^{jk}, \qquad \omega = e^{2\pi i/N}$$

The Quantum Fourier Transform (QFT) is the unitary that performs this transformation on amplitude vectors:

$$\text{QFT}_N |j\rangle = \frac{1}{\sqrt{N}}\sum_{k=0}^{N-1}e^{2\pi i jk/N}|k\rangle$$

For $N = 2^n$ (the case we care about), the QFT acts on $n$-qubit states. By writing $k$ in binary $k = k_1 2^{n-1} + \ldots + k_n 2^0$, the QFT factors as a product state:

$$\text{QFT}_{2^n}|j\rangle = \frac{1}{\sqrt{2^n}}\bigotimes_{l=1}^{n}\left(|0\rangle + e^{2\pi i j/2^l}|1\rangle\right)$$

This product form enables an efficient circuit. The QFT on $n$ qubits uses:

Total: $O(n^2)$ gates. The classical FFT needs $O(n \cdot 2^n)$ operations (it's fast, but exponential in $n$). The QFT is exponentially faster — but we pay: we cannot read out all $2^n$ Fourier coefficients, only sample from them.

THE SAMPLING CAVEAT

The QFT transforms $\sum_j f_j |j\rangle$ to $\sum_k \hat{f}_k |k\rangle$. Measuring gives a single $k$ drawn proportionally to $|\hat{f}_k|^2$. So the QFT is only useful when the Fourier spectrum is concentrated at a few frequencies you care about — as in period finding, where the spectrum has peaks at multiples of $1/r$.

QFT circuit for $n = 3$ qubits

$$\begin{array}{lcl} |q_0\rangle & \longrightarrow & H \;\; R_2 \;\; R_3 \;\; \cdots \\ |q_1\rangle & \longrightarrow & \cdot \;\;\;\;\; H \;\; R_2 \;\;\; \cdots \\ |q_2\rangle & \longrightarrow & \cdot \;\;\;\;\; \cdot \;\;\;\;\; H \;\;\;\; \cdots \\ \end{array} \quad\text{followed by bit reversal}$$

The $R_k$ gate on qubit $q_i$ is controlled by qubit $q_j$ where $k = j - i + 1$. Each Hadamard creates the $|0\rangle + e^{i\phi}|1\rangle$ superposition; the controlled phases build up the binary fractions $0.j_{m}\ldots j_n$ in the exponent.

7. Shor's Factoring Algorithm (1994)

Peter Shor's 1994 algorithm factors an $n$-bit integer $N$ in polynomial time $O(n^3)$ on a quantum computer. It threatens RSA and all cryptosystems based on the hardness of integer factoring. The best classical algorithm (general number field sieve) runs in sub-exponential time $L[1/3, (64/9)^{1/3}]$, which is still superpolynomial.

Step 1 — Classical reduction to period finding

This step uses classical number theory (Euler, Fermat):

  1. If $N$ is even, return 2. If $N = p^k$, use classical algorithms to find $p$.
  2. Pick a random $a$ with $1 < a < N$ and compute $\gcd(a, N)$ classically. If $\gcd \neq 1$, we found a factor — done.
  3. Otherwise, find the period $r$ of the modular exponential: the smallest $r > 0$ with $a^r \equiv 1 \pmod N$.
  4. If $r$ is even and $a^{r/2} \not\equiv -1 \pmod N$, then $\gcd(a^{r/2} - 1, N)$ and $\gcd(a^{r/2} + 1, N)$ are non-trivial factors of $N$ with probability $\geq 1/2$ over the random choice of $a$.
WHY STEP 4 WORKS

From $a^r \equiv 1 \pmod N$ we get $a^r - 1 \equiv 0 \pmod N$. If $r$ is even, $a^r - 1 = (a^{r/2}-1)(a^{r/2}+1)$. Both factors share a non-trivial factor with $N$ unless $a^{r/2} \equiv \pm 1 \pmod N$. The probability analysis shows this bad case has probability at most $1/2$ for each randomly chosen $a$ when $N$ has at least two distinct odd prime factors.

Step 2 — Quantum period finding

This is where the quantum speedup comes from. Choose $Q = 2^m$ with $N^2 \leq Q < 2N^2$. The circuit:

1. Prepare uniform superposition:

$$\frac{1}{\sqrt{Q}}\sum_{x=0}^{Q-1}|x\rangle|0\rangle$$

2. Apply the modular exponentiation oracle (compute $a^x \bmod N$ into the second register):

$$\frac{1}{\sqrt{Q}}\sum_{x=0}^{Q-1}|x\rangle|a^x \bmod N\rangle$$

3. Measure the second register. Say we observe the value $v = a^{x_0} \bmod N$. The first register collapses to a uniform superposition over all $x$ with $a^x \equiv v \pmod N$, i.e., all $x = x_0, x_0 + r, x_0 + 2r, \ldots$:

$$\frac{1}{\sqrt{Q/r}}\sum_{k=0}^{Q/r - 1}|x_0 + kr\rangle$$

4. Apply the QFT to the first register. The amplitudes concentrate at multiples of $Q/r$:

$$\text{QFT}\left(\frac{1}{\sqrt{Q/r}}\sum_k |x_0 + kr\rangle\right) \approx \frac{1}{\sqrt{r}}\sum_{j=0}^{r-1} e^{2\pi i j x_0/r}\left|\frac{jQ}{r}\right\rangle$$

5. Measure the first register: get some multiple $j \cdot Q/r$. Dividing by $Q$ gives $j/r$ as a rational number. Using the continued fraction algorithm classically, extract the denominator $r$ from the rational approximation $j/r \approx \text{measured value}/Q$.

Success probability per run: $\Omega(1/\log\log r)$. Expected $O(\log\log N)$ quantum runs suffice.

Complexity and cryptographic implications

ResourceShor's algorithmClassical best (GNFS)
Gate count$O(n^3)$ or $O(n^2 \log n)$ with Fourier sampling$\exp(O(n^{1/3}(\log n)^{2/3}))$
Logical qubits needed (RSA-2048)$\approx 4000$N/A (classical)
Physical qubits needed (with error correction)Millions (estimated)N/A
Current state of hardware~1000 physical qubitsClassical clusters easily factor 800-bit numbers

The post-quantum cryptography response: NIST standardized lattice-based cryptographic algorithms in 2024: CRYSTALS-Kyber (ML-KEM) for key encapsulation and CRYSTALS-Dilithium (ML-DSA) for digital signatures. These are believed secure against both classical and quantum adversaries.

8. Grover's Search Algorithm (1996)

Problem: Search an unstructured list of $N = 2^n$ items for a target $x^*$ satisfying $f(x^*) = 1$ (and $f(x) = 0$ for all others). Classical: $O(N)$ queries. Quantum: $O(\sqrt{N})$ queries. Grover's algorithm is provably optimal — no quantum algorithm can do better than $\Omega(\sqrt{N})$ queries.

Amplitude amplification geometry

Start with the uniform superposition:

$$|s\rangle = H^{\otimes n}|0\rangle^n = \frac{1}{\sqrt{N}}\sum_{x=0}^{N-1}|x\rangle$$

Decompose this in the 2D subspace spanned by the target $|x^*\rangle$ and the uniform superposition over non-targets $|s'\rangle = \frac{1}{\sqrt{N-1}}\sum_{x \neq x^*}|x\rangle$:

$$|s\rangle = \sin\theta\,|x^*\rangle + \cos\theta\,|s'\rangle, \qquad \sin\theta = \frac{1}{\sqrt{N}}$$

For large $N$, $\theta \approx 1/\sqrt{N}$ is tiny — the initial state has small overlap with the target.

One Grover iteration

Each iteration applies two reflections:

1. Oracle reflection $O_f$ — flips the sign of the target:

$$O_f|x\rangle = (-1)^{f(x)}|x\rangle \qquad \Rightarrow \qquad O_f = I - 2|x^*\rangle\langle x^*|$$

2. Diffusion operator $D$ — reflects about the uniform superposition:

$$D = 2|s\rangle\langle s| - I$$

In circuit form, $D = H^{\otimes n}(2|0\rangle\langle 0| - I)H^{\otimes n}$, implemented with $n$ Hadamards, a multi-controlled $Z$ gate (or controlled-NOT network), and $n$ more Hadamards.

The composition $G = D \cdot O_f$ is a rotation by $2\theta$ in the 2D subspace:

$$G^k|s\rangle = \sin((2k+1)\theta)|x^*\rangle + \cos((2k+1)\theta)|s'\rangle$$

The probability of measuring $|x^*\rangle$ after $k$ iterations is $\sin^2((2k+1)\theta)$. This is maximized at $(2k+1)\theta \approx \pi/2$, giving:

$$k_{\text{opt}} = \left\lfloor\frac{\pi}{4\theta}\right\rfloor \approx \left\lfloor\frac{\pi\sqrt{N}}{4}\right\rfloor$$

After $k_{\text{opt}}$ iterations, the probability of finding $x^*$ is $\geq 1 - 1/N$.

Why you can't do better

The BBBV theorem (Bennett, Bernstein, Brassard, Vazirani, 1996): any quantum algorithm for unstructured search requires $\Omega(\sqrt{N})$ oracle calls. The proof uses a hybrid argument showing that the amplitude of the target state grows by at most $O(1/\sqrt{N})$ per oracle call from any starting state, so $\Omega(\sqrt{N})$ calls are needed to reach constant amplitude.

Multiple targets

If there are $t$ targets out of $N$ items, replace $\sin\theta = 1/\sqrt{N}$ with $\sin\theta = \sqrt{t/N}$. The optimal number of iterations becomes $O(\sqrt{N/t})$. For $t = N/2$ (half the database is marked), only $1$ or $2$ iterations suffice.

9. Quantum Phase Estimation (QPE)

QPE is a meta-algorithm — it's a subroutine used inside Shor's algorithm, quantum chemistry simulations (HHL, VQE), and many others. Given a unitary $U$ and one of its eigenstates $|u\rangle$ with eigenvalue $e^{2\pi i \varphi}$ (i.e., $U|u\rangle = e^{2\pi i \varphi}|u\rangle$), QPE estimates $\varphi$ to $t$ bits of precision using $O(t)$ applications of controlled-$U$.

The circuit

  1. Allocate $t$ ancilla "counting" qubits initialized to $|0\rangle$ and one (or more) "phase" register(s) holding $|u\rangle$.
  2. Apply $H^{\otimes t}$ to the counting register: $\frac{1}{\sqrt{2^t}}\sum_{j=0}^{2^t-1}|j\rangle \otimes |u\rangle$
  3. For $k = 0, 1, \ldots, t-1$: apply $\text{controlled-}U^{2^k}$ (the $k$-th counting qubit controls $U$ applied $2^k$ times to the phase register). By eigenvalue, the phase register is unchanged, but the counting qubit picks up a phase $e^{2\pi i \varphi \cdot 2^k}$.
  4. After all controlled unitaries, the counting register holds: $\frac{1}{\sqrt{2^t}}\sum_{j}e^{2\pi i \varphi j}|j\rangle$
  5. Apply $\text{QFT}^{-1}$ to the counting register. If $\varphi = m/2^t$ exactly, the output is $|m\rangle$ with certainty. Otherwise, the output is concentrated within $\pm 1$ of $2^t\varphi$.
  6. Measure: the result is a $t$-bit approximation to $\varphi$.

To use QPE in practice you need to efficiently implement controlled-$U^{2^k}$. In Shor's algorithm, $U|y\rangle = |ay \bmod N\rangle$ and controlled-$U^{2^k}$ is modular exponentiation by repeated squaring. In quantum chemistry, $U = e^{-iHt}$ for a molecular Hamiltonian $H$, and QPE extracts the ground-state energy $E_0$ (since $H|E_0\rangle = E_0|E_0\rangle$ means $e^{-iHt}|E_0\rangle = e^{-iE_0 t}|E_0\rangle$).

10. VQE and QAOA (near-term algorithms)

Fault-tolerant quantum algorithms like Shor's and QPE require error-corrected logical qubits, which need thousands of physical qubits each. Current hardware (NISQ — Noisy Intermediate-Scale Quantum) has 50–1000 noisy physical qubits. Near-term algorithms are designed to run on NISQ devices without error correction.

Variational Quantum Eigensolver (VQE)

VQE finds the ground state energy $E_0 = \min_{|\psi\rangle} \langle\psi|H|\psi\rangle$ of a Hamiltonian $H$ (often a molecular Hamiltonian in quantum chemistry). It uses the variational principle: any state gives an upper bound on $E_0$.

Define a parameterized quantum circuit (the "ansatz") $U(\theta)$ that prepares the state $|\psi(\theta)\rangle = U(\theta)|0\rangle^n$ for some vector of parameters $\theta$. The VQE loop:

  1. Run $|\psi(\theta)\rangle$ on quantum hardware; measure $\langle\psi(\theta)|H|\psi(\theta)\rangle$ by decomposing $H$ into Pauli terms and measuring each separately.
  2. Pass the energy estimate to a classical optimizer (COBYLA, BFGS, gradient descent).
  3. The optimizer proposes new $\theta'$; repeat until convergence.
$$E(\theta) = \langle\psi(\theta)|H|\psi(\theta)\rangle \geq E_0$$

VQE outperforms classical Hartree-Fock on strongly correlated molecules (e.g., FeMoco, the iron-molybdenum cofactor in nitrogenase). The challenge is the "barren plateau" problem: for deep circuits, $\nabla_\theta E(\theta)$ vanishes exponentially with system size, making optimization infeasible.

Quantum Approximate Optimization Algorithm (QAOA)

QAOA targets combinatorial optimization: find $x^* \in \{0,1\}^n$ maximizing $C(x)$ (e.g., Max-Cut of a graph). Encode the objective as a diagonal Hamiltonian $H_C$ with $H_C|x\rangle = C(x)|x\rangle$, and define a mixer Hamiltonian $H_B = \sum_i X_i$.

Depth-$p$ QAOA prepares:

$$|\gamma, \beta\rangle = e^{-i\beta_p H_B}e^{-i\gamma_p H_C} \cdots e^{-i\beta_1 H_B}e^{-i\gamma_1 H_C}|s\rangle$$

and maximizes $\langle\gamma,\beta|H_C|\gamma,\beta\rangle$ over the $2p$ real parameters $\{\gamma_i, \beta_i\}$. As $p \to \infty$, the optimal QAOA state approaches the true optimum (adiabatic theorem). For finite $p$, QAOA provides an approximation guarantee that improves with depth.

For Max-Cut on 3-regular graphs, depth-1 QAOA achieves approximation ratio $\geq 0.6924$ (Farhi et al., 2014). The best classical algorithm achieves $\approx 0.8786$ (Goemans-Williamson SDP), so QAOA isn't there yet — but it's a natural fit for quantum hardware.

11. Interactive: Grover's search visualizer

The diagram below shows the geometric picture of amplitude amplification in the 2D subspace spanned by the target state $|x^*\rangle$ and the uniform superposition over non-targets $|s'\rangle$. Each Grover iteration rotates the state vector by $2\theta$ toward $|x^*\rangle$. Use the N items slider to change the database size, then step through the iterations.

Grover amplitude amplification — geometric picture

The unit circle represents the 2D subspace. The blue vector is the current quantum state. Each iteration rotates it by $2\theta$ toward $|x^*\rangle$ (top). The green bar shows the probability of measuring the target on the next measurement.

12. Source code

Source — Quantum Algorithms
// ================================================================
// DEUTSCH-JOZSA
// ================================================================
function deutsch_jozsa(oracle_f, n):
    // Initialize n+1 qubits: |0...0>|1>
    qubits = [ZERO]*n + [ONE]
    // Apply H to all qubits
    for q in qubits: apply_H(q)
    // Query oracle once
    apply_oracle(oracle_f, qubits[0..n-1], qubits[n])
    // Apply H to first n qubits
    for q in qubits[0..n-1]: apply_H(q)
    // Measure first n qubits
    result = measure(qubits[0..n-1])
    return "constant" if result == 0...0 else "balanced"

// ================================================================
// GROVER'S ITERATION
// ================================================================
function grover_oracle(state, target):
    // Flip sign of target state amplitude
    state[target] *= -1
    return state

function grover_diffusion(state):
    N = len(state)
    mean = sum(state) / N
    // Reflect about the mean
    return [2*mean - a for a in state]

function grover_search(f, n):
    N = 2^n
    // Start: uniform superposition
    state = [1/sqrt(N)] * N
    k_opt = floor(pi * sqrt(N) / 4)
    for i in 1..k_opt:
        state = grover_oracle(state, target)
        state = grover_diffusion(state)
    // Measure: highest amplitude index is the target
    return argmax(|state[i]|^2)

// ================================================================
// QFT (recursive)
// ================================================================
function QFT(state, n):
    if n == 0: return state
    // Apply H to first qubit
    apply_H(state, qubit=0)
    // Apply controlled phase rotations
    for k in 2..n:
        apply_controlled_R(state, control=k-1, target=0, angle=2*pi/2^k)
    // Recurse on remaining n-1 qubits
    QFT(state[subsystem 1..n-1], n-1)
    // Reverse bit order
    swap_bits(state)
    return state

// ================================================================
// SHOR'S REDUCTION TO PERIOD FINDING
// ================================================================
function shor_factor(N):
    while true:
        a = random_int(2, N-1)
        g = gcd(a, N)
        if g > 1: return g         // lucky: immediate factor
        r = quantum_period_find(a, N)  // quantum subroutine
        if r % 2 != 0: continue    // r must be even
        x = modpow(a, r//2, N)
        if x == N-1: continue      // degenerate case
        f1 = gcd(x-1, N)
        f2 = gcd(x+1, N)
        if 1 < f1 < N: return f1
        if 1 < f2 < N: return f2
"""
Quantum algorithm implementations using Qiskit and NumPy.
"""

import numpy as np
from math import gcd, pi, sqrt, floor

# ================================================================
# Manual state-vector simulation helpers
# ================================================================

def tensor(*ops):
    """Tensor product of matrices."""
    result = ops[0]
    for op in ops[1:]:
        result = np.kron(result, op)
    return result

H = np.array([[1, 1], [1, -1]]) / sqrt(2)
I = np.eye(2)
X = np.array([[0, 1], [1, 0]])
Z = np.array([[1, 0], [0, -1]])

def R(k):
    """Phase rotation gate R_k."""
    return np.array([[1, 0], [0, np.exp(2j * pi / 2**k)]])

# ================================================================
# Deutsch-Jozsa (statevector simulation)
# ================================================================

def deutsch_jozsa_sim(f_bitstring):
    """
    f_bitstring: list of 2^n values (0 or 1), defines f on {0,1}^n.
    Returns 'constant' or 'balanced'.
    """
    n = int(np.log2(len(f_bitstring)))
    N = 2**n
    # Build oracle matrix U_f for phase kickback
    U_f = np.eye(2 * N, dtype=complex)
    for x in range(N):
        if f_bitstring[x] == 1:
            U_f[2*x, 2*x]     =  0;  U_f[2*x, 2*x+1]   = 1
            U_f[2*x+1, 2*x]   =  1;  U_f[2*x+1, 2*x+1] = 0
    # Initial state |0...0>|1>
    state = np.zeros(2 * N, dtype=complex)
    state[1] = 1.0  # last qubit = |1>
    # Apply H to all n+1 qubits
    H_all = tensor(*([H] * (n + 1)))
    state = H_all @ state
    # Apply oracle
    state = U_f @ state
    # Apply H to first n qubits (acting on 2N dimensional space)
    H_n = tensor(*([H] * n), I)
    state = H_n @ state
    # Measure first n qubits (project onto |0...0> in first register)
    # Sum probabilities of |0...0>|0> and |0...0>|1>
    prob_zero = abs(state[0])**2 + abs(state[1])**2
    return 'constant' if prob_zero > 0.99 else 'balanced'

# Test: constant function
f_const = [0] * 8
print(deutsch_jozsa_sim(f_const))  # 'constant'

# Test: balanced function (first half 0, second half 1)
f_bal = [0]*4 + [1]*4
print(deutsch_jozsa_sim(f_bal))    # 'balanced'

# ================================================================
# Grover's search (statevector simulation)
# ================================================================

def grover_search(target, n):
    """
    Find 'target' in database of size 2^n using Grover's algorithm.
    Returns found index.
    """
    N = 2**n
    # Uniform superposition
    state = np.ones(N, dtype=complex) / sqrt(N)
    k_opt = floor(pi * sqrt(N) / 4)

    for _ in range(k_opt):
        # Oracle: flip sign of target
        state[target] *= -1
        # Diffusion: reflect about mean
        mean = np.mean(state)
        state = 2 * mean - state
        state[target] = -(state[target] - 2*mean) + 2*mean  # undo double
        # Redo correctly:
        state_before_diff = state.copy()
        state_before_diff[target] *= -1  # undo target flip for diffusion
        # Actually do it right:

    # Redo from scratch properly
    state = np.ones(N, dtype=complex) / sqrt(N)
    for _ in range(k_opt):
        # Step 1: Oracle
        state[target] *= -1
        # Step 2: Diffusion D = 2|s> 0.1)[0])
# Should show peaks at multiples of N/r = 2

# ================================================================
# Qiskit: Deutsch-Jozsa
# ================================================================

try:
    from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister
    from qiskit_aer import AerSimulator

    def dj_circuit_balanced(n=3):
        """Build a balanced Deutsch-Jozsa circuit."""
        qr = QuantumRegister(n + 1, 'q')
        cr = ClassicalRegister(n, 'c')
        qc = QuantumCircuit(qr, cr)
        qc.x(qr[n])                 # ancilla to |1>
        qc.h(range(n + 1))          # H to all qubits
        # Balanced oracle: XOR each input qubit into ancilla
        for i in range(n):
            qc.cx(qr[i], qr[n])
        qc.h(range(n))              # H to first n qubits
        qc.measure(range(n), range(n))
        return qc

    qc = dj_circuit_balanced(n=3)
    sim = AerSimulator()
    result = sim.run(qc, shots=1024).result()
    print("DJ balanced result:", result.get_counts())
    # Should show only non-zero strings (balanced)
except ImportError:
    print("Qiskit not installed; skipping circuit demo.")
/**
 * Manual state-vector simulation of Grover's search on 4 elements.
 * No external libraries. Runs in any JavaScript environment.
 */
(function() {
  const N = 4;         // database size = 2^2
  const TARGET = 2;    // we're searching for index 2

  // State vector: complex numbers as {re, im}
  let state = Array.from({length: N}, () => ({re: 1/Math.sqrt(N), im: 0}));

  function cMul(a, b) {
    return { re: a.re*b.re - a.im*b.im, im: a.re*b.im + a.im*b.re };
  }
  function cAdd(a, b) { return { re: a.re+b.re, im: a.im+b.im }; }
  function cScale(s, a) { return { re: s*a.re, im: s*a.im }; }
  function cNeg(a) { return { re: -a.re, im: -a.im }; }
  function cAbs2(a) { return a.re*a.re + a.im*a.im; }

  function oracle(state, target) {
    // Multiply target amplitude by -1
    const s = state.map((a, i) => i === target ? cNeg(a) : {...a});
    return s;
  }

  function diffusion(state) {
    // D = 2|s> = uniform superposition
    // New amplitude: 2 * mean - old amplitude
    const n = state.length;
    const meanRe = state.reduce((s, a) => s + a.re, 0) / n;
    const meanIm = state.reduce((s, a) => s + a.im, 0) / n;
    return state.map(a => ({
      re: 2*meanRe - a.re,
      im: 2*meanIm - a.im
    }));
  }

  const kOpt = Math.floor(Math.PI * Math.sqrt(N) / 4);
  console.log(`Searching ${N} items for target=${TARGET}`);
  console.log(`Optimal iterations: ${kOpt}`);

  // Initial probabilities
  console.log('Initial probabilities:', state.map(a => cAbs2(a).toFixed(4)));

  for (let k = 0; k < kOpt; k++) {
    state = oracle(state, TARGET);
    state = diffusion(state);
    const probs = state.map(a => cAbs2(a).toFixed(4));
    console.log(`After iter ${k+1}:`, probs);
  }

  const foundIdx = state.reduce((best, a, i) =>
    cAbs2(a) > cAbs2(state[best]) ? i : best, 0);

  console.log(`Measured: ${foundIdx} (target was ${TARGET})`);
  console.log(`Probability of finding target: ${(cAbs2(state[TARGET])*100).toFixed(1)}%`);
})();

/**
 * QFT on a 4-element state vector (n=2 qubits).
 * Manual implementation without library dependencies.
 */
(function() {
  const N = 4;
  const TWO_PI = 2 * Math.PI;

  function qft(state) {
    // state is array of {re, im} complex numbers
    const out = Array.from({length: N}, () => ({re: 0, im: 0}));
    for (let k = 0; k < N; k++) {
      for (let j = 0; j < N; j++) {
        const angle = TWO_PI * j * k / N;
        const re = Math.cos(angle);
        const im = Math.sin(angle);
        out[k].re += (state[j].re * re - state[j].im * im) / Math.sqrt(N);
        out[k].im += (state[j].re * im + state[j].im * re) / Math.sqrt(N);
      }
    }
    return out;
  }

  // Signal with period 2 (alternating 1, -1, 1, -1)
  const signal = [
    {re: 1/Math.sqrt(2), im: 0},
    {re: 0, im: 0},
    {re: 1/Math.sqrt(2), im: 0},
    {re: 0, im: 0}
  ];
  const spectrum = qft(signal);
  console.log('QFT spectrum |amplitudes|^2:',
    spectrum.map(a => (a.re*a.re + a.im*a.im).toFixed(4)));
  // Peak at k=0 and k=2, corresponding to period N/2 = 2.
})();
/*
 * QFT on a classical complex array (n=3, N=8).
 * Illustrates the circuit structure via explicit gate application.
 * Compile: gcc -O2 -lm quantum_sim.c -o quantum_sim
 */
#include <stdio.h>
#include <math.h>
#include <complex.h>

#define N_QUBITS 3
#define N_STATES 8

typedef double complex cplx;

/* Apply Hadamard to qubit 'qubit' of an n-qubit state vector */
void apply_H(cplx *state, int qubit, int n) {
    int stride = 1 << qubit;
    int size   = 1 << n;
    for (int i = 0; i < size; i++) {
        if ((i & stride) == 0) {
            cplx a = state[i];
            cplx b = state[i | stride];
            state[i]        = (a + b) / sqrt(2.0);
            state[i|stride] = (a - b) / sqrt(2.0);
        }
    }
}

/* Apply controlled-Rk (phase gate) with control=ctrl, target=tgt */
void apply_CR(cplx *state, int ctrl, int tgt, int k, int n) {
    cplx phase = cexp(2.0 * M_PI * I / (1 << k));
    int size   = 1 << n;
    for (int i = 0; i < size; i++) {
        /* Both ctrl and tgt must be |1> to apply phase */
        if ((i >> ctrl & 1) && (i >> tgt & 1)) {
            state[i] *= phase;
        }
    }
}

/* Swap qubits a and b */
void swap_qubits(cplx *state, int a, int b, int n) {
    int size = 1 << n;
    for (int i = 0; i < size; i++) {
        int bit_a = (i >> a) & 1;
        int bit_b = (i >> b) & 1;
        if (bit_a != bit_b) {
            int j = i ^ (1 << a) ^ (1 << b);
            if (i < j) {
                cplx tmp = state[i];
                state[i] = state[j];
                state[j] = tmp;
            }
        }
    }
}

/* n-qubit QFT */
void qft(cplx *state, int n) {
    for (int qubit = n - 1; qubit >= 0; qubit--) {
        apply_H(state, qubit, n);
        for (int ctrl = qubit - 1; ctrl >= 0; ctrl--) {
            int k = qubit - ctrl + 1;
            apply_CR(state, ctrl, qubit, k, n);
        }
    }
    /* Reverse bit order */
    for (int i = 0; i < n / 2; i++) {
        swap_qubits(state, i, n - 1 - i, n);
    }
}

int main(void) {
    /* Period-4 signal: 1/sqrt(2), 0, 1/sqrt(2), 0, 0, 0, 0, 0 */
    cplx state[N_STATES] = {0};
    state[0] = 1.0 / sqrt(2.0);
    state[4] = 1.0 / sqrt(2.0);

    printf("Input state:\n");
    for (int i = 0; i < N_STATES; i++)
        printf("  |%d>: %.4f + %.4fi\n", i, creal(state[i]), cimag(state[i]));

    qft(state, N_QUBITS);

    printf("\nAfter QFT (probabilities):\n");
    for (int i = 0; i < N_STATES; i++) {
        double prob = creal(state[i]) * creal(state[i]) +
                      cimag(state[i]) * cimag(state[i]);
        if (prob > 0.01)
            printf("  |%d>: %.4f\n", i, prob);
    }
    /* Expected peaks: |0>, |2>  (multiples of N/period = 8/4 = 2) */
    return 0;
}
/**
 * C++ template state-vector simulator with Grover's search.
 * Compile: g++ -O2 -std=c++17 grover.cpp -o grover
 */
#include <iostream>
#include <vector>
#include <complex>
#include <cmath>
#include <algorithm>
#include <numeric>
#include <cassert>

using Complex = std::complex<double>;
using State   = std::vector<Complex>;

constexpr double PI = M_PI;

// ----------------------------------------------------------------
// State-vector helpers
// ----------------------------------------------------------------

State uniform_superposition(int n) {
    int N = 1 << n;
    return State(N, 1.0 / std::sqrt(N));
}

double prob(const State& sv, int idx) {
    return std::norm(sv[idx]);  // |amplitude|^2
}

int measure(const State& sv) {
    // Deterministic argmax (no randomness for demo)
    return std::max_element(sv.begin(), sv.end(),
        [](const Complex& a, const Complex& b){ return std::norm(a) < std::norm(b); })
        - sv.begin();
}

// ----------------------------------------------------------------
// Grover oracle: negate the amplitude of 'target'
// ----------------------------------------------------------------

void grover_oracle(State& sv, int target) {
    sv[target] = -sv[target];
}

// ----------------------------------------------------------------
// Grover diffusion: reflect about the mean
// ----------------------------------------------------------------

void grover_diffusion(State& sv) {
    int N = sv.size();
    Complex mean = std::accumulate(sv.begin(), sv.end(), Complex{0, 0})
                   / static_cast<double>(N);
    for (auto& a : sv)
        a = 2.0 * mean - a;
}

// ----------------------------------------------------------------
// Full Grover search
// ----------------------------------------------------------------

int grover_search(int target, int n) {
    int N = 1 << n;
    State sv = uniform_superposition(n);
    int k_opt = static_cast<int>(std::floor(PI * std::sqrt(N) / 4.0));

    std::cout << "Searching N=" << N << " items for target=" << target
              << " (" << k_opt << " iterations)\n";

    for (int k = 0; k < k_opt; k++) {
        grover_oracle(sv, target);
        grover_diffusion(sv);
        std::cout << "  iter " << k+1
                  << "  P(target) = " << prob(sv, target) << "\n";
    }
    return measure(sv);
}

// ----------------------------------------------------------------
// QFT
// ----------------------------------------------------------------

void apply_H_gate(State& sv, int qubit, int n) {
    int stride = 1 << qubit;
    int size   = 1 << n;
    const double s = 1.0 / std::sqrt(2.0);
    for (int i = 0; i < size; i++) {
        if ((i & stride) == 0) {
            Complex a = sv[i], b = sv[i | stride];
            sv[i]         = s * (a + b);
            sv[i | stride] = s * (a - b);
        }
    }
}

void apply_CR_gate(State& sv, int ctrl, int tgt, int k, int n) {
    Complex phase = std::exp(Complex{0, 2.0 * PI / (1 << k)});
    int size = 1 << n;
    for (int i = 0; i < size; i++)
        if ((i >> ctrl & 1) && (i >> tgt & 1))
            sv[i] *= phase;
}

void qft(State& sv, int n) {
    for (int q = n - 1; q >= 0; q--) {
        apply_H_gate(sv, q, n);
        for (int c = q - 1; c >= 0; c--)
            apply_CR_gate(sv, c, q, q - c + 1, n);
    }
    // Bit reversal
    for (int i = 0; i < n / 2; i++) {
        int bit_a = 1 << i, bit_b = 1 << (n-1-i);
        for (int s = 0; s < (1 << n); s++)
            if ((s & bit_a) != (s & bit_b ? bit_a : 0))
                ;  // swap handled below
        // Proper bit-reversal permutation
        int size = 1 << n;
        for (int s = 0; s < size; s++) {
            int t = s ^ ((s ^ (s << (n-1-2*i))) & (bit_b | bit_a));
            if (t > s) std::swap(sv[s], sv[t]);
        }
    }
}

int main() {
    // Grover demo
    int found = grover_search(7, 4);  // target=7 in N=16
    std::cout << "Found: " << found << "\n\n";

    // QFT demo: period-4 signal in N=8
    int n = 3, N = 8, period = 4;
    State sv(N, 0);
    for (int k = 0; k < N / period; k++)
        sv[k * period] = 1.0 / std::sqrt(N / period);

    std::cout << "QFT of period-" << period << " signal (N=" << N << "):\n";
    qft(sv, n);
    for (int i = 0; i < N; i++) {
        double p = std::norm(sv[i]);
        if (p > 0.01)
            std::cout << "  |" << i << "> prob=" << p << "\n";
    }
    return 0;
}
/**
 * Java: Grover's search and Deutsch-Jozsa state-vector simulation.
 * Compile: javac QuantumSim.java && java QuantumSim
 */
public class QuantumSim {

    // Complex number arithmetic
    static double[] add(double[] a, double[] b) {
        return new double[]{a[0]+b[0], a[1]+b[1]};
    }
    static double[] scale(double s, double[] a) {
        return new double[]{s*a[0], s*a[1]};
    }
    static double[] negate(double[] a) {
        return new double[]{-a[0], -a[1]};
    }
    static double abs2(double[] a) { return a[0]*a[0] + a[1]*a[1]; }

    // ----------------------------------------------------------------
    // Grover's search
    // ----------------------------------------------------------------
    static double[][] groverSearch(int target, int n) {
        int N = 1 << n;
        double[][] sv = new double[N][2];
        double amp = 1.0 / Math.sqrt(N);
        for (int i = 0; i < N; i++) sv[i][0] = amp;

        int kOpt = (int)(Math.PI * Math.sqrt(N) / 4.0);
        System.out.printf("Grover: N=%d, target=%d, iterations=%d%n", N, target, kOpt);

        for (int k = 0; k < kOpt; k++) {
            // Oracle: negate target
            sv[target] = negate(sv[target]);

            // Diffusion: reflect about mean
            double meanRe = 0, meanIm = 0;
            for (double[] a : sv) { meanRe += a[0]; meanIm += a[1]; }
            meanRe /= N; meanIm /= N;
            for (int i = 0; i < N; i++) {
                sv[i][0] = 2*meanRe - sv[i][0];
                sv[i][1] = 2*meanIm - sv[i][1];
            }
            System.out.printf("  iter %d  P(target)=%.4f%n", k+1, abs2(sv[target]));
        }
        return sv;
    }

    // ----------------------------------------------------------------
    // Deutsch-Jozsa
    // ----------------------------------------------------------------
    static String deutschJozsa(int[] f) {
        // f is a truth table of size N = 2^n
        int N = f.length;
        int n = 31 - Integer.numberOfLeadingZeros(N);  // log2(N)

        // State: n+1 qubits, size = 2*N
        double[][] sv = new double[2 * N][2];
        sv[1][0] = 1.0;  // |0...0>|1>

        // Apply H to all n+1 qubits
        for (int q = 0; q <= n; q++) applyH(sv, q, n + 1);

        // Oracle (phase kickback)
        for (int x = 0; x < N; x++) {
            if (f[x] == 1) {
                // Swap |x,0> and |x,1> (XOR into ancilla)
                double[] tmp = sv[2*x].clone();
                sv[2*x]   = sv[2*x+1];
                sv[2*x+1] = tmp;
            }
        }

        // Apply H to first n qubits
        for (int q = 0; q < n; q++) applyH(sv, q + 1, n + 1);
        // Note: bit ordering — qubit 0 is ancilla (index bit 0)

        // Measure: probability of first n qubits being all-zero
        double probAllZero = abs2(sv[0]) + abs2(sv[1]);
        return probAllZero > 0.99 ? "constant" : "balanced";
    }

    static void applyH(double[][] sv, int qubit, int totalQubits) {
        int stride = 1 << qubit;
        int size   = 1 << totalQubits;
        double s   = 1.0 / Math.sqrt(2.0);
        for (int i = 0; i < size; i++) {
            if ((i & stride) == 0) {
                double[] a = sv[i].clone();
                double[] b = sv[i | stride].clone();
                sv[i]         = scale(s, add(a, b));
                sv[i | stride] = scale(s, add(a, negate(b)));
            }
        }
    }

    public static void main(String[] args) {
        // Grover demo
        int n = 4, target = 11;
        double[][] sv = groverSearch(target, n);
        int found = 0;
        for (int i = 1; i < sv.length; i++)
            if (abs2(sv[i]) > abs2(sv[found])) found = i;
        System.out.printf("Measured: %d (expected %d)%n%n", found, target);

        // Deutsch-Jozsa demo
        int[] fConst = new int[8];  // constant-0
        int[] fBal   = {0,1,0,1,0,1,0,1};  // balanced
        System.out.println("Constant-0 function: " + deutschJozsa(fConst));
        System.out.println("Balanced function:   " + deutschJozsa(fBal));
    }
}
// Quantum circuit simulation in Go: QFT and Grover's search.
// Run: go run quantum_sim.go
package main

import (
	"fmt"
	"math"
	"math/cmplx"
)

// ----------------------------------------------------------------
// State vector type
// ----------------------------------------------------------------

type State []complex128

func uniformSuperposition(n int) State {
	N := 1 << n
	sv := make(State, N)
	amp := complex(1.0/math.Sqrt(float64(N)), 0)
	for i := range sv {
		sv[i] = amp
	}
	return sv
}

func prob(sv State, idx int) float64 {
	return math.Pow(cmplx.Abs(sv[idx]), 2)
}

func argmax(sv State) int {
	best := 0
	for i := 1; i < len(sv); i++ {
		if prob(sv, i) > prob(sv, best) {
			best = i
		}
	}
	return best
}

// ----------------------------------------------------------------
// Gate applications
// ----------------------------------------------------------------

func applyH(sv State, qubit, nQubits int) {
	stride := 1 << qubit
	size := 1 << nQubits
	s := complex(1.0/math.Sqrt(2), 0)
	for i := 0; i < size; i++ {
		if i&stride == 0 {
			a, b := sv[i], sv[i|stride]
			sv[i] = s * (a + b)
			sv[i|stride] = s * (a - b)
		}
	}
}

func applyCR(sv State, ctrl, tgt, k, nQubits int) {
	phase := cmplx.Exp(complex(0, 2*math.Pi/float64(1<<k)))
	size := 1 << nQubits
	for i := 0; i < size; i++ {
		if (i>>ctrl)&1 == 1 && (i>>tgt)&1 == 1 {
			sv[i] *= phase
		}
	}
}

func swapQubits(sv State, a, b, nQubits int) {
	size := 1 << nQubits
	for i := 0; i < size; i++ {
		bitA := (i >> a) & 1
		bitB := (i >> b) & 1
		if bitA != bitB {
			j := i ^ (1 << a) ^ (1 << b)
			if i < j {
				sv[i], sv[j] = sv[j], sv[i]
			}
		}
	}
}

// ----------------------------------------------------------------
// QFT
// ----------------------------------------------------------------

func qft(sv State, n int) {
	for q := n - 1; q >= 0; q-- {
		applyH(sv, q, n)
		for c := q - 1; c >= 0; c-- {
			applyCR(sv, c, q, q-c+1, n)
		}
	}
	// Bit reversal
	for i := 0; i < n/2; i++ {
		swapQubits(sv, i, n-1-i, n)
	}
}

// ----------------------------------------------------------------
// Grover's search
// ----------------------------------------------------------------

func groverOracle(sv State, target int) {
	sv[target] = -sv[target]
}

func groverDiffusion(sv State) {
	N := len(sv)
	var mean complex128
	for _, a := range sv {
		mean += a
	}
	mean /= complex(float64(N), 0)
	for i := range sv {
		sv[i] = 2*mean - sv[i]
	}
}

func groverSearch(target, n int) int {
	N := 1 << n
	sv := uniformSuperposition(n)
	kOpt := int(math.Pi * math.Sqrt(float64(N)) / 4.0)

	fmt.Printf("Grover: N=%d, target=%d, kOpt=%d\n", N, target, kOpt)
	for k := 0; k < kOpt; k++ {
		groverOracle(sv, target)
		groverDiffusion(sv)
		fmt.Printf("  iter %d  P(target)=%.4f\n", k+1, prob(sv, target))
	}
	return argmax(sv)
}

func main() {
	// ---- Grover demo ----
	found := groverSearch(13, 4)
	fmt.Printf("Measured: %d\n\n", found)

	// ---- QFT demo: period-4 signal in N=8 ----
	n, period := 3, 4
	N := 1 << n
	sv := make(State, N)
	for k := 0; k < N/period; k++ {
		sv[k*period] = complex(1.0/math.Sqrt(float64(N/period)), 0)
	}
	fmt.Printf("Input (period-%d signal, N=%d):\n", period, N)
	for i, a := range sv {
		if cmplx.Abs(a) > 0.01 {
			fmt.Printf("  |%d>: %.4f+%.4fi\n", i, real(a), imag(a))
		}
	}
	qft(sv, n)
	fmt.Println("After QFT (significant probabilities):")
	for i, a := range sv {
		p := prob(State(sv), i)
		_ = a
		if p > 0.01 {
			fmt.Printf("  |%d>: %.4f\n", i, p)
		}
	}
	// Expected peaks at |0> and |2> (multiples of N/period = 2)
}

13. Summary

ONE-PARAGRAPH SUMMARY

Quantum algorithms achieve speedups by engineering interference: wrong answers cancel, right answers constructively reinforce. Deutsch-Jozsa and Bernstein-Vazirani demonstrate this with a single query, though they solve contrived problems. Simon's algorithm — the first proven exponential separation — inspired Shor's factoring algorithm, which uses the Quantum Fourier Transform to extract the period of a modular exponential function, reducing integer factoring to a polynomial-time problem and threatening RSA. Grover's algorithm achieves an optimal quadratic speedup for unstructured search via amplitude amplification, a geometric rotation in a 2D subspace. Quantum Phase Estimation ties these ideas together into a general-purpose eigenvalue-extraction engine. Near-term variational algorithms (VQE, QAOA) trade exact speedup guarantees for practical deployability on noisy hardware. The unifying theme: all quantum speedups are instances of structured interference, and the art of quantum algorithm design is finding that structure in your problem.

Where to go next