Open this lesson in your favourite AI. It'll walk you through the why, explain the demo, and quiz you on the try-it list.
Interference is the part of the quantum-computing story that classical probabilistic computing genuinely cannot reproduce. Probabilities are non-negative; amplitudes are complex, and complex numbers can cancel. Every quantum speedup — Shor, Grover, Deutsch-Jozsa — is at heart an arrangement of unitaries that drives the amplitude on the wrong answers towards zero (destructive interference) and the amplitude on the right answer towards one (constructive interference) before measurement collapses the state. If you remember only one mechanical fact from this entire course, make it this one: a quantum algorithm is choreographed amplitude addition. Choosing post-quantum primitives means choosing problems where no such choreography is known.
Apply a Hadamard to to get . Apply a Hadamard again and you don't get a uniform superposition over four 'paths' — you get back exactly. The amplitude on from the path is , and from is . They sum to zero. The amplitude on adds to . This is the simplest possible non-trivial interference.
Use these three in order. Each builds on the one before.
In one paragraph, explain the difference between adding probabilities and adding amplitudes. Why can amplitudes cancel but probabilities cannot?
Walk me step by step through the calculation $HH|0\rangle = |0\rangle$. Track the amplitude on $|0\rangle$ via the two paths and the amplitude on $|1\rangle$ via the two paths separately. Where exactly does cancellation happen, and why does it not happen on $|0\rangle$?
Shor's algorithm uses the quantum Fourier transform to extract the period of $a^x \bmod N$ from a uniform superposition over $x$. Sketch how interference selects out the multiples of $N/r$ in the output register. Why is there no analogous classical 'lucky cancellation' that lets a probabilistic computer factor in polynomial time?
// main.go
// run: go run main.go
package main
import (
"fmt"
"math"
"math/cmplx"
)
func matVec(m [2][2]complex128, v [2]complex128) [2]complex128 {
return [2]complex128{
m[0][0]*v[0] + m[0][1]*v[1],
m[1][0]*v[0] + m[1][1]*v[1],
}
}
func roundC(c complex128, prec float64) complex128 {
scale := math.Pow(10, prec)
return complex(
math.Round(real(c)*scale)/scale,
math.Round(imag(c)*scale)/scale,
)
}
func roundVec(v [2]complex128) [2]complex128 {
return [2]complex128{roundC(v[0], 4), roundC(v[1], 4)}
}
func main() {
s := 1 / math.Sqrt(2)
H := [2][2]complex128{
{complex(s, 0), complex(s, 0)},
{complex(s, 0), complex(-s, 0)},
}
ket0 := [2]complex128{1, 0}
step1 := matVec(H, ket0)
step2 := matVec(H, step1)
r1 := roundVec(step1)
r2 := roundVec(step2)
fmt.Printf("After H : [%v %v]\n", r1[0], r1[1]) // uniform superposition
fmt.Printf("After HH: [%v %v]\n", r2[0], r2[1]) // back to |0> exactly
// Track amplitudes on |1> from the two paths explicitly:
// path 0->0->1: amplitude = (1/sqrt2) * (1/sqrt2) = 1/2
// path 0->1->1: amplitude = (1/sqrt2) * (-1/sqrt2) = -1/2
// sum = 0 -- destructive interference
_ = cmplx.Abs // imported for completeness
p0FromPath1 := s * s
p1FromPath2 := s * (-s)
fmt.Printf("amp on |1> via path 1: %v\n", p0FromPath1)
fmt.Printf("amp on |1> via path 2: %v\n", p1FromPath2)
fmt.Printf("sum (destructive): %v\n", p0FromPath1+p1FromPath2)
}go run main.go