Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introduction

Welcome to Quantum Computing

You’ve probably heard that quantum computers are magical machines that solve impossible problems. Maybe you’ve seen headlines about “quantum supremacy” or watched videos of people getting excited about qubits.

Here’s what those headlines don’t tell you: quantum computing is also hard. Not hard like building a spaceship, but hard like learning a new way of thinking. The mathematics is real. The intuition takes time.

This book is for programmers who want to build that intuition from scratch.

What This Book Is

This is a tutorial written for curious programmers who are comfortable with Python and want to understand quantum computing through hands-on practice with Qiskit.

Think of it like:

  • The Rust Book — one concept at a time, with working examples
  • A language learning app — daily practice with QCoder problems
  • A lab manual — not a novel to read passively

The official Qiskit documentation is excellent. It is also overwhelming. This book strips away the noise and builds up your understanding step by step, so that when you do read the official docs, everything clicks.

What You’ll Learn

By the end of this book, you will be able to:

SkillWhat It Means
Read circuitsUnderstand what a circuit diagram means before running anything
Write circuitsBuild circuits that do exactly what you intend
Debug with statevectorsInspect exact quantum states to find bugs
Design patternsRecognize state preparation, oracles, and reflections
Solve QCoder problemsWork through the QCoder problem sets with confidence
Explore QMLRead quantum machine learning code without getting lost

This is a realistic goal. It is also a strong one.

The Teaching Philosophy

This book is built on a few core beliefs about how people actually learn difficult technical subjects.

Circuits Before Hardware

You will first understand what a circuit does mathematically. Hardware noise, transpilation, calibration, and backend management are real concerns—but they are distractions if you still confuse state preparation with measurement.

We will focus entirely on circuit behavior using exact simulation.

Exact Before Noisy

Before asking “what outcomes appear?”, we ask “what state did I create?”

This means we use Statevector and StatevectorSampler extensively. Not because sampling isn’t important, but because the exact state reveals the why behind the numbers.

Patterns Before Prestige Algorithms

It is better to deeply understand:

  • how to prepare basis states
  • how superposition works
  • what entanglement means in practice
  • how oracles and reflections work

…than to vaguely memorize Grover’s algorithm or the Quantum Fourier Transform. Those bigger topics become obviously useful once the patterns are solid.

Practice Before Theory

Every chapter includes:

  1. A mental model to understand conceptually
  2. Working Qiskit code to try immediately
  3. Exercises to build muscle memory
  4. QCoder problems to test your understanding

Reading without practicing is like watching swimming videos and expecting to know how to swim.

Why Qiskit?

Qiskit is IBM’s open-source quantum computing framework. It is:

  • Well-documented — official tutorials are extensive
  • Actively maintained — regular updates and improvements
  • Beginner-friendly APIs — the circuit interface is clean
  • Industry-standard — used in real quantum research

If you learn Qiskit well, you can contribute to real quantum computing projects.

Official Resources: The official Qiskit documentation is at docs.quantum.ibm.com. IBM also offers structured learning courses including Basics of Quantum Information and Fundamentals of Quantum Algorithms. This tutorial complements those resources by providing a circuit-first, problem-driven approach.

Why QCoder?

QCoder is a Japanese quantum programming contest platform that presents quantum problems as concrete circuit challenges. Each problem tells you exactly what the input and output should be, and you write the circuit to make it happen.

Why is this valuable?

Because quantum computing has a reputation for feeling “mysterious.” QCoder forces you to turn vague understanding into circuits that actually work. If you can solve QCoder problems, you understand the material.

What This Book Is NOT

This book does not promise to:

  • Make you a quantum algorithm researcher overnight
  • Replace the official Qiskit documentation
  • Cover every gate, every algorithm, every hardware consideration
  • Teach quantum physics in depth

What it does do is bring you to the point where:

  • Official tutorials feel approachable
  • QCoder problems look structured instead of mysterious
  • You can design and test your own circuits confidently

Before You Begin

Make sure you:

  1. Are comfortable with Python (functions, classes, loops)
  2. Have basic linear algebra (vectors, matrices, multiplication) — or are willing to learn
  3. Have set up your environment (see Setup)
  4. Are ready to practice, not just read

Quantum computing rewards patience. Take your time with the early chapters—they are the foundation for everything that comes after.

Let’s begin.


Next: How To Use This Book

How To Use This Book

This book works best when you treat it like a lab manual, not a novel.

That means: reading is only half the battle. The other half is typing code, running it, breaking it, and fixing it.

The Chapter Pattern

Every chapter follows this rhythm:

1. Learn one mental model
   ↓
2. Type a short Qiskit example
   ↓
3. Predict the result before running
   ↓
4. Run and compare
   ↓
5. Solve checkpoint exercises
   ↓
6. Try a QCoder problem

Skip the steps, and you’ll finish the book without the skills to use it.

Four Layers Per Chapter

Most chapters have four layers:

1. The Mental Model

This is the conceptual understanding. Examples:

  • “A Hadamard gate is a basis change, not just a gate symbol”
  • “cx(control, target) means ‘flip the target if the control is 1’”
  • “A phase can be invisible in immediate counts but still matter later”

If you skip this layer, Qiskit becomes memorization instead of understanding.

2. The Qiskit Move

This is the code pattern you’ll use repeatedly:

  • QuantumCircuit to define circuits
  • Statevector to inspect exact states
  • StatevectorSampler to sample measurement outcomes
  • compose, inverse, control to assemble larger circuits

These tools are reused until they feel routine.

3. The Worked Pattern

This is the circuit design skill:

  • preparing a basis state
  • creating superposition
  • building an oracle
  • implementing a reflection

Once you can name a pattern, many QCoder problems stop feeling unique.

4. The Exercises

Exercises are where learning becomes skill. Each chapter includes:

  • Checkpoint exercises — targeted practice with the chapter’s concept
  • QCoder problems — linked challenges from QCoder contests

The Reading Rhythm

For each chapter, use this rhythm:

  1. Read once without writing code (get the big picture)
  2. Type every code block yourself (muscle memory)
  3. Predict the result before running (this is critical)
  4. Run and compare (did you get what you expected?)
  5. Change one thing and predict again (explore)
  6. Solve at least one QCoder problem before moving on

That last step matters. If you only read, the material will feel clearer than it really is. QCoder problems expose gaps in understanding that passive reading hides.

The Statevector-First Workflow

Here is the workflow that will save you hours of frustration:

  1. Write the circuit
  2. Inspect Statevector()
  3. Predict what you expect
  4. Compare actual vs expected
  5. If wrong: debug using the checklist below
  6. Only then: measure and sample

This order matters because:

  • The statevector tells you exactly what the circuit does
  • Sampling only shows statistical outcomes
  • Many bugs are invisible in counts but obvious in amplitudes

The Debugging Checklist

When a circuit doesn’t behave as expected, check these in order:

#CheckWhat to look for
1Qubit orderingIs |10> interpreted as you expect?
2Amplitudes vs countsAre you looking at the right thing?
3Measurement timingDid you measure too early?
4Inverse/uncomputeDid you forget to undo temporary operations?
5Global vs relative phase-1 on one branch vs -1 on all branches
6Control vs targetIs your CNOT control-target correct?

This checklist resolves a surprising number of “quantum mysteries.”

QCoder: Your Practice Partner

QCoder problems are structured challenges that test your circuit-building skills. Each problem:

  • Provides a clear specification
  • Tests your circuit against known inputs
  • Gives immediate feedback on correctness

How to use them:

  1. Finish a chapter’s exercises
  2. Pick a linked QCoder problem (see the QCoder Problem Catalog)
  3. Read the problem description carefully
  4. Design your circuit on paper first
  5. Implement in Qiskit
  6. Submit and iterate

QCoder is unforgiving in the best way: either your circuit works, or it doesn’t.

Current QCoder Problems: 79 problems across 6 contests, organized by topic in the QCoder Problem Catalog.

When You Get Stuck

Getting stuck is part of the process. Here’s what to do:

On conceptual confusion:

  • Reread the mental model section
  • Draw the circuit by hand
  • Google with the phrase “qiskit [concept] example”

On code errors:

  • Copy the exact error message
  • Check Qiskit version (pip show qiskit)
  • Try the book’s exact code first

On circuit behavior:

  • Add print(Statevector(qc)) to see what’s happening
  • Test on the simplest possible input
  • Check the debugging checklist

On QCoder problems:

  • Read the problem statement again (twice)
  • Verify you understand the input/output format
  • Check if there’s a hint in the problem comments
  • Try the problem with fewer qubits first

How This Book Is Organized

The book has six parts:

PartTopicApprox. Difficulty
IHello, Quantum WorldGetting started
IISingle-Qubit ToolkitFoundational
IIITwo-Qubit TerritoryFoundational
IVCircuit PatternsIntermediate
VAlgorithmsIntermediate/Advanced
VIPutting It TogetherApplied

Don’t skip parts. Each one builds on the previous.

A Note on Pacing

This book is designed to be worked through over several weeks, not read in a weekend.

If you’re doing one chapter per day:

  • Days 1-5: Part I (Foundations)
  • Days 6-12: Part II (Single-Qubit)
  • Days 13-18: Part III (Two-Qubit)
  • Days 19-25: Part IV (Patterns)
  • Days 26-32: Part V (Algorithms)
  • Days 33-35: Part VI (Application)

Take breaks. Quantum intuition takes time to build.


Next: Setup

Setup

Let’s get your environment ready. This chapter walks you through installing everything you need to work through this book.

What You’re Installing

You need three things:

ToolPurposeVersion
PythonProgramming language3.13+
uvPackage manager and environment toolLatest
mdbookBook build toolLatest
QiskitQuantum computing frameworkLatest

Step 1: Install Python

Check if you have Python installed:

python --version

If you see Python 3.13 or higher, you’re good. Otherwise, install Python from python.org or use your system package manager.

Step 2: Install uv

uv is a fast Python package manager. It replaces pip for this project.

On macOS/Linux:

curl -LsSf https://astral.sh/uv/install.sh | sh

On Windows:

powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"

With pip (alternative):

pip install uv

Verify the installation:

uv --version

Step 3: Install mdbook

mdbook builds this tutorial into a nice-looking website.

On macOS (with Homebrew):

brew install mdbook

On Linux (with cargo):

cargo install mdbook

From binary (any OS):

Download from the releases page and add to your PATH.

Verify the installation:

mdbook --version

Step 4: Install Project Dependencies

This project uses uv to manage Python dependencies. The dependencies are defined in pyproject.toml.

cd /path/to/qiskit-learn
uv sync

This creates a virtual environment and installs:

  • qiskit — the main quantum computing framework
  • Other dependencies as needed

Important: Use uv sync, not pip install. The project is configured to use uv.

Step 5: Run Python with uv

Always run Python through uv to use the project environment:

# Run a single script
uv run python my_script.py

# Open an interactive shell
uv run python

Adding Packages

If you need to add packages, use uv add:

# Add a new package
uv add qiskit-machine-learning

# Add a development package
uv add --dev pytest

Never use pip install directly—it bypasses the project’s dependency management.

The Smoke Test

Let’s verify everything works. Create a file called test_setup.py:

from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector

# Create a simple circuit: one qubit, one Hadamard gate
qc = QuantumCircuit(1)
qc.h(0)

# Print the circuit diagram
print("Circuit:")
print(qc)

# Print the quantum state
print("\nStatevector:")
state = Statevector(qc)
print(state)

Run it:

uv run python test_setup.py

You should see:

Circuit:
    ┌───┐
 q: ┤ H ├
    └───┘

Statevector:
Statevector([0.70710678+0.j, 0.70710678+0.j],
            dims=(2,))

If you see something like this, your environment is ready!

Building the Book

To build the book into a static website:

mdbook build

To serve it locally with live reload:

mdbook serve --open

The --open flag automatically opens the book in your browser.

Project Structure

Here’s what the project looks like:

qiskit-learn/
├── book.toml          # mdbook configuration
├── pyproject.toml     # Python dependencies
├── uv.lock            # Locked dependency versions
├── book/              # Built book output
│   └── index.html
└── src/               # Book source files
    ├── SUMMARY.md     # Table of contents
    ├── introduction.md
    ├── setup.md
    └── ...

Why Statevector-First?

You might wonder why this book emphasizes Statevector over measurement.

Here’s why:

  1. Exact answers — Statevector gives you the exact quantum state, not statistical samples
  2. Faster debugging — You can see exactly what’s happening, not just averages
  3. Phase visibility — Relative phase is invisible in counts but visible in statevectors
  4. Foundation for understanding — Measurement statistics follow from the statevector

The workflow we’ll use:

  1. Write circuit
  2. Inspect Statevector()
  3. Understand the state
  4. Then: measure and sample

This order will save you hours of confusion.

Optional: Jupyter Notebooks

If you prefer Jupyter notebooks for experimentation:

uv add jupyter
uv run jupyter lab

This lets you run code interactively and keep notes alongside your circuits.

Note: The official Qiskit documentation includes many Jupyter notebooks at docs.quantum.ibm.com. After completing this tutorial, those notebooks become much more accessible.

Optional: Qiskit Machine Learning

If you want to explore quantum machine learning in the later chapters:

uv add qiskit-machine-learning

This is optional—the core chapters don’t need it.

Practice Utilities

Here’s a helper module you can save as practice.py for interactive verification:

"""Practice utilities for quantum computing exercises."""
from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector, Operator
from qiskit.visualization import plot_bloch_multivector
from qiskit.primitives import StatevectorSampler

def verify_state(qc: QuantumCircuit, expected_state: Statevector, verbose: bool = True) -> bool:
    """Verify that a circuit produces the expected state."""
    actual = Statevector(qc)
    match = actual.equiv(expected_state)
    if verbose:
        print(f"Circuit:\n{qc}")
        print(f"Expected: {expected_state}")
        print(f"Actual:   {actual}")
        print(f"Match: {match}")
    return match

def visualize_and_print(qc: QuantumCircuit):
    """Display Bloch sphere and print state for a circuit."""
    state = Statevector(qc)
    print(f"Circuit:\n{qc}\nState: {state}")
    display(plot_bloch_multivector(state))

def compare_gates(gate1: QuantumCircuit, gate2: QuantumCircuit, names: tuple = ("Gate1", "Gate2")):
    """Compare two gate sequences to check equivalence."""
    op1, op2 = Operator(gate1), Operator(gate2)
    equal = op1.equiv(op2)
    print(f"{names[0]}:")
    print(gate1)
    print(f"{names[1]}:")
    print(gate2)
    print(f"Equivalent: {equal}")
    return equal

def sample_and_print(qc: QuantumCircuit, shots: int = 1000) -> dict:
    """Measure a circuit and print the sample results."""
    qc_copy = qc.copy()
    qc_copy.measure_all()
    sampler = StatevectorSampler()
    result = sampler.run([qc_copy], shots=shots).result()
    counts = result[0].data.meas.get_counts()
    print(f"Circuit:\n{qc}\nMeasurement ({shots} shots): {counts}")
    return counts

# Example usage:
# from practice import verify_state, visualize_and_print, compare_gates
# from qiskit import QuantumCircuit
# from qiskit.quantum_info import Statevector

# verify_state(QuantumCircuit.from_qasm_str("OPENQASM 2.0; include 'qelib1.inc'; qreg q[1]; h q[0];"), 
#               Statevector([1/2**0.5, 1/2**0.5]))

Save this file in your project directory and import it as needed during exercises.

Tip: For interactive exploration, run uv run python to get a REPL where you can import these utilities and experiment with circuits.

Troubleshooting

“uv: command not found”

Close and reopen your terminal after installing uv. If it still doesn’t work, check that uv is in your PATH.

“Python version not supported”

This book requires Python 3.13 or newer. Update Python and try again.

“qiskit import fails”

Make sure you’re using uv run python, not just python. This ensures you’re using the project environment with qiskit installed.

“mdbook build fails”

Check that mdbook is installed and in your PATH:

mdbook --version

Summary

At the end of this setup, you should have:

  • Python 3.13+
  • uv installed and working
  • mdbook installed and working
  • Project dependencies installed (uv sync)
  • Smoke test passing

If any of these aren’t working, go back and check that step. Don’t skip ahead until the smoke test passes—everything else depends on a working environment.


Next: Your First Circuit

QCoder Problem Catalog

This catalog lists all problems from QCoder Programming Contests (QPC) 001-006, organized by topic. Use this as a reference for practice problems.

Total: 79 Problems Across 6 Contests

ContestProblemsFocus Areas
QPC00111State Generation, Oracles
QPC00214Phase, Arithmetic, QFT
QPC00316One-hot States, Reflections, Grover
QPC00417Zeta/Moebius Transform, Shor’s Algorithm
QPC0059Controlled Gates, Fourier Basis
QPC00612Error Correction, T-Gates

By Topic

State Preparation

Basic States (QPC001)

Phase States (QPC002)

One-Hot States (QPC003)

Tensor Product States (QPC003)

Uniform Amplitude Superposition (QPC001)


Oracles

Basic Oracles (QPC001)

Phase Shift Oracles (QPC002)

Shift Oracles (QPC004, QPC005)

Polynomial Oracles (QPC006)


Multi-Qubit Operations

SWAP and Qubit Manipulation (QPC002, QPC004)

Controlled Gates (QPC005, QPC006)


Quantum Fourier Transform (QFT)


Arithmetic Circuits

Modular Arithmetic (QPC004)


Reflections and Grover’s Algorithm

Reflections (QPC003, QPC004)

Grover’s Algorithm (QPC003)

Bit-Phase Conversion (QPC003)


Gate Manipulation

Notation (QPC003)

Uniform Superposition (QPC004)


Error Correction (QPC006)


Challenge Problems


Problem Difficulty Guide

Beginner (100-200 pts)

Perfect for starting out:

  • State generation (|0⟩, |1⟩, |+⟩, |−⟩)
  • Basic superposition with H gate
  • Simple oracles (Copy, XOR)
  • Bra-Ket notation

Intermediate (200-400 pts)

Building proficiency:

  • Multi-qubit state preparation
  • Phase manipulation
  • Controlled gates
  • Basic arithmetic
  • Shift oracles

Advanced (400-600 pts)

For deeper understanding:

  • Uniform amplitude superposition
  • QFT implementation
  • Modular arithmetic
  • Grover’s algorithm
  • Zeta/Moebius transforms
  • Error correction

Expert (600+ pts)

Challenge problems:

  • Hidden polynomial finding
  • Complex QFT applications
  • Advanced Shor’s algorithm components

Suggested Learning Path

Based on this tutorial’s structure:

ChapterRecommended Problems
First CircuitQPC001 A1, A2
Single-Qubit GatesQPC001 A3, A4, QPC003 A1
Hadamard & SuperpositionQPC001 A1, A2, QPC002 A1
PhaseQPC002 B1, QPC003 A1
Multi-Qubit BasicsQPC001 B1, B2
CNOT & EntanglementQPC001 A4, QPC003 A2, A3
Two-Qubit GatesQPC002 B3, QPC004 A2
OraclesQPC001 B3, B4
Grover’s AlgorithmQPC003 B6, B8
QFTQPC002 B4, QPC005 B1
ArithmeticQPC002 B5-B8, QPC004 B3-B5
Controlled GatesQPC005 A2, A3, QPC006 C1-C6
Error CorrectionQPC006 A1-A3

Official QCoder Resources

Your First Circuit

Let’s write your first quantum circuit. By the end of this chapter, you’ll have run code that creates superposition—the core quantum phenomenon that makes everything else interesting.

The Minimal Working Example

Type this code and run it:

from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector

# Create a quantum circuit with 1 qubit
qc = QuantumCircuit(1)

# Apply a Hadamard gate
qc.h(0)

# Print the circuit diagram
print("Circuit:")
print(qc)

# Get and print the quantum state
print("\nQuantum State:")
state = Statevector(qc)
print(state)

You should see output like this:

Circuit:
    ┌───┐
 q: ┤ H ├
    └───┘

Quantum State:
Statevector([0.70710678+0.j, 0.70710678+0.j],
            dims=(2,))

Let’s understand what this means.

Reading the Circuit Diagram

The circuit diagram shows:

  • One qubit, labeled q
  • One gate: H (Hadamard)
   ┌───┐
q: ┤ H ├
   └───┘

Reading the Statevector

The statevector [0.70710678+0.j, 0.70710678+0.j] represents:

\[|\psi\rangle = 0.707\ldots |0\rangle + 0.707\ldots |1\rangle\]

In cleaner notation:

\[|\psi\rangle = \frac{|0\rangle + |1\rangle}{\sqrt{2}}\]

This is called the plus state (or \(|+\rangle\)).

What Just Happened?

Here’s what you need to understand:

  1. Initial state: Every qubit starts as \(|0\rangle\)
  2. Hadamard gate: Transforms \(|0\rangle\) into a superposition of \(|0\rangle\) and \(|1\rangle\)
  3. Superposition: The qubit is both \(|0\rangle\) and \(|1\rangle\) at the same time (until measured)

The number 0.707... is \(\frac{1}{\sqrt{2}}\), which ensures the probabilities add up to 1.

A Circuit Is a Recipe

Keep this distinction clear:

ConceptDescriptionExample
CircuitThe sequence of gates to applyh(0)
StatevectorThe quantum state produced by the circuit\(\frac{
MeasurementThe process of getting a classical result"0" or "1"

A circuit is like a recipe. Apply the steps, and you get a state. Measure the state, and you get a result.

Two More Simple Examples

Example 1: Prepare \(|1\rangle\)

qc = QuantumCircuit(1)
qc.x(0)  # Apply the X (NOT) gate

state = Statevector(qc)
print(state)

Output:

Statevector([0.+0.j, 1.+0.j],
            dims=(2,))

This means: \(|\psi\rangle = 0|0\rangle + 1|1\rangle = |1\rangle\)

Example 2: Apply X then H

qc = QuantumCircuit(1)
qc.x(0)    # First: |0> -> |1>
qc.h(0)    # Then: |1> -> ?

state = Statevector(qc)
print(state)

What do you think the state will be?

Answer

The state is \(\frac{|0\rangle - |1\rangle}{\sqrt{2}}\), also known as the minus state or \(|-\rangle\).

Statevector([ 0.70710678+0.j, -0.70710678+0.j],
            dims=(2,))

The minus sign on the \(|1\rangle\) coefficient means this state behaves differently from \(|+\rangle\) even though they look similar when measured.

Predict Before You Run

This habit is crucial for learning quantum computing. Before running any code:

  1. Ask yourself: “What state should this circuit produce?”
  2. Run the code
  3. Compare your prediction to the actual result

This “predict, then verify” cycle builds intuition faster than anything else.

The Importance of Inspection

You might wonder: why not just measure and see the result?

Because measurement destroys quantum information. When you measure a qubit in superposition, it “collapses” to either \(|0\rangle\) or \(|1\rangle\) randomly. You lose access to the superposition.

By inspecting the statevector before measurement, you can see the full quantum state—including phases and amplitudes that measurement can’t reveal.

Your Turn

Try these exercises:

Exercise 1

Create a circuit that produces the state \(|0\rangle\) (the initial state). Verify with Statevector.

Exercise 2

Create a circuit that produces the state \(|1\rangle\).

Exercise 3

Predict what h(0) followed by x(0) produces. Then verify with code.

Exercise 4

Create two different circuits that both produce the state \(\frac{|0\rangle + |1\rangle}{\sqrt{2}}\).

Summary

In this chapter, you:

  • Created your first quantum circuit
  • Applied a Hadamard gate to create superposition
  • Inspected the statevector to see the exact quantum state
  • Learned the difference between a circuit and its resulting state
  • Practiced predicting before running

The pattern you’ll use throughout this book:

  1. Write a circuit
  2. Inspect the state
  3. Predict what measurement will show
  4. Measure to confirm

Try These On QCoder


Next: What Is a Qubit?

What Is a Qubit?

Before writing more circuits, let’s build a mental model for what a qubit is. This chapter is conceptual—it won’t teach you new code, but it will help everything else make sense.

The Classical Bit

You already know about classical bits. A bit is:

  • Either 0 or 1
  • The fundamental unit of classical computing
  • Readable, writable, copyable
Classical Bit:
┌─────────┐
│    0    │  ← One of two states
└─────────┘

The Quantum Bit (Qubit)

A qubit is a quantum system with two basis states, but it can also exist in superpositions of those states.

The basis states are labeled \(|0\rangle\) and \(|1\rangle\) (using Dirac notation).

Qubit:
┌─────────────────────────────────────┐
│                                     │
│   α|0⟩ + β|1⟩                       │
│                                     │
│   where |α|² + |β|² = 1             │
│                                     │
└─────────────────────────────────────┘

The numbers \(\alpha\) and \(\beta\) are called amplitudes. They can be complex numbers.

The Bloch Sphere

One common way to visualize a single qubit is the Bloch sphere. Every pure single-qubit state corresponds to a point on this sphere.

from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector
from qiskit.visualization import plot_bloch_multivector

# Visualize |0⟩
qc = QuantumCircuit(1)
sv = Statevector(qc)
display(plot_bloch_multivector(sv))
        |0⟩ (north pole, θ=0)
         │
         │        z-axis
         │
         ●─────────────── x-axis
        /│\
       / │ \
      /  │  \
    |1⟩ │   |+⟩ (equator)
        \|/

Key points:

  • \(|0\rangle\) is at the north pole (θ=0)
  • \(|1\rangle\) is at the south pole (θ=π)
  • \(|+\rangle = \frac{|0\rangle+|1\rangle}{\sqrt{2}}\) is on the equator (φ=0)
  • \(|-\rangle = \frac{|0\rangle-|1\rangle}{\sqrt{2}}\) is also on the equator (φ=π)

Try visualizing different states:

from qiskit.visualization import plot_bloch_multivector

# |1⟩ - south pole
qc = QuantumCircuit(1)
qc.x(0)
sv = Statevector(qc)
display(plot_bloch_multivector(sv))

# |+⟩ - on the equator
qc = QuantumCircuit(1)
qc.h(0)
sv = Statevector(qc)
display(plot_bloch_multivector(sv))

# |−⟩ - opposite on the equator
qc = QuantumCircuit(1)
qc.x(0)
qc.h(0)
sv = Statevector(qc)
display(plot_bloch_multivector(sv))

Note: plot_bloch_multivector displays qubit states as vectors on the Bloch sphere. The vector points to the state on the sphere surface.

Three Ways to Think About a Qubit

1. The Coin Analogy

Think of a qubit as a coin:

ClassicalQuantum
Heads or Tails\(
The coin is one or the otherThe coin can be in a superposition
Flipping reveals the resultMeasuring forces a choice

But this analogy has limits—real qubits have phases that coins don’t.

2. The State Vector

Mathematically, a qubit is a 2-dimensional complex vector:

\[|\psi\rangle = \begin{pmatrix} \alpha \\ \beta \end{pmatrix} = \alpha|0\rangle + \beta|1\rangle\]

With the constraint: \(|\alpha|^2 + |\beta|^2 = 1\)

The constraint comes from probability: the probabilities of measuring \(|0\rangle\) and \(|1\rangle\) must add up to 1.

3. The Probability Distribution

The amplitudes encode probabilities:

  • Probability of measuring \(|0\rangle\): \(|\alpha|^2\)
  • Probability of measuring \(|1\rangle\): \(|\beta|^2\)

Example: For \(|\psi\rangle = \frac{|0\rangle + |1\rangle}{\sqrt{2}}\):

  • \(|\alpha|^2 = |\frac{1}{\sqrt{2}}|^2 = \frac{1}{2}\)
  • \(|\beta|^2 = |\frac{1}{\sqrt{2}}|^2 = \frac{1}{2}\)

So you have a 50-50 chance of measuring either state.

The Phase Is Also Important

Amplitudes can be complex, which means they have a phase:

\[|\psi\rangle = \alpha|0\rangle + \beta|1\rangle\]

Two states with the same probabilities but different phases:

  • \(|+\rangle = \frac{|0\rangle + |1\rangle}{\sqrt{2}}\) (phases are the same)
  • \(|-\rangle = \frac{|0\rangle - |1\rangle}{\sqrt{2}}\) (phases differ by \(\pi\))

These measure the same (50-50) but behave differently when combined with other gates.

Measurement

When you measure a qubit:

  1. You get either \(|0\rangle\) or \(|1\rangle\)
  2. The probability of each outcome is determined by the amplitudes
  3. The state “collapses” to the measured value
  4. Superposition is destroyed
Before measurement:  α|0⟩ + β|1⟩
                          ↓
                      MEASURE
                          ↓
After measurement:    |0⟩  (with probability |α|²)
              or     |1⟩  (with probability |β|²)

Why Qubits Matter

Here’s why qubits can do things classical bits can’t:

  1. Superposition: A qubit can be in a combination of \(|0\rangle\) and \(|1\rangle\)
  2. Entanglement: Multiple qubits can be correlated in ways classical bits can’t be
  3. Interference: Phases can combine to amplify or cancel outcomes

These properties are the foundation for quantum algorithms.

Gates Are Unitary Operations

In classical computing, gates (like NOT) are functions that map bits to bits.

In quantum computing, gates are unitary operations—matrices that:

  • Preserve the normalization constraint (\(|\alpha|^2 + |\beta|^2 = 1\))
  • Are reversible
  • Transform the state vector

The Hadamard gate (H) transforms: \[H|0\rangle = \frac{|0\rangle + |1\rangle}{\sqrt{2}}\]

\[H|1\rangle = \frac{|0\rangle - |1\rangle}{\sqrt{2}}\]

Qubits vs Classical Bits: Key Differences

PropertyClassical BitQubit
States0 or 1Any normalized superposition
CopyingEasyImpossible (no-cloning theorem)
ReadingDestructive?Destructive (collapses state)
OperationsBoolean logicUnitary matrices
DeterminismAlways deterministicProbabilistic (when measured)

A Note on Notation

This book uses Dirac notation throughout. Here’s a quick reference:

NotationMeaning
\(0\rangle\)
\(1\rangle\)
\(\psi\rangle\)
\(\langle\psi\)
\(\langle\psi\phi\rangle\)
\(+\rangle\)
\(-\rangle\)

Summary

A qubit is a two-level quantum system that:

  • Has two basis states: \(|0\rangle\) and \(|1\rangle\)
  • Can exist in superpositions: \(\alpha|0\rangle + \beta|1\rangle\)
  • Has amplitudes that encode probabilities and phases
  • Collapses upon measurement
  • Is transformed by unitary gates

Understanding qubits as vectors in a 2D complex space will make everything in this book click.

Quick Self-Check Quiz

Test your understanding with these questions:

Q1: If a qubit state is \\(|ψ⟩ = \frac{1}{2}|0⟩ + \frac{\sqrt{3}}{2}|1⟩\\), what is P(|0⟩)?

Answer: \(|1/2|² = 1/4 = 0.25\) (25%)

from qiskit.quantum_info import Statevector
sv = Statevector([1/2, (3**0.5)/2])
print("Probabilities:", sv.probabilities())  # [0.25, 0.75]
Q2: Can you copy a qubit in superposition? (Hint: think about the no-cloning theorem)

Answer: No. The no-cloning theorem proves that arbitrary quantum states cannot be copied. This is a fundamental difference from classical bits.

Q3: What does measurement do to a superposition state?

Answer: It collapses the state to either |0⟩ or |1⟩, with probabilities determined by the amplitudes. The superposition is destroyed.

Q4: Is \\(e^{iπ}|ψ⟩\\) the same physical state as \\(|ψ⟩\\)?

Answer: Yes, up to global phase. Global phase changes are physically unobservable. But relative phase changes (like \(e^{iπ}|1⟩\)) are physically significant.

Checkpoint Exercises

Exercise 1

Use Qiskit to verify that \(|+\rangle\) and \(|-\rangle\) both have 50-50 measurement probabilities.

Exercise 2

Create a state with P(|0⟩) = 0.8. What are the amplitudes?

Exercise 3

Verify that \(|+\rangle\) and \(|-\rangle\) are orthogonal: \(⟨+|−⟩ = 0\).

Exercise 4

Show that \(|0⟩\) and \(|1⟩\) are also orthogonal.

Exercise 5

Explain in your own words why amplitudes must be normalized.


Next: Measurement: From Quantum to Classical

Measurement: From Quantum to Classical

In the last chapter, we created superposition and inspected the statevector. Now let’s understand what happens when we actually measure a qubit.

The Measurement Problem

Quantum states are not directly observable. When you measure a qubit:

  1. You get a classical result: 0 or 1
  2. The probability of each outcome comes from the amplitudes
  3. The state collapses to the measured value
Before: α|0⟩ + β|1⟩     ──MEASURE──>    |0⟩  (prob |α|²)
                                         |1⟩  (prob |β|²)

Two Different Questions

These are fundamentally different questions:

QuestionToolAnswer
“What state is the qubit in?”StatevectorExact amplitudes and phases
“What do I observe when I measure?”SamplingProbabilistic counts

You need both perspectives to understand quantum circuits.

Sampling with StatevectorSampler

Qiskit provides StatevectorSampler for simulating measurements:

from qiskit import QuantumCircuit
from qiskit.primitives import StatevectorSampler

# Create a circuit: Hadamard on one qubit
qc = QuantumCircuit(1)
qc.h(0)

# Add measurements
qc.measure_all()

# Sample with 1000 shots
sampler = StatevectorSampler()
result = sampler.run([qc], shots=1000).result()

# Get the counts
counts = result[0].data.meas.get_counts()
print(counts)

Typical output (will vary due to randomness):

{'0': 497, '1': 503}

Close to 50-50, as expected for a Hadamard on \(|0\rangle\).

Running Multiple Times

Each run of the same circuit gives slightly different counts due to randomness:

# Run the same circuit 4 times with 100 shots each
for i in range(4):
    result = sampler.run([qc], shots=100).result()
    counts = result[0].data.meas.get_counts()
    print(f"Run {i+1}: {counts}")

You might see:

Run 1: {'0': 52, '1': 48}
Run 2: {'1': 55, '0': 45}
Run 3: {'0': 51, '1': 49}
Run 4: {'1': 53, '0': 47}

The counts fluctuate around 50-50. The average of many runs approaches the true probability.

Why Not Just Measure?

If measurement gives random results, why do we care about quantum computing?

Because:

  1. We control the probabilities through circuit design
  2. Interference can make “wrong” answers cancel out
  3. Entanglement creates correlations that classical systems can’t match

Measurement is the end of computation, not the beginning.

The Statevector Tells the Truth

If you only look at measurement counts, you miss important information:

from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector

# Create two circuits with different phases
qc1 = QuantumCircuit(1)
qc1.h(0)        # |+⟩ = (|0⟩ + |1⟩) / √2

qc2 = QuantumCircuit(1)
qc2.h(0)        # |+⟩
qc2.z(0)        # Apply Z: (|0⟩ - |1⟩) / √2 = |−⟩

# Look at the states
print("State 1 (|+⟩):", Statevector(qc1))
print("State 2 (|−⟩):", Statevector(qc2))

Output:

State 1 (|+⟩): Statevector([ 0.70710678+0.j,  0.70710678+0.j], dims=(2,))
State 2 (|−⟩): Statevector([ 0.70710678+0.j, -0.70710678+0.j], dims=(2,))

The states are different (different phase), but if you measure either one, you’ll get 50-50 counts.

The phase difference is invisible to measurement but crucial for interference.

A Two-Qubit Example

Let’s see measurement on entangled states:

from qiskit import QuantumCircuit
from qiskit.primitives import StatevectorSampler

# Create a Bell state: (|00⟩ + |11⟩) / √2
qc = QuantumCircuit(2)
qc.h(0)
qc.cx(0, 1)
qc.measure_all()

# Sample many times
sampler = StatevectorSampler()
result = sampler.run([qc], shots=1000).result()
counts = result[0].data.meas.get_counts()
print(counts)

Typical output:

{'11': 502, '00': 498}

You only see 00 and 11. Never 01 or 10.

This is quantum correlation—the measurement outcomes are linked even though neither outcome is predetermined.

When Measurement Destroys Information

If you measure in the middle of a circuit, you destroy the quantum state:

# What NOT to do: measure too early
qc = QuantumCircuit(2)
qc.h(0)    # Creates superposition
qc.cx(0, 1)  # Creates entanglement
qc.measure_all()  # ← Measuring here destroys everything!

# vs. What you want: measure at the end
qc_good = QuantumCircuit(2)
qc_good.h(0)
qc_good.cx(0, 1)
# No measurement yet—inspect the state first!
state = Statevector(qc_good)
print("Before measurement:", state)

The good circuit shows the full entangled state. The measured circuit shows only collapsed results.

The Workflow: Statevector First

Here’s the recommended workflow for any circuit:

  1. Write the circuit
  2. Inspect Statevector()
    • Are the amplitudes correct?
    • Are the phases correct?
  3. Predict what measurement will show
  4. Add measurements and sample
  5. Verify counts match your prediction

This order prevents the “I have no idea why this is broken” debugging spiral.

Checkpoint Exercises

Exercise 1

Create a circuit that always measures 1. Verify with 100 shots.

Exercise 2

Create a circuit with a 25-75 split between 0 and 1.

Exercise 3

Build two circuits that produce identical measurement counts but different statevectors.

Exercise 4

Create a Bell state and list all possible measurement outcomes.

Exercise 5

Explain why measuring a qubit in superposition “destroys” information.

The Deep Truth

Measurement isn’t just reading a value. In quantum mechanics, measurement:

  1. Forces the system to choose
  2. Destroys superposition
  3. Creates randomness where there was none

This is why quantum computing is powerful and subtle: you must carefully design circuits so that the “right” answers become more probable, even though any individual measurement is random.

Summary

In this chapter, you learned:

  • Measurement collapses quantum states to classical outcomes
  • StatevectorSampler simulates measurement statistics
  • Counts are probabilistic but average to true probabilities
  • Phase differences are invisible to immediate measurement
  • The statevector-first workflow prevents many bugs

Measurement is how quantum computation becomes classical information. But understanding what happens before measurement is the key to designing useful quantum circuits.

Official Documentation: For more on measurement in Qiskit, see the Qiskit documentation on measurement. The StatevectorSampler is the recommended way to simulate measurement statistics.


Try These On QCoder


Next: Qubit Ordering: The Endian Problem

Qubit Ordering: The Endian Problem

This chapter exists because qubit ordering trips up nearly every quantum computing beginner. The good news: once you understand it, it becomes automatic.

The Big Idea

Qiskit uses little-endian ordering for basis states.

This means:

  • Qubit 0 is the least significant bit (rightmost)
  • Qubit 1 is to the left of qubit 0
  • Qubit 2 is further left, etc.
For a 3-qubit system with q2=1, q1=0, q0=1:
┌────────────────────────────────────────────┐
│  Basis label: |101⟩                        │
│  Qubit 2: 1  |  Qubit 1: 0  |  Qubit 0: 1  │
│      (MSB)                       (LSB)     │
└────────────────────────────────────────────┘

Why This Matters

When you see a basis state like |101>:

  • It means qubit 2 is 1
  • It means qubit 1 is 0
  • It means qubit 0 is 1

This is backwards from how we normally write numbers (where the leftmost digit is most significant).

A Concrete Example

from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector

# Prepare |10⟩ on two qubits
qc = QuantumCircuit(2)
qc.x(1)  # Flip qubit 1

state = Statevector(qc)
print(state)

Output:

Statevector([0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j],
            dims=(2, 2))

The statevector shows the amplitudes in order: |00⟩, |01⟩, |10⟩, |11⟩.

The |10⟩ component has amplitude 1, meaning the state is \(|10\rangle\).

The Mapping

For 2 qubits, the statevector position maps to basis states like this:

PositionStatevector IndexBasis StateQubit Values
0[0]|00⟩q1=0, q0=0
1[1]|01⟩q1=0, q0=1
2[2]|10⟩q1=1, q0=0
3[3]|11⟩q1=1, q0=1

Preparing Specific States

To prepare |101> on 3 qubits:

from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector

qc = QuantumCircuit(3)

# q0 = 1 (rightmost qubit)
qc.x(0)

# q2 = 1 (leftmost qubit)
qc.x(2)

state = Statevector(qc)
print(state)

Output shows |101> as the only non-zero amplitude.

Measurement Output

The same ordering applies to measurement results:

from qiskit import QuantumCircuit
from qiskit.primitives import StatevectorSampler

qc = QuantumCircuit(2)
qc.x(0)  # Set q0 = 1
qc.measure_all()

sampler = StatevectorSampler()
result = sampler.run([qc], shots=100).result()
print(result[0].data.meas.get_counts())

Output:

{'01': 100}

The count shows 01 (not 10) because q1=0 and q0=1.

The Intuition

Think of it like reading a phone number:

555-1234
│   │
│   └── Least significant (rightmost)
└──────── Most significant (leftmost)

Qubits are numbered from right (least significant) to left (most significant).

Why Little-Endian?

There’s no deep quantum reason for this choice—it’s a convention.

Some frameworks use big-endian. Qiskit chose little-endian because:

  • It matches C/C++ integer representation
  • It makes certain mathematical operations more natural
  • It’s consistent across IBM’s quantum hardware

Once you know it’s little-endian, you can work with any framework.

A Common Mistake

Beginners often flip qubit indices when preparing states:

# WRONG: Thinking q1=1 gives |01⟩
qc = QuantumCircuit(2)
qc.x(1)  # This actually gives |10⟩, not |01⟩!

# RIGHT: Check with Statevector
state = Statevector(qc)
print(state)

Always verify with Statevector until the ordering becomes second nature.

A Systematic Approach

When you need to prepare a specific basis state:

  1. Write down the basis label: |101>
  2. Break it into qubits: q2=1, q1=0, q0=1
  3. Flip the right qubits:
    qc = QuantumCircuit(3)
    if q2 == 1: qc.x(2)  # Flip qubit 2
    if q1 == 1: qc.x(1)  # Flip qubit 1
    if q0 == 1: qc.x(0)  # Flip qubit 0
    
  4. Verify with Statevector

Tables to Memorize

2-Qubit Basis States

LabelQubit 1Qubit 0Statevector Index
|00⟩000
|01⟩011
|10⟩102
|11⟩113

3-Qubit Basis States

LabelQubit 2Qubit 1Qubit 0
|000⟩000
|001⟩001
|010⟩010
|011⟩011
|100⟩100
|101⟩101
|110⟩110
|111⟩111

General Rule

For an n-qubit system:

  • Qubit 0 is always the rightmost (least significant)
  • Qubit n-1 is always the leftmost (most significant)
  • Basis state |xₙ₋₁...x₂x₁x₀⟩ means qubit i has value xᵢ

When Qubit Ordering Bites Back

These situations commonly cause confusion:

SituationWhat Goes Wrong
Preparing arithmetic states`
Reading statevectorsIndex doesn’t match expected basis state
QCoder problemsProblem states might use different conventions
Copying circuit diagramsGate positions might assume different ordering

Debugging Tip

When a circuit “should” work but doesn’t, qubit ordering is often the culprit.

Quick check:

# Print both representations
qc = QuantumCircuit(2)
qc.x(1)

state = Statevector(qc)
print("State:", state)
print("Non-zero indices:", [i for i, amp in enumerate(state) if abs(amp) > 0])

If you expect |01> but get |10>, you found the bug.

Summary

Key takeaways:

  • Qiskit uses little-endian qubit ordering
  • Qubit 0 is the least significant bit (rightmost)
  • Basis state |abc> means q2=a, q1=b, q0=c (for 3 qubits)
  • Always verify state preparations with Statevector
  • Qubit ordering errors are the #1 source of beginner bugs

This convention will feel natural after some practice. When in doubt, check with Statevector.


Checkpoint Exercises

  1. Prepare |10> on two qubits and verify the statevector.
  2. Prepare |101> on three qubits.
  3. Explain why qc.x(0) on two qubits gives |01>.
  4. Create a table showing all 2-qubit basis states with their qubit values.
  5. Predict what qc.x(0); qc.x(2) produces on a 3-qubit system.

Official Documentation: For more on qubit ordering and statevector representation, see the Qiskit documentation on Statevector. The QuantumCircuit qubit layout documentation explains how qubit ordering affects circuit execution.

Try These On QCoder


Next: Gate Basics: X, Y, Z

Gate Basics: X, Y, Z

Now that you understand qubits and statevectors, let’s learn the basic gates. These are the building blocks for all quantum circuits.

What Is a Gate?

A quantum gate is a unitary operation that transforms a quantum state. Think of it as a function that maps input states to output states.

Input State ──[Gate]──> Output State
   |α⟩|0⟩ + |β⟩|1⟩          |α'⟩|0⟩ + |β'⟩|1⟩

Gates preserve the normalization (\(|\alpha|^2 + |\beta|^2 = 1\)) and are reversible.

The Pauli Gates

The Pauli gates are the most fundamental single-qubit gates. They correspond to 180° rotations around the x, y, and z axes of the Bloch sphere.

The X Gate (NOT)

The X gate flips \(|0\rangle\) and \(|1\rangle\):

\[X|0\rangle = |1\rangle\] \[X|1\rangle = |0\rangle\]

from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector

# X gate on |0⟩ gives |1⟩
qc = QuantumCircuit(1)
qc.x(0)
print(Statevector(qc))

Output:

Statevector([0.+0.j, 1.+0.j],
            dims=(2,))

The X gate is the quantum equivalent of classical NOT, but with a catch: it’s reversible.

The Z Gate (Phase Flip)

The Z gate flips the sign of the \(|1\rangle\) component:

\[Z|0\rangle = |0\rangle\] \[Z|1\rangle = -|1\rangle\]

# Z gate leaves |0⟩ unchanged
qc1 = QuantumCircuit(1)
qc1.z(0)
print("Z on |0⟩:", Statevector(qc1))

# Z gate flips sign of |1⟩
qc2 = QuantumCircuit(1)
qc2.x(0)  # Prepare |1⟩
qc2.z(0)  # Apply Z
print("Z on |1⟩:", Statevector(qc2))

Output:

Z on |0⟩: Statevector([1.+0.j, 0.+0.j], dims=(2,))
Z on |1⟩: Statevector([0.+0.j, -1.+0.j], dims=(2,))

The Z gate is invisible if your qubit is definitely in \(|0\rangle\)—but becomes crucial in superposition states.

The Y Gate

The Y gate does both: flips the state and adds a phase:

\[Y|0\rangle = i|1\rangle\] \[Y|1\rangle = -i|0\rangle\]

# Y on |0⟩
qc = QuantumCircuit(1)
qc.y(0)
print(Statevector(qc))

Output:

Statevector([0.+0.j, 0.+1.j],
            dims=(2,))

Note the \(i\) (imaginary unit) in the amplitude. This is fine—amplitudes can be complex.

Visualizing the Pauli Gates

On the Bloch sphere, the Pauli gates are 180° rotations:

        |0⟩ (north pole)
           ↑
           │  X-axis
       ←────●─────→ rotation
           │
           ↓
        |1⟩ (south pole)

Let’s see this in action:

from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector
from qiskit.visualization import plot_bloch_multivector

# X gate: rotates from |0⟩ to |1⟩ around x-axis
qc_x = QuantumCircuit(1)
qc_x.x(0)
display(plot_bloch_multivector(Statevector(qc_x)))

# Z gate: leaves |0⟩ unchanged
qc_z = QuantumCircuit(1)
qc_z.z(0)
display(plot_bloch_multivector(Statevector(qc_z)))

# Z on |+⟩: rotates to |−⟩ (phase flip visible on equator)
qc_z_on_plus = QuantumCircuit(1)
qc_z_on_plus.h(0)
qc_z_on_plus.z(0)
display(plot_bloch_multivector(Statevector(qc_z_on_plus)))

# Y gate: interesting because it has imaginary amplitudes
qc_y = QuantumCircuit(1)
qc_y.y(0)
display(plot_bloch_multivector(Statevector(qc_y)))
GateRotation AxisAngle
Xx-axis180°
Yy-axis180°
Zz-axis180°

The Identity Gate

The identity gate (I) does nothing:

\[I|0\rangle = |0\rangle\] \[I|1\rangle = |1\rangle\]

qc = QuantumCircuit(1)
qc.i(0)  # Identity gate

You won’t use this much, but it appears in theoretical discussions.

Gate Composition

You can apply multiple gates in sequence:

# X then X gives |0⟩ again (X² = I)
qc = QuantumCircuit(1)
qc.x(0)
qc.x(0)
print(Statevector(qc))

# X then Z
qc2 = QuantumCircuit(1)
qc2.x(0)
qc2.z(0)
print(Statevector(qc2))

Key insight: Gate order matters!

\[XZ \neq ZX\]

# Z then X (different from X then Z)
qc3 = QuantumCircuit(1)
qc3.z(0)
qc3.x(0)
print(Statevector(qc3))

Do you notice the difference?

Checking Gate Equivalence

You can verify that two circuits are equivalent using Operator:

from qiskit.quantum_info import Operator

qc_xz = QuantumCircuit(1)
qc_xz.x(0)
qc_xz.z(0)

qc_zx = QuantumCircuit(1)
qc_zx.z(0)
qc_zx.x(0)

print("XZ circuit:")
print(qc_xz)

print("ZX circuit:")
print(qc_zx)

print("Are they equal?", Operator(qc_xz) == Operator(qc_zx))

Multi-Qubit Gates

The Pauli gates can also be applied to multiple qubits:

qc = QuantumCircuit(3)
qc.x(0)   # Flip qubit 0
qc.y(1)   # Flip qubit 1 (with phase)
qc.z(2)   # Flip qubit 2 (with phase)

Each gate acts independently on its target qubit.

Official Documentation: For more details on Pauli gates, see the Qiskit documentation on single-qubit gates. See also The Plus/Minus Basis for the complete gate summary table.

Checkpoint Exercises

Exercise 1

Apply X to \(|0\rangle\). What state do you get?

Exercise 2

Apply Z to \(|1\rangle\). What state do you get? Is it the same as \(Z|0\rangle\)?

Exercise 3

Apply X then Z to \(|0\rangle\). Then apply Z then X to \(|0\rangle\). Are the results the same?

Exercise 4

Apply Y to \(|0\rangle\). What do you notice about the amplitudes?

Exercise 5

Verify that \(X^2 = I\) (applying X twice returns to the original state).

The Next Gate: Hadamard

The Pauli gates are important, but they’re limited: they only give basis states. The Hadamard gate creates superposition, which is where quantum computing gets interesting.


Try These On QCoder


Official Documentation: For more details on Pauli gates, see the Qiskit documentation on single-qubit gates.


Next: Superposition: The Hadamard Gate

Superposition: The Hadamard Gate

The Hadamard gate is the key to quantum computing. It transforms a definite state into a superposition—allowing a qubit to be “both 0 and 1 at the same time.”

The Basic Action

The Hadamard gate (H) transforms the basis states:

\[H|0\rangle = \frac{|0\rangle + |1\rangle}{\sqrt{2}} = |+\rangle\] \[H|1\rangle = \frac{|0\rangle - |1\rangle}{\sqrt{2}} = |-\rangle\]

from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector

# H on |0⟩
qc = QuantumCircuit(1)
qc.h(0)
print("H|0⟩:", Statevector(qc))

# H on |1⟩
qc2 = QuantumCircuit(1)
qc2.x(0)  # Prepare |1⟩
qc2.h(0)  # Apply H
print("H|1⟩:", Statevector(qc2))

Output:

H|0⟩: Statevector([ 0.70710678+0.j,  0.70710678+0.j], dims=(2,))
H|1⟩: Statevector([ 0.70710678+0.j, -0.70710678+0.j], dims=(2,))

What Is \(|+\rangle\) and \(|-\rangle\)?

These are the plus and minus states—eigenstates in a different basis:

StateNameDefinition
\(+\rangle\)Plus state
\(-\rangle\)Minus state

The \(|+\rangle\) and \(|-\rangle\) states are:

  • Orthogonal: \(\langle +|- \rangle = 0\)
  • Normalized: \(|+\rangle = 1\)
  • Complete: Any single-qubit state can be written as a combination of them

The Matrix Form

The Hadamard matrix is:

\[H = \frac{1}{\sqrt{2}}\begin{pmatrix} 1 & 1 \\ 1 & -1 \end{pmatrix}\]

Applying this to \(|0\rangle = \begin{pmatrix} 1 \\ 0 \end{pmatrix}\):

\[H|0\rangle = \frac{1}{\sqrt{2}}\begin{pmatrix} 1 & 1 \\ 1 & -1 \end{pmatrix}\begin{pmatrix} 1 \\ 0 \end{pmatrix} = \frac{1}{\sqrt{2}}\begin{pmatrix} 1 \\ 1 \end{pmatrix} = |+\rangle\]

Creating Superposition on Multiple Qubits

To create equal superposition on n qubits, apply H to each:

from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector

# Equal superposition on 2 qubits
qc = QuantumCircuit(2)
qc.h(0)
qc.h(1)
print(Statevector(qc))

Output:

Statevector([0.5+0.j, 0.5+0.j, 0.5+0.j, 0.5+0.j],
            dims=(2, 2))

This is \(\frac{|00\rangle + |01\rangle + |10\rangle + |11\rangle}{2}\)—equal superposition over all 2-qubit basis states.

For n qubits: equal superposition gives \(\frac{1}{\sqrt{2^n}}\sum_{x}|x\rangle\)

Superposition Is Not Mixture

A common misconception: superposition means “the qubit is randomly 0 or 1.”

This is wrong.

Superposition is a coherent combination where phases matter. Consider:

\[|+\rangle = \frac{|0\rangle + |1\rangle}{\sqrt{2}}\]

\[|-\rangle = \frac{|0\rangle - |1\rangle}{\sqrt{2}}\]

Both give 50-50 measurement outcomes, but they are different quantum states. The difference only appears in interference effects.

Measuring Superposition States

When you measure a qubit in superposition:

from qiskit import QuantumCircuit
from qiskit.primitives import StatevectorSampler

qc = QuantumCircuit(1)
qc.h(0)
qc.measure_all()

sampler = StatevectorSampler()
result = sampler.run([qc], shots=1000).result()
print(result[0].data.meas.get_counts())

Output (approximately):

{'0': 498, '1': 502}

Measurement collapses the state randomly, with probabilities determined by the amplitudes.

The Power of Superposition

Here’s why superposition matters: with n qubits in superposition, you have a quantum state that represents all \(2^n\) basis states simultaneously.

1 qubit:  2 states at once
2 qubits: 4 states at once
3 qubits: 8 states at once
...
10 qubits: 1,024 states at once
20 qubits: 1,048,576 states at once

This isn’t just parallel computation—it’s a superposition where amplitudes can interfere constructively or destructively.

The Hadamard as Basis Change

Think of H as a basis change:

Computational Basis     ←→     Plus/Minus Basis
    |0⟩                      |+⟩
    |1⟩                      |−⟩

This is crucial: gates in different bases do different things.

Visualizing Superposition

from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector
from qiskit.visualization import plot_bloch_multivector

# |0⟩ before H - at north pole
qc = QuantumCircuit(1)
display(plot_bloch_multivector(Statevector(qc)))

# After H - |+⟩ on the equator!
qc = QuantumCircuit(1)
qc.h(0)
display(plot_bloch_multivector(Statevector(qc)))

# H on |1⟩ gives |−⟩ - opposite on equator
qc = QuantumCircuit(1)
qc.x(0)
qc.h(0)
display(plot_bloch_multivector(Statevector(qc)))

# Equal superposition on 2 qubits - shows both on Bloch spheres
qc = QuantumCircuit(2)
qc.h(0)
qc.h(1)
display(plot_bloch_multivector(Statevector(qc)))

Applying H Twice

What happens when you apply H twice?

\[HH|0\rangle = HH|0\rangle = |0\rangle\]

from qiskit.quantum_info import Operator

qc = QuantumCircuit(1)
qc.h(0)
qc.h(0)

print("H twice:", Operator(qc).data)
print("Identity?", Operator(qc).data.round(5))

Output:

H twice: [[ 1.00000e+00+0.j  -5.55112e-17+0.j]
          [-5.55112e-17+0.j  1.00000e+00+0.j]]
Identity? True

Yes! \(H^2 = I\) (identity). This is because H is its own inverse.

Basis Changes in Practice

A key insight: applying H transforms computational-basis operations to plus/minus-basis operations:

\[HZH = X\] \[HXH = Z\] \[HYH = -Y\]

This identity lets you implement operations in different bases. We’ll use this repeatedly.

Checkpoint Exercises

Exercise 1

Create superposition on 1 qubit. Verify the amplitudes are both \(1/\sqrt{2}\).

Exercise 2

Create superposition on 3 qubits. How many states are in superposition?

Exercise 3

Apply H twice to \(|1\rangle\). What state do you get?

Exercise 4

Create \(|-\rangle\) starting from \(|0\rangle\).

Exercise 5

Verify that \(HZH = X\) using Qiskit.

Summary

Key takeaways:

  • H creates superposition from basis states
  • \(H|0\rangle = |+\rangle\) and \(H|1\rangle = |-\rangle\)
  • H is its own inverse: \(H^2 = I\)
  • Superposition is coherent, not random
  • Multi-qubit superposition: apply H to each qubit
  • H changes between computational and plus/minus bases

The Hadamard gate is your gateway to quantum computation. With H, you can create any superposition state.

Official Documentation: The Hadamard gate is part of Qiskit’s circuit library. For a deeper look at superposition, see IBM’s superposition tutorial.


Next: Phase: The Z Gate and Relative Phase

Phase: The Z Gate and Relative Phase

The Z gate looked harmless when we first saw it. Now that you understand superposition, we can see its true power: the Z gate introduces relative phase, which is invisible in immediate measurements but crucial for interference.

Global Phase vs Relative Phase

This distinction is fundamental.

Global Phase

Multiplying the entire state by a complex number:

\[|\psi\rangle \rightarrow e^{i\phi}|\psi\rangle\]

This changes nothing physically. All measurement probabilities remain the same.

Relative Phase

Multiplying one amplitude by a complex number:

\[|0\rangle + |1\rangle \rightarrow |0\rangle - |1\rangle\]

This changes everything physically. The state is different, even if measurement statistics are the same.

The Z Gate and Relative Phase

The Z gate applies a phase flip to the \(|1\rangle\) component:

\[Z(\alpha|0\rangle + \beta|1\rangle) = \alpha|0\rangle - \beta|1\rangle\]

On basis states:

  • \(Z|0\rangle = |0\rangle\) (unchanged)
  • \(Z|1\rangle = -|1\rangle\) (phase flip)

Why Z Seems Useless on \(|0\rangle\)

When your qubit is definitely in \(|0\rangle\), Z does nothing visible:

from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector

qc = QuantumCircuit(1)
qc.z(0)
print(Statevector(qc))

Output:

Statevector([1.+0.j, 0.+0.j],
            dims=(2,))

The state is still \(|0\rangle\). No change.

But watch what happens after a Hadamard:

# Without Z
qc1 = QuantumCircuit(1)
qc1.h(0)
print("H only:", Statevector(qc1))

# With Z
qc2 = QuantumCircuit(1)
qc2.h(0)
qc2.z(0)
print("H then Z:", Statevector(qc2))

Output:

H only: Statevector([0.70710678+0.j, 0.70710678+0.j], dims=(2,))
H then Z: Statevector([0.70710678+0.j, -0.70710678+0.j], dims=(2,))

The states are different! The Z gate introduces a relative phase of \(\pi\) (180°) between the two branches.

The Minus State

The state \(|-\rangle = \frac{|0\rangle - |1\rangle}{\sqrt{2}}\) is created by H then Z:

qc = QuantumCircuit(1)
qc.h(0)
qc.z(0)
print(Statevector(qc))

Or equivalently by X then H:

qc = QuantumCircuit(1)
qc.x(0)
qc.h(0)
print(Statevector(qc))

Both give \(|-\rangle\). Verify this!

Visualizing Phase on the Bloch Sphere

On the Bloch sphere, phase corresponds to position around the equator:

from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector
from qiskit.visualization import plot_bloch_multivector
from math import pi

# |+⟩ - phase 0
qc = QuantumCircuit(1)
qc.h(0)
display(plot_bloch_multivector(Statevector(qc)))

# |−⟩ - phase π (180°)
qc = QuantumCircuit(1)
qc.x(0)
qc.h(0)
display(plot_bloch_multivector(Statevector(qc)))

# P(π/2) - phase π/2
qc = QuantumCircuit(1)
qc.p(pi/2, 0)
display(plot_bloch_multivector(Statevector(qc)))

# P(π/4) - phase π/4
qc = QuantumCircuit(1)
qc.p(pi/4, 0)
display(plot_bloch_multivector(Statevector(qc)))
           |+⟩ (phase 0)
            ●
           / \
          /   \
    |−⟩──●     ●──|+i⟩
         \   /
          \ /
           ●
          |0⟩
  • \(|+\rangle\) has phase 0
  • \(|-\rangle\) has phase π (180°)
  • \(|+i\rangle\) and \(|-i\rangle\) are eigenstates of Y (phase π/2 and 3π/2)

Phase in Multi-Qubit States

Relative phase becomes more interesting with multiple qubits:

from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector

# Create superposition on two qubits
qc = QuantumCircuit(2)
qc.h(0)
qc.h(1)

print("Equal superposition:", Statevector(qc))

# Now apply Z to qubit 0 only
qc2 = QuantumCircuit(2)
qc2.h(0)
qc2.h(1)
qc2.z(0)

print("With Z on q0:", Statevector(qc2))

The \(|1\rangle\) component on qubit 0 gets the phase flip, creating entanglement between the phase and which qubit is in state 1.

CZ Gate: Phase on Two Qubits

The controlled-Z (CZ) gate adds a phase flip when both qubits are \(|1\rangle\):

from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector

# Create superposition on both qubits
qc = QuantumCircuit(2)
qc.h(0)
qc.h(1)

print("Before CZ:", Statevector(qc))

# Apply CZ
qc.cz(0, 1)

print("After CZ:", Statevector(qc))

Output shows a phase flip on the \(|11\rangle\) component.

Why Phase Matters

Phase is invisible in immediate measurements but matters because:

  1. Interference: Phases combine when states recombine
  2. Entanglement: Phase correlations create entangled states
  3. Quantum algorithms: Phase manipulation is the core of most algorithms

Example: Grover’s algorithm uses phase differences to amplify the probability of marked states.

Measuring Phase

You can’t directly “see” relative phase in a single measurement. But you can reveal it with basis changes:

from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector
from qiskit.primitives import StatevectorSampler

# Create |−⟩
qc = QuantumCircuit(1)
qc.h(0)
qc.z(0)

# Measure in X basis (apply H then measure)
qc.h(0)  # Basis change: Z basis → X basis
qc.measure_all()

sampler = StatevectorSampler()
result = sampler.run([qc], shots=100).result()
print(result[0].data.meas.get_counts())

Output: Always 1 (never 0).

Why? Because \(|-\rangle\) is the \(|1\rangle\) state in the X basis. The phase is revealed by changing the measurement basis.

The Phase Gate (P)

Qiskit has a general phase gate p(theta) that adds an arbitrary phase:

\[P(\theta)|1\rangle = e^{i\theta}|1\rangle\]

from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector
from math import pi

# P(π) should equal Z
qc = QuantumCircuit(1)
qc.p(pi, 0)
print("P(π):", Statevector(qc))

# P(π/2) adds 90° phase
qc2 = QuantumCircuit(1)
qc2.p(pi/2, 0)
print("P(π/2):", Statevector(qc2))

Checkpoint Exercises

Exercise 1

Show that \(Z = PHP(\pi)\) where H is the Hadamard gate in the X basis. (Hint: use Operator to compare)

Exercise 2

Create \(|-\rangle\) three different ways and verify they give identical states.

Exercise 3

Apply Z to a qubit in superposition. Show that the probabilities don’t change but the state vector does.

Exercise 4

Use the CZ gate to create phase entanglement between two qubits.

Exercise 5

Design a circuit that distinguishes \(|+\rangle\) from \(|-\rangle\) without destroying the state.

Summary

Key concepts:

  • Global phase (\(e^{i\phi}|\psi\rangle\)) is physically irrelevant
  • Relative phase between amplitudes is physically meaningful
  • Z gate introduces relative phase of π on \(|1\rangle\)
  • Phase is invisible in immediate measurement but visible in interference
  • Basis changes reveal hidden phase information

Understanding phase is essential for quantum computing. Many algorithms (Grover, QFT, phase estimation) are fundamentally about manipulating phases.

Official Documentation: For more on phase and phase gates, see IBM’s phase kickback tutorial which explains how phase is used in quantum algorithms.


Try These On QCoder


Next: Rotations: RX, RY, RZ, and P

Rotations: RX, RY, RZ, and P

The Pauli gates (X, Y, Z) rotate by 180°. The rotation gates let you rotate by arbitrary angles—giving you precise control over quantum states.

Rotation Gates Overview

GateAxisDescription
rx(theta)x-axisRotation around the x-axis
ry(theta)y-axisRotation around the y-axis
rz(theta)z-axisRotation around the z-axis
p(theta)z-axisPhase (relative to

All angles are in radians, not degrees.

The RX Gate (X-Axis Rotation)

\(R_x(\theta)\) rotates around the x-axis:

\[R_x(\theta)|0\rangle = \cos(\frac{\theta}{2})|0\rangle - i\sin(\frac{\theta}{2})|1\rangle\]

from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector
from math import pi

# 180° rotation (should equal X)
qc = QuantumCircuit(1)
qc.rx(pi, 0)
print("RX(π):", Statevector(qc))

Output:

RX(π): Statevector([ 6.123234e-17+0.j, -6.123234e-17-1.j],
            dims=(2,))

The amplitude on \(|1\rangle\) is approximately \(-i\), which has the same effect as \(-1\) up to global phase. So \(R_x(\pi) \approx X\).

The RY Gate (Y-Axis Rotation)

\(R_y(\theta)\) rotates around the y-axis. This is often the most intuitive rotation because it directly controls the probability of measuring \(|0\rangle\) vs \(|1\rangle\):

\[R_y(\theta)|0\rangle = \cos(\frac{\theta}{2})|0\rangle + \sin(\frac{\theta}{2})|1\rangle\]

# RY(π/2) should give 50-50 superposition
qc = QuantumCircuit(1)
qc.ry(pi/2, 0)
print("RY(π/2):", Statevector(qc))

Output:

RY(π/2): Statevector([0.70710678+0.j, 0.70710678+0.j], dims=(2,))

The amplitudes are both \(1/\sqrt{2}\)—exactly like the Hadamard!

The Half-Angle Pattern

Notice the pattern: \(R_y(\theta)\) uses \(\theta/2\) in the formulas. This is because:

  • A full rotation (\(2\pi\)) should return to the same state
  • So the rotation angle in the formulas is \(\theta/2\)
Rotation AngleEffect
\(R_y(\pi)\)\(
\(R_y(\pi/2)\)\(
\(R_y(\pi/4)\)Partial rotation toward \(

Designing States with RY

The RY gate is your main tool for preparing states with specific probabilities:

# Want P(|1⟩) = 3/4
# That means sin²(θ/2) = 3/4
# So θ/2 = arcsin(√(3/4)) = π/3
# And θ = 2π/3

from math import pi, asin, sqrt

target_prob = 3/4
theta = 2 * asin(sqrt(target_prob))

qc = QuantumCircuit(1)
qc.ry(theta, 0)
state = Statevector(qc)
print("State:", state)

# Verify the probability
probs = state.probabilities()
print("P(|1⟩) =", probs[1])

Output:

State: Statevector([0.5+0.j, 0.8660254+0.j], dims=(2,))
P(|1⟩) = 0.7499999999999999

The probability is approximately 0.75, as desired.

The RZ Gate (Z-Axis Rotation)

\(R_z(\theta)\) rotates around the z-axis:

\[R_z(\theta)|0\rangle = |0\rangle\] \[R_z(\theta)|1\rangle = e^{i\theta}|1\rangle\]

# RZ adds phase to |1⟩
qc = QuantumCircuit(1)
qc.x(0)  # Start with |1⟩
qc.rz(pi/4, 0)  # Add π/4 phase
print(Statevector(qc))

Note: \(R_z\) doesn’t change the probabilities—only the phase.

The Phase Gate (P)

The p(theta) gate is equivalent to \(R_z(\theta)\):

\[P(\theta) = R_z(\theta)\]

qc = QuantumCircuit(1)
qc.x(0)
qc.p(pi/2, 0)  # Add π/2 phase to |1⟩
print(Statevector(qc))

Rotations in Practice

Creating Any Single-Qubit State

Any single-qubit state can be created with two rotations:

# Create a state with specific amplitudes (up to global phase)
alpha = 0.6  # amplitude for |0⟩
beta = 0.8   # amplitude for |1⟩

# From |0⟩, we need:
# R_y(θ)|0⟩ = cos(θ/2)|0⟩ + sin(θ/2)|1⟩
# So sin(θ/2) = |beta|, cos(θ/2) = |alpha|
# θ = 2 * atan2(|beta|, |alpha|)

from math import atan2, sqrt

theta = 2 * atan2(abs(beta), abs(alpha))

qc = QuantumCircuit(1)
qc.ry(theta, 0)

# Add phase if needed (using rz)
phi = 0  # phase on |1⟩
qc.rz(phi, 0)

print(Statevector(qc))

Visualizing Rotation Gates

The rotation gates trace paths on the Bloch sphere:

from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector
from qiskit.visualization import plot_bloch_multivector
from math import pi

# RY rotates between |0⟩ and |1⟩ (controls probability)
for angle in [0, pi/4, pi/2, 3*pi/4, pi]:
    qc = QuantumCircuit(1)
    qc.ry(angle, 0)
    display(plot_bloch_multivector(Statevector(qc)))

# RZ rotates around the equator (controls phase only)
# Note: RZ on |0⟩ has no visible effect on Bloch sphere
qc = QuantumCircuit(1)
qc.rz(pi/2, 0)
print("RZ(π/2) on |0⟩:", Statevector(qc))
display(plot_bloch_multivector(Statevector(qc)))

# RZ on |+⟩ - shows phase change
qc = QuantumCircuit(1)
qc.h(0)
display(plot_bloch_multivector(Statevector(qc)))

qc.rz(pi/2, 0)
display(plot_bloch_multivector(Statevector(qc)))

Key insight:

  • RY moves the state between poles (changes measurement probabilities)
  • RZ rotates around the equator (changes phase without changing probabilities)

Rotation Summary

GateMatrixAction
\(R_x(\theta)\)\(\begin{pmatrix} \cos\frac{\theta}{2} & -i\sin\frac{\theta}{2} \\ -i\sin\frac{\theta}{2} & \cos\frac{\theta}{2} \end{pmatrix}\)Rotate around x-axis
\(R_y(\theta)\)\(\begin{pmatrix} \cos\frac{\theta}{2} & -\sin\frac{\theta}{2} \\ \sin\frac{\theta}{2} & \cos\frac{\theta}{2} \end{pmatrix}\)Rotate around y-axis
\(R_z(\theta)\)\(\begin{pmatrix} e^{-i\theta/2} & 0 \\ 0 & e^{i\theta/2} \end{pmatrix}\)Rotate around z-axis
\(P(\theta)\)\(\begin{pmatrix} 1 & 0 \ 0 & e^{i\theta} \end{pmatrix}\)Add phase to

Useful Identities

IdentityMeaning
\(R_x(\pi) = X\)180° x-rotation = X gate
\(R_y(\pi) = Y\)180° y-rotation = Y gate
\(R_z(\pi) = Z\)180° z-rotation = Z gate
\(R_y(\pi/2) \approx H\)90° y-rotation ≈ Hadamard (up to phase)
\(R_x(\theta) = HR_z(\theta)H\)X-basis rotations via basis change

Checkpoint Exercises

Exercise 1

Use RY to create a state with P(|0⟩) = 1 (the |0⟩ state). What angle do you need?

Exercise 2

Use RY to create a state with P(|1⟩) = 0.25.

Exercise 3

Verify that RY(π) flips |0⟩ to |1⟩.

Exercise 4

Create a state with P(|1⟩) = 0.5 and a relative phase of π/2 on the |1⟩ component.

Exercise 5

Explain why RZ doesn’t change measurement probabilities.

Summary

Key takeaways:

  • Rotation gates give fine-grained control over quantum states
  • \(R_x\), \(R_y\), \(R_z\) rotate around different axes by angle θ
  • The formula uses θ/2 due to the geometry of the Bloch sphere
  • RY is best for controlling measurement probabilities
  • RZ controls phase without changing probabilities
  • P(θ) is equivalent to RZ(θ)

These gates give you the tools to create any single-qubit quantum state.

Official Documentation: Rotation gates are documented in the Qiskit circuit library. The PhaseGate (P) is equivalent to RZ. For the math behind rotations, see IBM’s transpilation documentation on gate decomposition.


Next: The Plus/Minus Basis and Basis Changes

The Plus/Minus Basis and Basis Changes

This chapter ties together everything you’ve learned about single-qubit gates and introduces the powerful concept of basis changes—a mental model that underlies many quantum algorithms.

The Four Cardinal Bases

Every single-qubit state can be expressed in any of these bases:

BasisStatesCreated By
Computational (Z)\(0\rangle\), \(
Plus/Minus (X)\(+\rangle\), \(
Circular (Y)\(+i\rangle\), \(

Understanding how to switch between bases is crucial for quantum algorithms.

The Computational Basis (Z Basis)

The default basis:

  • Eigenstates of the Z operator
  • \(|0\rangle\) and \(|1\rangle\)
  • Created by: \(I\) (for \(|0\rangle\)), \(X\) (for \(|1\rangle\))
from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector

# |0⟩ in Z basis
qc = QuantumCircuit(1)
print("|0⟩:", Statevector(qc))

# |1⟩ in Z basis
qc.x(0)
print("|1⟩:", Statevector(qc))

The Plus/Minus Basis (X Basis)

Eigenstates of the X operator:

  • \(|+\rangle = \frac{|0\rangle+|1\rangle}{\sqrt{2}}\)
  • \(|-\rangle = \frac{|0\rangle-|1\rangle}{\sqrt{2}}\)
  • Created by: \(H\) (for \(|+\rangle\)), \(XH\) or \(HX\) (for \(|-\rangle\))
# |+⟩ in X basis
qc = QuantumCircuit(1)
qc.h(0)
print("|+⟩:", Statevector(qc))

# |−⟩ in X basis
qc.x(0)
qc.h(0)
print("|−⟩:", Statevector(qc))

The Key Identity: HZH = X

This identity reveals the connection between bases:

\[HZH = X\]

What does this mean?

  • \(H\) changes from Z basis to X basis
  • \(Z\) acts (phase flip)
  • \(H\) changes back from X basis to Z basis
  • Net effect: bit flip!
from qiskit import QuantumCircuit
from qiskit.quantum_info import Operator

# Build HZH
qc_hzh = QuantumCircuit(1)
qc_hzh.h(0)
qc_hzh.z(0)
qc_hzh.h(0)

# Build X
qc_x = QuantumCircuit(1)
qc_x.x(0)

# Are they equal?
print("HZH == X:", Operator(qc_hzh) == Operator(qc_x))

General Basis Change Formula

In general, to transform an operation from one basis to another:

\[B \cdot \text{Operation} \cdot B^\dagger = \text{New Operation}\]

Where \(B\) is the basis change operation.

For the X-Z connection: \(HZH = X\)

Basis Changes in Practice

Problem: Apply X in the X basis

In the X basis, the eigenstates are \(|+\rangle\) and \(|-\rangle\). But we want to “flip” \(|+\rangle\) to \(|-\rangle\).

Solution: Change to Z basis, flip, change back.

# Flip |+⟩ to |−⟩
qc = QuantumCircuit(1)

# Start with |+⟩
qc.h(0)

# Change to Z basis, apply X, change back
qc.h(0)  # |+⟩ → |0⟩
qc.x(0)  # |0⟩ → |1⟩
qc.h(0)  # |1⟩ → |−⟩

print(Statevector(qc))

This is equivalent to applying Z in the original basis!

The Complete Picture: All Pauli Conjugates

The Hadamard gate transforms all Pauli gates:

GateTransform
\(H X H = Z\)X ↔ Z
\(H Y H = -Y\)Y stays Y (up to global phase)
\(H Z H = X\)Z ↔ X
from qiskit.quantum_info import Operator

# Verify all three
for op, name in [('x', 'X'), ('y', 'Y'), ('z', 'Z')]:
    qc = QuantumCircuit(1)
    qc.h(0)
    getattr(qc, op)(0)
    qc.h(0)
    
    x_qc = QuantumCircuit(1)
    if name == 'Y':
        x_qc.y(0)
        x_qc.x(0)
    else:
        x_qc.x(0)
    
    # We expect X → Z, Z → X, Y → Y
    print(f"H {name} H =", Operator(qc).simplify())

Why Basis Changes Matter

Basis changes are central to many quantum algorithms:

  1. Phase kickback: Use basis changes to move phase information
  2. Measurement basis: Measure in different bases to extract different information
  3. Algorithm design: QFT, Grover, and others use basis changes extensively

Measuring in Different Bases

You can measure in any basis by changing to that basis first:

from qiskit import QuantumCircuit
from qiskit.primitives import StatevectorSampler

# Measure |+⟩ in Z basis: 50-50
qc = QuantumCircuit(1)
qc.h(0)  # Create |+⟩
qc.measure_all()

sampler = StatevectorSampler()
result = sampler.run([qc], shots=100).result()
print("|+⟩ in Z basis:", result[0].data.meas.get_counts())

# Measure |+⟩ in X basis: always 0 (because |+⟩ is |0⟩ in X basis)
qc2 = QuantumCircuit(1)
qc2.h(0)  # Create |+⟩
qc2.h(0)  # Change to X basis for measurement
qc2.measure_all()

result2 = sampler.run([qc2], shots=100).result()
print("|+⟩ in X basis:", result2[0].data.meas.get_counts())

Summary of Single-Qubit Mastery

You now know all the single-qubit tools:

GateCodePurpose
Xx(q)Bit flip
Yy(q)Bit + phase flip
Zz(q)Phase flip
Hh(q)Create superposition / basis change
Pp(θ, q)Add phase
RXrx(θ, q)Rotation around x-axis
RYry(θ, q)Rotation around y-axis
RZrz(θ, q)Rotation around z-axis

Checkpoint Exercises

Exercise 1

Create \(|-\rangle\) starting from \(|0\rangle\) using only \(H\) and \(Z\).

Exercise 2

Verify that \(HZH = X\) on both \(|0\rangle\) and \(|1\rangle\).

Exercise 3

Measure \(|-\rangle\) in the Z basis. What do you expect? Verify with code.

Exercise 4

Design a circuit that flips \(|+\rangle\) to \(|-\rangle\).

Exercise 5

Explain in your own words why \(HZH = X\).

Summary

Key concepts:

  • Four main bases: Z (computational), X (plus/minus), Y (circular)
  • \(H\) connects Z and X bases
  • \(HZH = X\) shows the relationship between operations in different bases
  • You can measure in any basis by changing bases first
  • Basis changes are fundamental to quantum algorithms

With single-qubit gates mastered, you’re ready for the multi-qubit world.


Try These On QCoder


Next: Controlled Operations: CNOT

Controlled Operations: CNOT

This is where quantum computing gets interesting. The CNOT (Controlled-NOT) gate is your first two-qubit gate—it creates correlations between qubits that have no classical analog.

The CNOT Gate

The CNOT (Controlled-X) gate has two qubits:

  • Control qubit: The one that decides whether to act
  • Target qubit: The one that gets flipped
CNOT:  ┌───┐
control:─┤   ├──
         │ X │
target:──┤   ├──
         └───┘

The rule: **flip the target if and only if the control is \(|1\rangle\).

CNOT on Basis States

Let’s see what happens:

from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector

# Case 1: Control = 0, Target = 0 → No change
qc1 = QuantumCircuit(2)
qc1.cx(0, 1)  # CNOT with control=0, target=1
print("|00⟩ after CNOT:", Statevector(qc1))

# Case 2: Control = 1, Target = 0 → Flip target
qc2 = QuantumCircuit(2)
qc2.x(0)  # Set control to 1
qc2.cx(0, 1)
print("|10⟩ after CNOT:", Statevector(qc2))

# Case 3: Control = 0, Target = 1 → No change
qc3 = QuantumCircuit(2)
qc3.x(1)  # Set target to 1
qc3.cx(0, 1)
print("|01⟩ after CNOT:", Statevector(qc3))

# Case 4: Control = 1, Target = 1 → Flip target
qc4 = QuantumCircuit(2)
qc4.x([0, 1])  # Both are 1
qc4.cx(0, 1)
print("|11⟩ after CNOT:", Statevector(qc4))

Summary:

InputOutput
|00⟩|00⟩
|01⟩|01⟩
|10⟩|11⟩
|11⟩|10⟩

CNOT Syntax in Qiskit

qc.cx(control, target)

The first argument is the control qubit, the second is the target.

qc.cx(0, 1)  # Qubit 0 is control, qubit 1 is target
qc.cx(1, 0)  # Qubit 1 is control, qubit 0 is target

These are NOT the same! The control-target orientation matters.

CNOT on Superpositions

Here’s where it gets interesting:

# CNOT on superposition state
qc = QuantumCircuit(2)
qc.h(0)  # Put control in superposition
qc.cx(0, 1)

print("State:", Statevector(qc))

Output:

Statevector([0.70710678+0.j, 0.+0.j, 0.+0.j, 0.70710678+0.j],
            dims=(2, 2))

This is \(\frac{|00\rangle + |11\rangle}{\sqrt{2}}\)—a Bell state!

Reading the Statevector

For a 2-qubit statevector, the indices correspond to basis states:

IndexBasis StateValue
0|00⟩0.707…
1|01⟩0
2|10⟩0
3|11⟩0.707…

The state is \(\frac{|00\rangle + |11\rangle}{\sqrt{2}}\).

Why This Is Special

The state \(\frac{|00\rangle + |11\rangle}{\sqrt{2}}\) has a property that classical bits can’t replicate: quantum correlation.

If you measure qubit 0:

  • You see 0 with 50% probability → qubit 1 is also 0
  • You see 1 with 50% probability → qubit 1 is also 1

The outcomes are perfectly correlated, even though neither outcome is predetermined!

CNOT Is Not Reversible? Wrong!

Classical XOR is not reversible: 0 XOR 0 = 0, but so does 1 XOR 1 = 0.

Quantum CNOT is reversible:

  • Apply CNOT twice and you get back to the original state
  • The quantum operation is unitary
# CNOT twice = identity
qc = QuantumCircuit(2)
qc.h(0)
qc.cx(0, 1)
qc.cx(0, 1)

print("CNOT twice:", Statevector(qc))

Circuit Diagrams

Qiskit prints readable circuit diagrams:

qc = QuantumCircuit(2)
qc.h(0)
qc.cx(0, 1)
print(qc)
     ┌───┐
q_0: ┤ H ├──■──
     └───┘  │
q_1: ──────■──

The ■ is the control, the ⊕ is the target (often shown as ■───⊕───).

More Complex CNOT Patterns

CNOT Chain

# Create a GHZ state (Greenberger-Horne-Zeilinger)
qc = QuantumCircuit(3)
qc.h(0)
qc.cx(0, 1)
qc.cx(1, 2)
print(Statevector(qc))

This creates \(\frac{|000\rangle + |111\rangle}{\sqrt{2}}\).

CNOT Fan-Out

# One control, multiple targets
qc = QuantumCircuit(3)
qc.h(0)
qc.cx(0, 1)
qc.cx(0, 2)
print(Statevector(qc))

This creates \(\frac{|000\rangle + |110\rangle}{\sqrt{2}}\)? No wait—verify this yourself!

Checkpoint Exercises

Exercise 1

Apply CNOT to \(|01\rangle\). What state do you get?

Exercise 2

Apply CNOT to \(|10\rangle\). What state do you get?

Exercise 3

Create the Bell state \(\frac{|01\rangle + |10\rangle}{\sqrt{2}}\). (Hint: start with the right basis state)

Exercise 4

Create the GHZ state \(\frac{|000\rangle + |111\rangle}{\sqrt{2}}\) on three qubits.

Exercise 5

Show that CNOT is its own inverse (applying it twice returns to the original state).

Common Mistakes

MistakeResult
cx(1, 0) instead of cx(0, 1)Wrong correlation direction
Forgetting to put control in superpositionNo entanglement, just classical correlation
Measuring before CNOTDestroys superposition before entanglement forms

Summary

Key concepts:

  • CNOT flips the target if control is \(|1\rangle\)
  • Syntax: cx(control, target) in Qiskit
  • On superposition: creates Bell states
  • CNOT is reversible (its own inverse)
  • Creates quantum correlations (entanglement)

The CNOT gate is the workhorse of two-qubit quantum circuits. Master it, and everything else becomes easier.

Official Documentation: The CNOT (Controlled-X) gate is documented in the Qiskit circuit library. See also IBM’s tutorial on multi-qubit circuits.


Next: Entanglement and Bell States

Entanglement and Bell States

Entanglement is perhaps the most distinctly quantum phenomenon. It’s what makes quantum computers fundamentally different from classical computers.

What Is Entanglement?

Two qubits are entangled if their joint state cannot be written as a product of individual states.

Separable (not entangled): \[|\psi\rangle = |\phi\rangle \otimes |\chi\rangle\]

Entangled: \[|\psi\rangle \neq |\phi\rangle \otimes |\chi\rangle\]

Example of entangled state: \[\frac{|00\rangle + |11\rangle}{\sqrt{2}} \neq \left(\frac{|0\rangle + |1\rangle}{\sqrt{2}}\right) \otimes \left(\frac{|0\rangle + |1\rangle}{\sqrt{2}}\right)\]

The Four Bell States

The Bell states are the four maximal entangled two-qubit states:

NameDefinitionCreated By
\(\Phi^+\rangle\)\(\frac{
\(\Phi^-\rangle\)\(\frac{
\(\Psi^+\rangle\)\(\frac{
\(\Psi^-\rangle\)\(\frac{

Creating the Four Bell States

from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector

def bell_state(variant='+'):
    """Create one of the four Bell states."""
    qc = QuantumCircuit(2)
    
    if variant in ['Φ⁺', 'phi+', '+', 0]:
        # |Φ⁺⟩ = (|00⟩ + |11⟩) / √2
        qc.h(0)
        qc.cx(0, 1)
        
    elif variant in ['Φ⁻', 'phi-', '-', 1]:
        # |Φ⁻⟩ = (|00⟩ - |11⟩) / √2
        qc.h(0)
        qc.cx(0, 1)
        qc.z(0)
        
    elif variant in ['Ψ⁺', 'psi+', 2]:
        # |Ψ⁺⟩ = (|01⟩ + |10⟩) / √2
        qc.h(0)
        qc.cx(0, 1)
        qc.x(0)
        
    elif variant in ['Ψ⁻', 'psi-', 3]:
        # |Ψ⁻⟩ = (|01⟩ - |10⟩) / √2
        qc.h(0)
        qc.cx(0, 1)
        qc.z(0)
        qc.x(0)
    
    return qc

# Create and display each Bell state
for variant in ['Φ⁺', 'Φ⁻', 'Ψ⁺', 'Ψ⁻']:
    qc = bell_state(variant)
    state = Statevector(qc)
    print(f"{variant}: {state}")

Visualizing Entangled States

Unlike single-qubit states, entangled states cannot be visualized on individual Bloch spheres—each qubit’s state depends on the other. However, we can still visualize the individual qubit states to understand the correlations:

from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector
from qiskit.visualization import plot_bloch_multivector

# |Φ⁺⟩ = (|00⟩ + |11⟩)/√2 - correlated states
qc = QuantumCircuit(2)
qc.h(0)
qc.cx(0, 1)
state = Statevector(qc)
print("|Φ⁺⟩ state:")
print(state)
display(plot_bloch_multivector(state))

# |Ψ⁺⟩ = (|01⟩ + |10⟩)/√2 - anticorrelated
qc = bell_state('Ψ⁺')
state = Statevector(qc)
print("|Ψ⁺⟩ state:")
print(state)
display(plot_bloch_multivector(state))

Notice that when you measure one qubit of an entangled state, the other qubit’s state is immediately determined!

What Makes Entanglement Special?

1. Perfect Correlation

If you measure one qubit of \(|\Phi^+\rangle\), you know the other instantly:

Measure qubit 0 → |0⟩  ⇒  qubit 1 is |0⟩
Measure qubit 0 → |1⟩  ⇒  qubit 1 is |1⟩

This is true regardless of distance between the qubits.

2. No Local Hidden Variables

Classical intuition says: the correlation must come from:

  • Pre-determined outcomes, or
  • Hidden signals between particles

Quantum mechanics says: neither is true. The outcomes aren’t determined until measurement.

3. Cannot Be Copied

You cannot clone an entangled state: \[|\psi\rangle \otimes |\phi\rangle \neq |\psi\rangle \otimes |\psi\rangle\]

This is the no-cloning theorem—a fundamental consequence of quantum mechanics.

Measuring Bell States

Let’s see the correlation in action:

from qiskit import QuantumCircuit
from qiskit.primitives import StatevectorSampler

# Create |Φ⁺⟩
qc = QuantumCircuit(2)
qc.h(0)
qc.cx(0, 1)
qc.measure_all()

# Sample many times
sampler = StatevectorSampler()
result = sampler.run([qc], shots=1000).result()
print(result[0].data.meas.get_counts())

Typical output (always the same pattern):

{'00': 498, '11': 502}

Only 00 and 11 appear. Never 01 or 10.

Changing the Correlation

Different Bell states give different correlations:

# |Φ⁻⟩ = (|00⟩ - |11⟩) / √2
qc = QuantumCircuit(2)
qc.h(0)
qc.cx(0, 1)
qc.z(0)  # This flips the phase
qc.measure_all()

result = sampler.run([qc], shots=1000).result()
print("|Φ⁻⟩:", result[0].data.meas.get_counts())

The counts are the same (50-50 on 00 and 11), but the phase is different.

Verifying Entanglement

How do you know if a state is entangled? One test: try to write it as a product state.

from qiskit.quantum_info import Statevector

# This IS separable: (|0⟩ + |1⟩)/√2 ⊗ (|0⟩ + |1⟩)/√2
state1 = Statevector(QuantumCircuit(2))
print("Separable:", state1)

# This is NOT separable: |Φ⁺⟩
qc = QuantumCircuit(2)
qc.h(0)
qc.cx(0, 1)
state2 = Statevector(qc)
print("Entangled:", state2)

For two qubits, you can check if a state is entangled by looking at the reduced density matrix—but that’s a topic for later.

Entanglement in Multi-Qubit Systems

GHZ State (3 qubits)

\[\frac{|000\rangle + |111\rangle}{\sqrt{2}}\]

qc = QuantumCircuit(3)
qc.h(0)
qc.cx(0, 1)
qc.cx(1, 2)
print(Statevector(qc))

W State (3 qubits)

\[\frac{|100\rangle + |010\rangle + |001\rangle}{\sqrt{3}}\]

This is a different kind of entanglement—all three qubits are involved, but no pair alone determines the third.

Why Entanglement Matters

Entanglement is the resource for:

  1. Quantum teleportation — transferring quantum states
  2. Superdense coding — sending 2 classical bits with 1 quantum bit
  3. Quantum key distribution — secure communication
  4. Quantum algorithms — speedups in computation

Without entanglement, quantum computers would be no more powerful than classical computers.

Common Misconceptions

MisconceptionReality
“Entanglement means information travels instantly”No information is transferred. Measurement outcomes are random.
“Entanglement is like correlation in classical systems”Classical correlation can’t explain quantum violations of Bell inequalities
“Entanglement proves quantum weirdness”Entanglement is a consequence of superposition + multi-qubit gates

Checkpoint Exercises

Exercise 1

Create all four Bell states and verify their statevectors.

Exercise 2

Measure a Bell state 1000 times. What patterns do you see?

Exercise 3

Create the GHZ state and measure it. What are the possible outcomes?

Exercise 4

Explain why \(|\Phi^+\rangle\) cannot be written as a product of two single-qubit states.

Exercise 5

Create the W state \(\frac{|100\rangle + |010\rangle + |001\rangle}{\sqrt{3}}\) on three qubits.

Summary

Key concepts:

  • Entanglement: states that cannot be written as products
  • Four Bell states: \(|\Phi^\pm\rangle\) and \(|\Psi^\pm\rangle\)
  • Perfect correlations in measurement outcomes
  • Cannot be explained by classical physics
  • No cloning possible
  • Resource for quantum communication and algorithms

Entanglement is what makes quantum computing special. Understanding Bell states prepares you for the circuit patterns that power quantum algorithms.

Official Documentation: For more on entanglement and Bell states, see IBM’s entanglement tutorial. The Qiskit BellState circuit provides a convenient way to create Bell states.


Try These On QCoder


Next: More Two-Qubit Gates: CZ, SWAP

More Two-Qubit Gates: CZ, SWAP

Beyond CNOT, Qiskit provides several useful two-qubit gates. We’ll cover the most important ones: CZ (Controlled-Z) and SWAP.

The CZ Gate (Controlled-Z)

The CZ gate applies a Z operation when the control is \(|1\rangle\):

\[CZ|ij\rangle = (-1)^{ij}|ij\rangle\]

InputOutput
|00⟩|00⟩
|01⟩|01⟩
|10⟩|10⟩
|11⟩-|11⟩
from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector

# CZ on |11⟩ gives -|11⟩
qc = QuantumCircuit(2)
qc.x([0, 1])  # Prepare |11⟩
qc.cz(0, 1)

print("CZ|11⟩:", Statevector(qc))

Output: \(-|11\rangle\)

CZ Creates Phase Entanglement

On superposition states, CZ creates phase correlations:

# Create superposition then apply CZ
qc = QuantumCircuit(2)
qc.h(0)
qc.h(1)
qc.cz(0, 1)

print("CZ on superposition:", Statevector(qc))

The \(|11\rangle\) component picks up a minus sign.

CZ vs CNOT

GateEffect on |11⟩Effect on |10⟩Effect on |01⟩
CNOT|10⟩ (flip)|11⟩ (flip)|01⟩ (no change)
CZ-|11⟩ (phase)|10⟩ (no change)|01⟩ (no change)

The SWAP Gate

The SWAP gate exchanges the states of two qubits:

\[SWAP|ij\rangle = |ji\rangle\]

InputOutput
|00⟩|00⟩
|01⟩|10⟩
|10⟩|01⟩
|11⟩|11⟩
from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector

# SWAP |01⟩ → |10⟩
qc = QuantumCircuit(2)
qc.x(1)  # |01⟩
qc.swap(0, 1)
print("SWAP|01⟩:", Statevector(qc))

Implementing SWAP with CNOTs

SWAP can be decomposed into three CNOTs:

def swap_via_cnot(qc, a, b):
    """Swap qubits a and b using three CNOTs."""
    qc.cx(a, b)
    qc.cx(b, a)
    qc.cx(a, b)

# Verify it's equivalent to SWAP
qc1 = QuantumCircuit(2)
qc1.swap(0, 1)

qc2 = QuantumCircuit(2)
swap_via_cnot(qc2, 0, 1)

from qiskit.quantum_info import Operator
print("SWAP == 3×CNOT:", Operator(qc1) == Operator(qc2))

Other Two-Qubit Gates

iSWAP

Performs a swap with an additional phase:

\[iSWAP|01\rangle = i|10\rangle\] \[iSWAP|10\rangle = i|01\rangle\]

qc = QuantumCircuit(2)
qc.iswap(0, 1)

RZZ (Controlled-RZ)

A controlled rotation around the Z axis:

from math import pi
qc = QuantumCircuit(2)
qc.rzz(pi/4, 0, 1)

Controlled-H

Hadamard controlled on one qubit:

qc = QuantumCircuit(2)
qc.ch(0, 1)  # Controlled-H

Gate Decompositions

Many “native” gates are actually decomposed into simpler gates on real hardware:

# CZ can be made from CNOT + H on target
qc = QuantumCircuit(2)
qc.h(1)  # H on target
qc.cx(0, 1)
qc.h(1)  # H on target

# Verify: CZ == H-CNOT-H
from qiskit.quantum_info import Operator
print("H-CNOT-H == CZ:", Operator(qc) == Operator(QuantumCircuit(2, circuit=qc).compose(QuantumCircuit(2))))

Actually, let’s verify this correctly:

cz = QuantumCircuit(2)
cz.cz(0, 1)

h_cnot_h = QuantumCircuit(2)
h_cnot_h.h(1)
h_cnot_h.cx(0, 1)
h_cnot_h.h(1)

print("CZ == H-CNOT-H:", Operator(cz) == Operator(h_cnot_h))

Summary of Two-Qubit Gates

GateSymbolEffectCode
CNOT■───⊕Flip target if control=1cx(c, t)
CZ■───■Phase flip if both=1cz(c, t)
SWAP×───×Exchange qubit statesswap(a, b)
iSWAPiSWAPSwap with phaseiswap(a, b)

Checkpoint Exercises

Exercise 1

Create a circuit that swaps \(|10\rangle\) to \(|01\rangle\).

Exercise 2

Show that CZ on \(|++\rangle\) (both qubits in plus state) gives a phase difference.

Exercise 3

Verify that SWAP = CNOT(0,1) CNOT(1,0) CNOT(0,1).

Exercise 4

Use CZ to create a phase-entangled state starting from \(|++\rangle\).

Exercise 5

Create a circuit that applies CZ only when both qubits are in \(|1\rangle\).

Summary

Key concepts:

  • CZ adds phase when both qubits are \(|1\rangle\)
  • SWAP exchanges qubit states
  • Many gates are equivalent under basis transformations
  • Gate decomposition matters for hardware implementation

With two-qubit gates mastered, you’re ready to build complex circuits and patterns.

Official Documentation: The CZ and SWAP gates are documented in the Qiskit circuit library and SWAPGate.


Try These On QCoder


Next: State Preparation Patterns

State Preparation Patterns

State preparation is the art of designing circuits that produce specific quantum states. This chapter covers the most common patterns you’ll encounter.

Pattern 1: Basis States

The simplest pattern: flip qubits to match the target basis state.

To prepare \(|101\rangle\):

from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector

qc = QuantumCircuit(3)
qc.x(0)  # q0 = 1 (rightmost)
qc.x(2)  # q2 = 1 (leftmost)
print(Statevector(qc))

Remember: qubit 0 is the rightmost (least significant).

Pattern 2: Uniform Superposition

Apply H to all qubits:

def uniform_superposition(n):
    qc = QuantumCircuit(n)
    for i in range(n):
        qc.h(i)
    return qc

# Test on 4 qubits
qc = uniform_superposition(4)
state = Statevector(qc)
print(state)

This creates \(\frac{1}{\sqrt{2^n}}\sum_{x=0}^{2^n-1}|x\rangle\).

Pattern 3: Superposition with Specific Weights

Use RY to control probabilities:

from math import pi, asin, sqrt

def prepare_amplitudes(amplitudes):
    """Prepare a 1-qubit state with given amplitudes."""
    # amplitudes = [alpha, beta]
    alpha, beta = amplitudes
    theta = 2 * asin(abs(beta))
    qc = QuantumCircuit(1)
    qc.ry(theta, 0)
    if beta.real < 0:
        qc.z(0)
    return qc

# Create (√(3)/2)|0⟩ + (1/2)|1⟩
qc = prepare_amplitudes([sqrt(3)/2, 0.5])
print(Statevector(qc))

Pattern 4: Two-Qubit Routing

For states like \(\sqrt{\frac{3}{4}}|00\rangle + \frac{1}{2}|11\rangle\):

from math import pi

qc = QuantumCircuit(2)
# Use RY to set the branch split
qc.ry(pi/3, 0)  # cos(π/6)² = 3/4, sin(π/6)² = 1/4
# Copy the branch to qubit 1
qc.cx(0, 1)
print(Statevector(qc))

The RY sets probabilities, then CNOT routes them.

Pattern 5: Unequal Superposition

Create superpositions over a subset of states:

# (|10⟩ + |11⟩)/√2
qc = QuantumCircuit(2)
qc.h(0)  # Creates (|0⟩ + |1⟩)/√2 on qubit 0
qc.x(1)  # Set qubit 1 to |1⟩
# Result: (|01⟩ + |11⟩)/√2? No! Check with statevector
print(Statevector(qc))

Try this and verify. Then figure out how to get \((|10\rangle + |11\rangle)/\sqrt{2}\).

Pattern 6: Sign-Encoded Information

Add phases to distinguish states:

# (|00⟩ - |11⟩)/√2
qc = QuantumCircuit(2)
qc.h(0)
qc.cx(0, 1)
qc.z(0)
print(Statevector(qc))

Sign differences are invisible in immediate measurement but visible in interference.

Pattern 7: Multi-Qubit GHZ and W States

GHZ State

def ghz_state(n):
    qc = QuantumCircuit(n)
    qc.h(0)
    for i in range(n-1):
        qc.cx(i, i+1)
    return qc

qc = ghz_state(5)
print(Statevector(qc))

Creates \(\frac{|0…0\rangle + |1…1\rangle}{\sqrt{2}}\).

W State

def w_state(n):
    """Create (|100...0⟩ + |010...0⟩ + ... + |000...1⟩)/√n"""
    qc = QuantumCircuit(n)
    # More complex—requires multi-controlled rotations
    # We'll cover this later
    return qc

The W state is more complex to construct. For now, trust that it exists.

The State Preparation Checklist

When designing a state preparation circuit:

  1. Name the target state in mathematical notation
  2. Identify probabilities for each basis state
  3. Use RY to set branch probabilities
  4. Use CNOT to route amplitude to correct basis states
  5. Add phases with Z or CZ to distinguish states
  6. Verify with Statevector before proceeding

Manual vs Convenience Methods

Qiskit has QuantumCircuit.initialize():

from math import sqrt

qc = QuantumCircuit(2)
qc.initialize([sqrt(3)/2, 0, 0, 0.5])
print(Statevector(qc))

This works but hides the circuit structure. Manual construction teaches more.

Checkpoint Exercises

Exercise 1

Prepare \(\frac{|00\rangle + |01\rangle + |10\rangle}{\sqrt{3}}\) (3-way superposition on 2 qubits)

Exercise 2

Prepare \(|010\rangle\) on 3 qubits

Exercise 3

Prepare \(\frac{|00\rangle + |11\rangle}{\sqrt{2}}\)

Exercise 4

Prepare \(\frac{|100\rangle + |010\rangle + |001\rangle}{\sqrt{3}}\) (W state)

Exercise 5

Design a state preparation for \((|000\rangle + |111\rangle)/\sqrt{2}\)

Summary

Key patterns:

  • Basis states: flip qubits with X
  • Superposition: apply H gates
  • Unequal amplitudes: use RY
  • Routing: use CNOT
  • Phases: use Z/CZ

State preparation is foundational for everything that follows.


Try These On QCoder


Next: Phase Kickback: Encoding in Phase

Phase Kickback: Encoding in Phase

Phase kickback is one of the most elegant tricks in quantum computing. It lets you transfer information from control qubits to phases—enabling many quantum algorithms.

The Core Idea

When a controlled operation acts on an eigenstate of the target operation, the control qubit picks up the eigenvalue as a phase.

In simpler terms: a controlled operation can move phase information from the target to the control.

The Simplest Example

The \(|-\rangle\) state is an eigenstate of X:

\[X|-\rangle = -|-\rangle\]

This means:

from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector

# Prepare |-⟩ on qubit 1
qc = QuantumCircuit(2)
qc.x(1)      # |1⟩
qc.h(1)      # H|1⟩ = |−⟩

# Apply CNOT with |−⟩ as target
qc.cx(0, 1)

print(Statevector(qc))

The \(|1\rangle\) component on qubit 0 picks up a minus sign!

What Happened?

Before: \[|0\rangle \otimes |-\rangle + |1\rangle \otimes |-\rangle = (|0\rangle + |1\rangle) \otimes |-\rangle\]

After CNOT: \[|0\rangle \otimes |-\rangle + |1\rangle \otimes X|-\rangle = |0\rangle \otimes |-\rangle - |1\rangle \otimes |-\rangle = (|0\rangle - |1\rangle) \otimes |-\rangle\]

The control qubit now has the phase!

The Key Identity

Remember from the basis change chapter:

\[HZH = X\]

This means:

  • \(|+\rangle\) is the \(+1\) eigenstate of \(Z\) (eigenvalue \(+1\))
  • \(|-\rangle\) is the \(-1\) eigenstate of \(Z\) (eigenvalue \(-1\))

And:

  • \(|0\rangle\) is the \(+1\) eigenstate of \(Z\) (eigenvalue \(+1\))
  • \(|1\rangle\) is the \(-1\) eigenstate of \(Z\) (eigenvalue \(-1\))

Phase Kickback with Z

Using \(Z|1\rangle = -|1\rangle\):

# Kickback with Z
qc = QuantumCircuit(2)
qc.h(0)      # Put control in superposition
qc.x(1)      # Prepare |1⟩ (eigenstate of Z with eigenvalue -1)
qc.cz(0, 1) # Controlled-Z

print(Statevector(qc))

The \(|1\rangle\) branch on qubit 0 picks up a minus sign.

Why Is This Useful?

Phase kickback lets you:

  1. Encode classical information in phases
  2. Transfer phase information between qubits
  3. Create controlled operations from simpler ones
  4. Build oracles for Grover’s algorithm

A Concrete Application: Phase Oracle

A phase oracle marks a target state \(|w\rangle\) by flipping its phase:

\[O_w|x\rangle = \begin{cases} -|x\rangle & \text{if } x = w \ |x\rangle & \text{otherwise} \end{cases}\]

For \(|w\rangle = |11\rangle\):

# Phase oracle for |11⟩
qc = QuantumCircuit(2)
qc.h(0)
qc.h(1)  # Put both in superposition
qc.cz(0, 1)  # Mark |11⟩
qc.h(0)
qc.h(1)

print(Statevector(qc))

Try this and verify that \(|11\rangle\) gets the minus sign.

The Phase Kickback Pattern

The standard phase kickback pattern:

def phase_kickback(n_qubits, eigenstate_prep, controlled_op):
    """Generic phase kickback circuit."""
    qc = QuantumCircuit(n_qubits)
    
    # 1. Put control qubits in superposition
    for i in range(n_qubits - 1):
        qc.h(i)
    
    # 2. Prepare eigenstate on ancilla
    eigenstate_prep(qc)
    
    # 3. Apply controlled operation
    controlled_op(qc)
    
    return qc

Basis Changes Reveal Phase

After phase kickback, you can’t see the phase in computational basis measurement. Change to X basis:

# Create phase, then measure in X basis
qc = QuantumCircuit(2)
qc.h(0)
qc.x(1)
qc.cz(0, 1)

# Measure in X basis
qc.h(0)  # Change to X basis
qc.measure_all()

from qiskit.primitives import StatevectorSampler
sampler = StatevectorSampler()
result = sampler.run([qc], shots=100).result()
print(result[0].data.meas.get_counts())

The phase becomes a bit flip in the X basis.

Checkpoint Exercises

Exercise 1

Show that \(HZH = X\) using phase kickback logic.

Exercise 2

Create a circuit where qubit 0 picks up phase \(\pi/2\) (not \(\pi\)).

Exercise 3

Build a phase oracle that marks \(|00\rangle\).

Exercise 4

Explain why \(|+\rangle\) and \(|-\rangle\) measure the same in Z basis but differently in X basis.

Exercise 5

Design a circuit that transfers phase from qubit A to qubit B.

Summary

Key concepts:

  • Phase kickback: controlled operations on eigenstates create phase on controls
  • \(|-\rangle\) is the \(-1\) eigenstate of \(X\)
  • \(|1\rangle\) is the \(-1\) eigenstate of \(Z\)
  • Phase kickback enables oracles and many algorithms

Phase kickback is fundamental to understanding Grover’s algorithm and quantum phase estimation.


Try These On QCoder


Next: Reversible Logic and Oracles

Reversible Logic and Oracles

Quantum circuits must be reversible—you can always run them backward. This chapter teaches you to think about quantum computation as reversible transformation.

Why Reversibility Matters

Classical computing:

  • AND: 2 inputs → 1 output (information loss)
  • NOT: 1 input → 1 output (reversible)

Quantum computing:

  • All gates must be unitary (reversible)
  • Uncomputing: computing a result, using it, then uncomputing

Bit-Flip Oracles

A bit-flip oracle flips an ancilla qubit when a condition is met:

\[|x\rangle|b\rangle \rightarrow |x\rangle|b \oplus f(x)\rangle\]

from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector

# Oracle that flips ancilla when x = 11
def oracle_11():
    qc = QuantumCircuit(3)  # x[0], x[1], ancilla
    qc.ccx(0, 1, 2)  # Toffoli: flip ancilla if x0=1 AND x1=1
    return qc

# Test on |11⟩
qc = QuantumCircuit(3)
qc.x([0, 1])  # |11⟩
qc.x(2)        # ancilla = |1⟩
qc.compose(oracle_11(), inplace=True)
print(Statevector(qc))

The ancilla flips from \(|1\rangle\) to \(|0\rangle\).

Phase Oracles

A phase oracle marks states with a phase:

\[|x\rangle \rightarrow (-1)^{f(x)}|x\rangle\]

# Phase oracle for |11⟩
def phase_oracle_11():
    qc = QuantumCircuit(2)
    qc.cz(0, 1)  # Flip phase of |11⟩
    return qc

Phase oracles are simpler because they don’t need an ancilla.

Converting Bit-Flip to Phase

Use the \(|-\rangle\) ancilla trick:

# Convert bit-flip oracle to phase oracle
def bitflip_to_phase(oracle):
    """Wrap a bit-flip oracle with |-⟩ ancilla."""
    qc = QuantumCircuit(oracle.num_qubits + 1)
    
    # Prepare |-⟩ on ancilla (last qubit)
    qc.x(oracle.num_qubits)
    qc.h(oracle.num_qubits)
    
    # Apply oracle
    qc.compose(oracle, inplace=True)
    
    # Uncompute |-⟩ preparation
    qc.h(oracle.num_qubits)
    qc.x(oracle.num_qubits)
    
    return qc

# Test
original = oracle_11()
converted = bitflip_to_phase(original)
print(Operator(original))
print(Operator(converted))

Reversible Arithmetic

Classical operations can be made reversible:

# Reversible XOR (CNOT)
qc = QuantumCircuit(2)
qc.cx(0, 1)  # q1 = q1 XOR q0
# To reverse: apply again
qc.cx(0, 1)  # Back to original
# Reversible AND (Toffoli/CCX)
qc = QuantumCircuit(3)
qc.ccx(0, 1, 2)  # q2 = q2 AND q1 AND q0
# Toffoli is self-inverse
qc.ccx(0, 1, 2)  # Back to original

The Uncomputation Pattern

  1. Compute auxiliary values
  2. Use the values
  3. Uncompute to restore them
# Compute f(x), use it, uncompute
def use_reversibly():
    qc = QuantumCircuit(4)
    
    # Compute: ancilla = f(x)
    qc.ccx(0, 1, 3)  # ancilla = x0 AND x1
    
    # Use ancilla (e.g., controlled operation)
    qc.ccx(3, 2, 3)  # something using ancilla
    
    # Uncompute: restore ancilla to |0⟩
    qc.ccx(0, 1, 3)  # Uncompute
    
    return qc

Uncomputing Is Not Optional

If you don’t uncompute:

  • Work qubits stay entangled with data
  • Noise accumulates
  • Algorithm correctness fails

Always uncompute when done!

Checkpoint Exercises

Exercise 1

Build a phase oracle that marks \(|01\rangle\).

Exercise 2

Build a phase oracle that marks \(|100\rangle\).

Exercise 3

Convert a bit-flip oracle for \(|11\rangle\) to a phase oracle.

Exercise 4

Implement a reversible XOR circuit and verify it uncomputes correctly.

Exercise 5

Design a reversible circuit that computes \(|x, y\rangle \rightarrow |x, y \oplus x\rangle\).

Summary

Key concepts:

  • Bit-flip oracles: flip ancilla based on input
  • Phase oracles: mark states with phase
  • Conversion via \(|-\rangle\) ancilla
  • All quantum operations are reversible
  • Uncompute auxiliary qubits after use

Oracles are the building blocks of Grover’s algorithm.


Try These On QCoder


Next: Reflections and Amplitude Amplification

Reflections and Amplitude Amplification

Reflections are the second ingredient of Grover’s algorithm. A reflection flips the sign of a state while leaving orthogonal states unchanged—the quantum equivalent of “reflecting” in a mirror.

What Is a Reflection?

A reflection operator \(R\) about a state \(|\psi\rangle\):

\[R = I - 2|\psi\rangle\langle\psi|\]

Properties:

  • \(R|\psi\rangle = -|\psi\rangle\) (the target state flips sign)
  • \(R|\phi\rangle = |\phi\rangle\) for all \(|\phi\rangle \perp |\psi\rangle\)
  • \(R^2 = I\) (reflections are their own inverse)

A Simple Reflection: Z Gate

On one qubit, \(Z = I - 2|1\rangle\langle 1|\):

  • \(Z|1\rangle = -|1\rangle\)
  • \(Z|0\rangle = |0\rangle\)

This is a reflection about the \(|0\rangle\) state.

Reflection About Any State

To reflect about \(|\psi\rangle\):

  1. Prepare \(|\psi\rangle\) from \(|0\rangle\)
  2. Apply \(Z\) on ancilla
  3. Unprepare \(|\psi\rangle\)

\[R_\psi = U(I - 2|0\rangle\langle 0|)U^\dagger = I - 2|\psi\rangle\langle\psi|\]

where \(U|0\rangle = |\psi\rangle\).

Example: Reflect About \(|+\rangle\)

from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector

# Reflection about |+⟩
# Since H|0⟩ = |+⟩, we have: H Z H = X = I - 2|+⟩⟨+|
qc = QuantumCircuit(1)
qc.h(0)
qc.z(0)
qc.h(0)

# Verify: this equals X
from qiskit.quantum_info import Operator
print("X:", Operator(QuantumCircuit(1, lambda qc: qc.x(0))))
print("H Z H:", Operator(qc))
print("Equal:", Operator(QuantumCircuit(1, lambda qc: qc.x(0))) == Operator(qc))

Reflection About Superposition States

Reflection about \(|+\rangle\) flips \(|-\rangle\) but leaves \(|+\rangle\) unchanged.

Try it:

qc = QuantumCircuit(1)
qc.x(0)  # |−⟩
qc.h(0)
qc.z(0)
qc.h(0)
print("Flip |−⟩:", Statevector(qc))

The Diffusion Operator

Grover’s diffusion operator reflects about the uniform superposition:

\[D = H^{\otimes n}(I - 2|0\rangle\langle 0|^{\otimes n})H^{\otimes n} = I - 2\sum_x|x\rangle\langle x|/N\]

def diffusion(n):
    qc = QuantumCircuit(n)
    
    # Reflect about |0...0⟩
    qc.x(range(n))
    qc.h(n-1)
    qc.mcx(list(range(n-1)), n-1)  # Multi-controlled Z
    qc.h(n-1)
    qc.x(range(n))
    
    # Equivalently: H X X ... X H
    return qc

Amplitude Amplification

Combining oracle + diffusion:

  1. Oracle marks target states (flips sign)
  2. Diffusion reflects about uniform superposition
  3. Target amplitudes grow
from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector

def amplitude_amplification_1iter(n, target):
    qc = QuantumCircuit(n)
    
    # Start in uniform superposition
    qc.h(range(n))
    
    # Oracle: mark target state
    for i, bit in enumerate(target):
        if bit == '1':
            qc.x(i)
    qc.h(n-1)
    qc.mcx(list(range(n-1)), n-1)
    qc.h(n-1)
    for i, bit in enumerate(target):
        if bit == '1':
            qc.x(i)
    
    # Diffusion
    qc.h(range(n))
    qc.x(range(n))
    qc.h(n-1)
    qc.mcx(list(range(n-1)), n-1)
    qc.h(n-1)
    qc.x(range(n))
    qc.h(range(n))
    
    return qc

# Test on 2 qubits, target |11⟩
qc = amplitude_amplification_1iter(2, '11')
state = Statevector(qc)
print("After 1 iteration:", state)
print("P(|11⟩):", state.probabilities()[3])

Checkpoint Exercises

Exercise 1

Verify that Z is a reflection about \(|0\rangle\).

Exercise 2

Build a reflection about \(|11\rangle\) on 2 qubits.

Exercise 3

Explain why \(HZH = X\) is a reflection about \(|+\rangle\).

Exercise 4

Design a reflection about \((|00\rangle + |11\rangle)/\sqrt{2}\).

Exercise 5

Show that two reflections in succession amplify the marked amplitude.

Summary

Key concepts:

  • Reflections flip sign of target state
  • \(Z\) reflects about \(|0\rangle\)
  • \(X\) reflects about \(|+\rangle\)
  • Diffusion reflects about uniform superposition
  • Oracle + Diffusion = Amplitude Amplification

Reflections are the mechanism behind Grover’s search speedup.


Try These On QCoder


Next: Grover Search

Grover Search

Grover’s algorithm is the most famous quantum search algorithm. It finds a marked item in an unsorted database of \(N\) items in \(O(\sqrt{N})\) steps— quadratically faster than classical search.

The Problem

Given an unsorted list of \(N\) items, find the one marked “good.”

Classically: \(O(N)\) operations.

With Grover: \(O(\sqrt{N})\) operations.

The Quantum Insight

Rather than searching through items one by one, Grover’s algorithm:

  1. Creates uniform superposition over all items
  2. Uses an oracle to mark the target
  3. Amplifies the marked state’s amplitude (amplitude amplification)

The Circuit Structure

┌─────────────────────────────────────────────────────────────┐
│                                                             │
│  H⊗n ──[Oracle]── H⊗n ──[Diffusion]── H⊗n ──[Oracle]── ...│
│                                                             │
│              One Grover Iteration                           │
└─────────────────────────────────────────────────────────────┘

Building Grover’s Algorithm

Step 1: Uniform Superposition

from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector
from math import pi

n = 2  # 2 qubits = 4 items
qc = QuantumCircuit(n)
qc.h(range(n))
print("Uniform superposition:", Statevector(qc))

Step 2: Oracle (Mark the Target)

For target \(|11\rangle\):

def oracle_11():
    qc = QuantumCircuit(n)
    qc.cz(0, 1)  # Flip phase of |11⟩
    return qc

qc.compose(oracle_11(), inplace=True)
print("After oracle:", Statevector(qc))

Step 3: Diffusion (Amplify)

def diffusion():
    qc = QuantumCircuit(n)
    qc.h(range(n))
    qc.x(range(n))
    qc.h(n-1)
    qc.mcx(list(range(n-1)), n-1)
    qc.h(n-1)
    qc.x(range(n))
    qc.h(range(n))
    return qc

qc.compose(diffusion(), inplace=True)
print("After diffusion:", Statevector(qc))

Step 4: Measure

qc.measure_all()
from qiskit.primitives import StatevectorSampler
sampler = StatevectorSampler()
result = sampler.run([qc], shots=256).result()
print(result[0].data.meas.get_counts())

The target \(|11\rangle\) should dominate.

Understanding the Amplification

| Stage | \(|11\rangle\) Amplitude | |—––|————————| | Start (uniform) | \(1/\sqrt{4} = 0.5\) | | After oracle | \(-0.5\) (flipped sign) | | After diffusion | \(\approx 1.0\) (amplified) |

The diffusion step reflects about the mean amplitude, boosting the negative amplitude.

Optimal Iteration Count

For \(N\) items with \(M\) marked items:

  • Optimal iterations \(\approx \frac{\pi}{4}\sqrt{\frac{N}{M}}\)
  • Too few: amplitude not fully amplified
  • Too many: amplitude rotates past target
def grover_search(n, target, iterations=1):
    qc = QuantumCircuit(n)
    qc.h(range(n))
    
    for _ in range(iterations):
        # Oracle: flip phase of |target⟩
        for i, bit in enumerate(reversed(target)):
            if bit == '0':
                qc.x(i)
        qc.h(n-1)
        qc.mcx(list(range(n-1)), n-1)
        qc.h(n-1)
        for i, bit in enumerate(reversed(target)):
            if bit == '0':
                qc.x(i)
        
        # Diffusion: flip phase of all except |0...0⟩, then reflect
        qc.h(range(n))
        qc.x(range(n))
        qc.h(n-1)
        qc.mcx(list(range(n-1)), n-1)
        qc.h(n-1)
        qc.x(range(n))
        qc.h(range(n))
    
    qc.measure_all()
    return qc

Testing on Different Targets

# Search for |01⟩ on 2 qubits
# Note: target string is 'q1 q0'
qc = grover_search(2, '01', iterations=1)
result = sampler.run([qc], shots=256).result()
print("Searching for |01⟩:", result[0].data.meas.get_counts())

Visualizing Amplitude Evolution

import matplotlib.pyplot as plt

def grover_amplitudes(n, target):
    """Return amplitudes after each iteration."""
    amplitudes = []
    
    qc = QuantumCircuit(n)
    qc.h(range(n))
    amplitudes.append(Statevector(qc))
    
    for _ in range(3):  # 3 iterations
        # Oracle
        for i, bit in enumerate(reversed(target)):
            if bit == '0':
                qc.x(i)
        qc.h(n-1)
        qc.mcx(list(range(n-1)), n-1)
        qc.h(n-1)
        for i, bit in enumerate(reversed(target)):
            if bit == '0':
                qc.x(i)
        
        # Diffusion
        qc.h(range(n))
        qc.x(range(n))
        qc.h(n-1)
        qc.mcx(list(range(n-1)), n-1)
        qc.h(n-1)
        qc.x(range(n))
        qc.h(range(n))
        
        amplitudes.append(Statevector(qc))
    
    return amplitudes

# Plot amplitudes for |11⟩ on 2 qubits
amplitudes = grover_amplitudes(2, '11')
for i, amp in enumerate(amplitudes):
    print(f"Iteration {i}: |11⟩ amplitude = {amp[3]:.3f}")

Checkpoint Exercises

Exercise 1

Implement Grover’s search for \(|00\rangle\) on 2 qubits.

Exercise 2

Search for \(|101\rangle\) on 3 qubits.

Exercise 3

Compare 1, 2, and 3 iterations. What happens with too many?

Exercise 4

Search for one marked item among 8 (3 qubits). Verify speedup.

Exercise 5

Explain why Grover’s algorithm is optimal.

Summary

Key concepts:

  • Grover searches unsorted data in \(O(\sqrt{N})\)
  • Oracle marks target with phase flip
  • Diffusion amplifies marked amplitude
  • Optimal iterations \(\approx \pi/4\sqrt{N/M}\)
  • Works by amplitude amplification, not parallelism

Grover’s algorithm is your first taste of quantum speedup. It shows how quantum mechanics can outperform classical computation.


Try These On QCoder


Next: Quantum Fourier Transform

Quantum Fourier Transform

The Quantum Fourier Transform (QFT) is a quantum version of the discrete Fourier transform. It’s a key subroutine in many quantum algorithms, including Shor’s factoring algorithm.

What QFT Does

QFT transforms between two representations of quantum data:

  1. Computational basis: states labeled by binary strings
  2. Fourier basis: states labeled by phases

Mathematically: \[|x\rangle \xrightarrow{\text{QFT}} \frac{1}{\sqrt{N}}\sum_{k=0}^{N-1} e^{2\pi i xk/N}|k\rangle\]

The 2-Qubit QFT

from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector
from math import pi

def qft_2():
    qc = QuantumCircuit(2)
    qc.h(1)
    qc.cp(pi/2, 0, 1)  # Controlled phase
    qc.h(0)
    qc.swap(0, 1)
    return qc

qc = qft_2()
print(qc)
print(Statevector(qc))

The Pattern

The n-qubit QFT follows a pattern:

  1. Apply Hadamard to most significant qubit
  2. Apply controlled phases with decreasing angles
  3. Repeat on remaining qubits
  4. Swap at the end (to fix qubit ordering)
def qft(n):
    qc = QuantumCircuit(n)
    for i in range(n-1, -1, -1):
        qc.h(i)
        for j in range(i):
            qc.cp(pi/(2**(i-j)), j, i)
    # Swap qubits to match little-endian convention
    for i in range(n // 2):
        qc.swap(i, n - i - 1)
    return qc

Why Controlled Phases?

The Fourier basis states are distinguished by phase patterns:

QFT|00⟩ = (|00⟩ + |01⟩ + |10⟩ + |11⟩)/2        (all same phase)
QFT|01⟩ = (|00⟩ - |01⟩ + |10⟩ - |11⟩)/2       (alternating)
QFT|10⟩ = (|00⟩ + i|01⟩ - |10⟩ - i|11⟩)/2     (phase pattern)
QFT|11⟩ = (|00⟩ - i|01⟩ - |10⟩ + i|11⟩)/2     (different pattern)

The controlled phases build these phase patterns.

QFT and Qubit Order

QFT is usually defined with big-endian ordering (qubit n-1 is most significant). Qiskit uses little-endian. The final SWAP fixes this.

# Without SWAP: output appears reversed
qc = QuantumCircuit(2)
qc.h(0)
qc.h(1)
qc.cp(pi/2, 0, 1)

print("Without swap:", Statevector(qc))

Inverse QFT

QFT is unitary, so it has an inverse (conjugate transpose):

qc = QuantumCircuit(2)
qc.x(0)  # Start with |01⟩

# Apply QFT then inverse QFT
qc.compose(qft_2(), inplace=True)
qc.compose(qft_2().inverse(), inplace=True)

print("After QFT† QFT:", Statevector(qc))

If implemented correctly, you get back to the original state.

Fourier Basis

The Fourier basis states are eigenstates of phase operations:

\[|j\rangle_{\text{Fourier}} \xrightarrow{\text{Phase}} e^{2\pi i j/N}|j\rangle_{\text{Fourier}}\]

This is why QFT is useful for phase estimation.

Checkpoint Exercises

Exercise 1

Implement the 3-qubit QFT by hand.

Exercise 2

Apply QFT to each basis state and describe the phase patterns.

Exercise 3

Verify that QFT followed by inverse QFT returns to the original state.

Exercise 4

Remove the SWAP gates and explain what changes.

Exercise 5

Create a circuit that adds phase \(e^{2\pi i/4}\) to the \(|1\rangle\) state.

Summary

Key concepts:

  • QFT transforms between computational and Fourier bases
  • Built from Hadamards and controlled phases
  • SWAP gates fix qubit ordering
  • Inverse QFT is QFT with negated phases
  • Foundation for phase estimation and Shor’s algorithm

QFT is the quantum version of a fundamental mathematical operation. Understanding it opens the door to many quantum algorithms.


Try These On QCoder


Next: Arithmetic Circuits and Modular Thinking

Arithmetic Circuits and Modular Thinking

Many quantum algorithms involve arithmetic operations. This chapter teaches you to think about quantum arithmetic as reversible basis-state transformations.

The Reversibility Constraint

Classical arithmetic often loses information:

  • AND: 2 inputs → 1 output
  • Addition can overflow

Quantum arithmetic must be reversible:

  • Use ancilla qubits for results
  • Preserve inputs (or uncompute them)

Basis State Mapping

Design arithmetic circuits by thinking about basis states:

OperationMapping
SWAP\(
Increment\(
Add constant\(

SWAP: The Simplest Arithmetic Circuit

from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector

# SWAP |xy⟩ → |yx⟩
qc = QuantumCircuit(2)
qc.swap(0, 1)

# Test on all inputs
for bits in ['00', '01', '10', '11']:
    qc_test = QuantumCircuit(2)
    for i, b in enumerate(reversed(bits)):
        if b == '1':
            qc_test.x(i)
    qc_test.compose(qc, inplace=True)
    print(f"{bits} → {Statevector(qc_test)}")

Increment

Adding 1 modulo \(2^n\):

def increment(n):
    """Create a circuit that adds 1 modulo 2^n."""
    qc = QuantumCircuit(n)
    for i in range(n-1, 0, -1):
        qc.mcx(list(range(i)), i)
    qc.x(0)
    return qc

# Test on 2 qubits
inc = increment(2)
for bits in ['00', '01', '10', '11']:
    qc_test = QuantumCircuit(2)
    for i, b in enumerate(reversed(bits)):
        if b == '1':
            qc_test.x(i)
    qc_test.compose(inc, inplace=True)
    print(f"{bits}+1 = {Statevector(qc_test)}")

Addition in the Fourier Basis

Addition becomes phase rotation in the Fourier basis:

# Add k in Fourier basis
def add_fourier(k, n):
    qc = QuantumCircuit(n)
    for i in range(n):
        qc.p(2*pi*k/(2**(i+1)), i)
    return qc

This is more efficient than computational basis addition.

Modular Arithmetic

Many quantum algorithms require modular arithmetic (operations modulo \(N\)):

\[x + y \pmod{N}\]

Key property: all operations wrap around at \(N\).

# Modular addition: (x + k) mod 4
def add_mod_4(k):
    qc = QuantumCircuit(2)
    inc = increment(2)
    for _ in range(k % 4):
        qc.compose(inc, inplace=True)
    return qc

Design Patterns

Pattern 1: Start with Basis States

Before writing gates, write the intended mapping:

|w⟩ → |w⟩    (identity)
|w⟩ → |f(w)⟩ (transformation)

Pattern 2: Test Exhausively

For small circuits, test all possible inputs:

def test_circuit(circuit, n_qubits, expected_mapping):
    for i in range(2**n_qubits):
        qc = QuantumCircuit(n_qubits)
        # Prepare basis state |i⟩
        for j in range(n_qubits):
            if (i >> j) & 1:
                qc.x(j)
        qc.compose(circuit, inplace=True)
        state = Statevector(qc)
        # Check result

Pattern 3: Use Ancillas Wisely

  • Use ancillas for intermediate results
  • Uncompute to restore ancillas to |0⟩
  • Minimize ancilla count when possible

Arithmetic and QFT

Some arithmetic is easier in the Fourier basis:

  • Phase rotations implement addition
  • QFT → add phases → inverse QFT

This is how Shor’s algorithm implements modular exponentiation.

Checkpoint Exercises

Exercise 1

Implement a 2-qubit decrement (subtract 1).

Exercise 2

Implement a 2-qubit circuit that computes \(|x\rangle \rightarrow |2x \mod 4\rangle\).

Exercise 3

Test your increment circuit exhaustively on all 2-bit inputs.

Exercise 4

Design a circuit that swaps \(|01\rangle\) and \(|10\rangle\) but leaves \(|00\rangle\) and \(|11\rangle\) unchanged.

Exercise 5

Explain why modular arithmetic is necessary for quantum algorithms.

Summary

Key concepts:

  • Arithmetic circuits map basis states
  • Must be reversible (use ancillas)
  • Test exhaustively on small inputs
  • Fourier basis simplifies some operations
  • Modular arithmetic wraps around

Arithmetic circuits are building blocks for quantum algorithms like Shor’s.


Try These On QCoder


Next: A Productive Qiskit Workflow

A Productive Qiskit Workflow

Writing circuits is half the job. The other half is debugging, testing, and structuring them. This chapter gives you a workflow that scales.

The Core Loop

For every circuit you build:

  1. Write the circuit constructor
  2. Inspect Statevector()
  3. Predict the state
  4. Compare actual vs predicted
  5. If wrong: debug using the checklist
  6. Then: measure and sample

Write Functions, Not Scripts

from qiskit import QuantumCircuit

def bell_state() -> QuantumCircuit:
    """Create a Bell state."""
    qc = QuantumCircuit(2)
    qc.h(0)
    qc.cx(0, 1)
    return qc

def bell_minus() -> QuantumCircuit:
    """Create the |Φ⁻⟩ state."""
    qc = QuantumCircuit(2)
    qc.h(0)
    qc.cx(0, 1)
    qc.z(0)
    return qc

Benefits:

  • Testable
  • Composable
  • Documented
  • Reusable

Use Statevector Aggressively

from qiskit.quantum_info import Statevector

state = Statevector(bell_state())
print("Probabilities:", state.probabilities())
print("State:", state)

Before measurement, always check the statevector. It tells you exactly what’s happening.

Tip: For the complete debugging checklist, see How To Use This Book.

Building Test Harnesses

For arithmetic and oracle circuits:

def test_basis_state_mapping(circuit, n_qubits, expected):
    """Test a circuit on all basis states."""
    results = {}
    for i in range(2**n_qubits):
        # Prepare input state
        qc = QuantumCircuit(n_qubits)
        for j in range(n_qubits):
            if (i >> j) & 1:
                qc.x(j)
        
        # Apply circuit
        qc.compose(circuit, inplace=True)
        
        # Check output
        state = Statevector(qc)
        results[i] = state
    
    return results

Use Parameters

from qiskit.circuit import Parameter

def rotation_circuit(theta):
    qc = QuantumCircuit(1)
    qc.ry(theta, 0)
    return qc

theta = Parameter("θ")
qc = rotation_circuit(theta)

# Evaluate for different values
for val in [0, pi/4, pi/2, pi]:
    assigned = qc.assign_parameters({theta: val})
    print(f"θ={val}: {Statevector(assigned)}")

Compose Large Circuits

# Build subcircuits
oracle = phase_oracle_11()
diffusion = diffusion_2qubit()

# Compose into Grover iteration
grover_iter = QuantumCircuit(2)
grover_iter.compose(oracle, inplace=True)
grover_iter.compose(diffusion, inplace=True)

Inverse and Control

# Inverse of a circuit
circuit_inverse = circuit.inverse()

# Controlled version
controlled_circuit = circuit.control()

# Both
controlled_inverse = circuit.inverse().control()

Save and Load Circuits

# Save circuit
qc.qasm()  # Returns OpenQASM string

# Load circuit
from qiskit import qasm2
qc = qasm2.load("circuit.qasm")

Summary Checklist

  • Write circuit constructors as functions
  • Inspect statevector before measurement
  • Build test harnesses for small circuits
  • Use parameters for reusable circuits
  • Compose subcircuits instead of writing monolithic circuits
  • Keep the debugging checklist in mind
  • Document what each circuit does

Checkpoint Exercises

Exercise 1

Refactor a previous exercise into a function with tests.

Exercise 2

Build a test harness that checks all 2-qubit basis inputs.

Exercise 3

Parameterize a one-qubit rotation circuit.

Exercise 4

Build a prepare-reflect-unprepare helper.


Next: Quantum Machine Learning with Qiskit

Quantum Machine Learning with Qiskit

Quantum Machine Learning (QML) combines quantum computing with classical machine learning. This chapter gives you a realistic starting point.

What QML Looks Like in Practice

Beginner-friendly QML typically falls into two categories:

TypeDescriptionQiskit Package
Quantum KernelsMeasure similarity between quantum statesqiskit-machine-learning
Variational ModelsParameterized circuits optimized for tasksqiskit-machine-learning

Installing the Package

uv add qiskit-machine-learning

Quantum Kernels

The workflow:

  1. Encode classical data into quantum states (feature map)
  2. Prepare states and measure overlap (kernel)
  3. Use kernel in classical ML algorithm
from qiskit import QuantumCircuit
from qiskit.circuit import ParameterVector

# Simple feature map
def feature_map(x, n=2):
    qc = QuantumCircuit(n)
    for i in range(n):
        qc.h(i)
        qc.rz(x[i], i)
    qc.cx(0, 1)
    return qc

# Use it
x = ParameterVector("x", 2)
fm = feature_map(x)
print(fm)

Variational Circuits

A variational circuit has:

  1. Feature map (encodes data)
  2. Ansatz (trainable parameters)
  3. Measurement (outputs predictions)
from qiskit import QuantumCircuit
from qiskit.circuit import ParameterVector

# Trainable ansatz
def ansatz(params, n=2):
    qc = QuantumCircuit(n)
    for i in range(n):
        qc.ry(params[i], i)
    qc.cx(0, 1)
    for i in range(n):
        qc.rz(params[n+i], i)
    return qc

# Use it
params = ParameterVector("θ", 4)
ans = ansatz(params)
print(ans)

Connecting to Scikit-Learn

from qiskit_machine_learning.algorithms import QiskitClassifier
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split

# Simple example structure (full implementation requires more setup)
X, y = make_classification(n_samples=100, n_features=2, n_classes=2)
X_train, X_test, y_train, y_test = train_test_split(X, y)

How Earlier Chapters Support QML

QML ConceptRelevant Chapter
Feature mapsState preparation, basis changes
Kernel computationPhase, interference
Ansatz designSingle/two-qubit gates
Gradient computationMeasurement, sampling

QML isn’t a separate island—it’s built from circuit primitives.

A Realistic Beginner Goal

You don’t need to become a QML researcher. A good goal:

  • Read a kernel tutorial without getting lost
  • Write a parameterized feature map
  • Write a variational ansatz
  • Understand where trainable parameters live
  • Debug a circuit before training

If you can do these, official tutorials become approachable.

What to Watch Out For

PitfallSymptom
Too many qubitsBarren plateaus, noise
Feature/ansatz confusionWrong parameters optimized
Classical preprocessingData not encoded properly
Shot noiseInconsistent gradients

Checkpoint Exercises

Exercise 1

Install qiskit-machine-learning.

Exercise 2

Write a 2-qubit feature map with parameterized rotations.

Exercise 3

Write a 4-parameter variational ansatz.

Exercise 4

Identify which earlier chapters support kernel methods vs variational methods.


Next: Next Steps

Next Steps

You’ve completed the tutorial. Now what? Here’s how to keep growing.

If You Want to Get Stronger at QCoder

Practice with these contest archives:

LevelProblemsFocus
BeginnerQPC001 A-seriesState preparation
IntermediateQPC003 A-seriesComplex state prep
IntermediateQPC002 B-seriesPhase and oracles
AdvancedQPC003 B-seriesReflections, Grover
AdvancedQPC005Fourier basis

Work through them in order. Each contest builds on the previous.

If You Want Algorithm Depth

Next topics to study:

  • Quantum Phase Estimation — Estimate eigenvalues of unitary operators
  • Shor’s Algorithm — Factor integers (requires QFT + modular arithmetic)
  • Quantum Walk Algorithms — Generalization of Grover
  • Amplitude Estimation — Quantum version of Monte Carlo

At this point, the official Qiskit tutorials become essential.

If You Want QML Depth

Next topics to study:

  • Quantum Kernels — Measure quantum state similarity
  • Variational Quantum Eigensolvers (VQE) — Find ground states
  • Quantum Approximate Optimization (QAOA) — Combinatorial optimization
  • Hybrid Classical-Quantum Training — Gradients, optimizers

If You Want Hardware Experience

Next steps:

  1. Get an IBM Quantum account (free tier available)
  2. Run circuits on real quantum hardware
  3. Learn about transpilation (circuit rewriting for hardware)
  4. Study noise models and error mitigation

The Standard to Maintain

Don’t settle for “I read about it.” Hold yourself to:

  1. State the transformation — Can you write the intended unitary?
  2. Implement it — Can you write the circuit in Qiskit?
  3. Test it — Can you verify correctness on small inputs?
  4. Explain it — Can you explain why it works?

That standard keeps you honest.

ResourceTypeLevel
Qiskit DocumentationOfficial docsAll levels
Qiskit TextbookTutorialsBeginner-Intermediate
QCoderPractice problemsAll levels
Mike & IkeTextbookAdvanced

Official API References

For quick reference while working through the chapters:

Core Qiskit

Gates

IBM Learning Courses

Qiskit Machine Learning

A Final Thought

Quantum computing is hard. Not hard like magic—hard like any deep technical skill. It rewards patience and systematic learning.

You now have the foundation. The rest is practice.

Build circuits. Break them. Fix them. Submit QCoder problems. Read the docs. Ask questions.

The quantum future needs people who understand this stuff.

Go make it happen.


Congratulations on completing Qiskit for Curious Programmers!