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:
| Skill | What It Means |
|---|---|
| Read circuits | Understand what a circuit diagram means before running anything |
| Write circuits | Build circuits that do exactly what you intend |
| Debug with statevectors | Inspect exact quantum states to find bugs |
| Design patterns | Recognize state preparation, oracles, and reflections |
| Solve QCoder problems | Work through the QCoder problem sets with confidence |
| Explore QML | Read 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:
- A mental model to understand conceptually
- Working Qiskit code to try immediately
- Exercises to build muscle memory
- 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:
- Are comfortable with Python (functions, classes, loops)
- Have basic linear algebra (vectors, matrices, multiplication) — or are willing to learn
- Have set up your environment (see Setup)
- 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:
QuantumCircuitto define circuitsStatevectorto inspect exact statesStatevectorSamplerto sample measurement outcomescompose,inverse,controlto 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:
- Read once without writing code (get the big picture)
- Type every code block yourself (muscle memory)
- Predict the result before running (this is critical)
- Run and compare (did you get what you expected?)
- Change one thing and predict again (explore)
- 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:
- Write the circuit
- Inspect
Statevector() - Predict what you expect
- Compare actual vs expected
- If wrong: debug using the checklist below
- 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:
| # | Check | What to look for |
|---|---|---|
| 1 | Qubit ordering | Is |10> interpreted as you expect? |
| 2 | Amplitudes vs counts | Are you looking at the right thing? |
| 3 | Measurement timing | Did you measure too early? |
| 4 | Inverse/uncompute | Did you forget to undo temporary operations? |
| 5 | Global vs relative phase | -1 on one branch vs -1 on all branches |
| 6 | Control vs target | Is 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:
- Finish a chapter’s exercises
- Pick a linked QCoder problem (see the QCoder Problem Catalog)
- Read the problem description carefully
- Design your circuit on paper first
- Implement in Qiskit
- 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:
| Part | Topic | Approx. Difficulty |
|---|---|---|
| I | Hello, Quantum World | Getting started |
| II | Single-Qubit Toolkit | Foundational |
| III | Two-Qubit Territory | Foundational |
| IV | Circuit Patterns | Intermediate |
| V | Algorithms | Intermediate/Advanced |
| VI | Putting It Together | Applied |
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:
| Tool | Purpose | Version |
|---|---|---|
| Python | Programming language | 3.13+ |
| uv | Package manager and environment tool | Latest |
| mdbook | Book build tool | Latest |
| Qiskit | Quantum computing framework | Latest |
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:
- Exact answers — Statevector gives you the exact quantum state, not statistical samples
- Faster debugging — You can see exactly what’s happening, not just averages
- Phase visibility — Relative phase is invisible in counts but visible in statevectors
- Foundation for understanding — Measurement statistics follow from the statevector
The workflow we’ll use:
- Write circuit
- Inspect
Statevector() - Understand the state
- 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 pythonto 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
| Contest | Problems | Focus Areas |
|---|---|---|
| QPC001 | 11 | State Generation, Oracles |
| QPC002 | 14 | Phase, Arithmetic, QFT |
| QPC003 | 16 | One-hot States, Reflections, Grover |
| QPC004 | 17 | Zeta/Moebius Transform, Shor’s Algorithm |
| QPC005 | 9 | Controlled Gates, Fourier Basis |
| QPC006 | 12 | Error Correction, T-Gates |
By Topic
State Preparation
Basic States (QPC001)
- A1 - Generate Plus state | 100 pts
- A2 - Generate Uniform Superposition State | 200 pts
- A3 - Generate state (|0⟩+|3⟩)/√2 | 200 pts
- A4 - Generate state (|0⟩+|1⟩+|2⟩)/√3 | 200 pts
- A5 - Generate state II | 100 pts
Phase States (QPC002)
- A1 - Generate state (−|0⟩−|0⟩) | 100 pts
- A2 - Generate state (|0⟩−|3⟩)/2 | 200 pts
- A3 - Generate state (|0⟩−|2ⁿ−1⟩)/2 I | 300 pts
- A4 - Generate state (|0⟩−|2ⁿ−1⟩)/2 II | 100 pts
- A5 - Generate state (|0⟩−|2ⁿ−1⟩)/2 III | 200 pts
- B1 - Generate state e^(iθ)|0⟩ | 100 pts
One-Hot States (QPC003)
- A1 - Generate state i|1⟩ | 100 pts
- A2 - Generate state (|10⟩+|01⟩)/√2 | 200 pts
- A3 - Generate state (|100⟩+|010⟩+|001⟩)/√3 | 300 pts
- A4 - Generate One-hot Superposition State I | 200 pts
- A5 - Generate One-hot Superposition State II | 200 pts
- A6 - Generate One-hot Superposition State III | 300 pts
Tensor Product States (QPC003)
Uniform Amplitude Superposition (QPC001)
- C1 - Generate Uniform Amplitude Superposition State I | 300 pts
- C2 - Generate Uniform Amplitude Superposition State II | 600 pts
Oracles
Basic Oracles (QPC001)
- B1 - Copy Oracle | 100 pts
- B2 - XOR Oracle | 200 pts
- B3 - Less Than Oracle I | 300 pts
- B4 - Less Than Oracle II | 500 pts
Phase Shift Oracles (QPC002)
- B2 - Phase Shift Oracle | 200 pts
Shift Oracles (QPC004, QPC005)
- QPC004 A3 - Cyclic Shift Oracle I | 200 pts
- QPC004 A4 - Cyclic Shift Oracle II | 200 pts
- QPC004 A5 - Minus One Oracle | 300 pts
- QPC005 A4 - Cyclic Shift Oracle III | 400 pts
- QPC005 A5 - Minus Oracle | 400 pts
Polynomial Oracles (QPC006)
- B1 - Polynomial Oracle | 300 pts
- B2 - The Hidden Polynomial I | 600 pts
- B3 - The Hidden Polynomial II | 600 pts
Multi-Qubit Operations
SWAP and Qubit Manipulation (QPC002, QPC004)
- B3 - SWAP Qubits | 200 pts
- A2 - Qubit Swap | 200 pts
- QPC005 Ex - Reverse and Shift | 600 pts
Controlled Gates (QPC005, QPC006)
- A2 - Controlled Gate I | 200 pts
- A3 - Controlled Gate II | 200 pts
- C1 - T-dagger | 200 pts
- C2 - Controlled-T | 300 pts
- C3 - Multi-Controlled X | 300 pts
- C4 - Controlled-S | 400 pts
- C5 - Controlled-H | 400 pts
- C6 - Controlled-Controlled-Z | 500 pts
Quantum Fourier Transform (QFT)
- B4 - Quantum Fourier Transform | 200 pts
- B1 - Action in the Fourier Basis | 300 pts
- B2 - Left Action in Another Fourier Basis | 500 pts
- B3 - Right Action in Another Fourier Basis | 600 pts
Arithmetic Circuits
- B5 - Quantum Arithmetic I | 200 pts
- B6 - Quantum Arithmetic II | 200 pts
- B7 - Quantum Arithmetic III | 200 pts
- B8 - Quantum Arithmetic IV | 200 pts
Modular Arithmetic (QPC004)
- B3 - Controlled Constant Addition | 300 pts
- B4 - Left and Right Aligned | 400 pts
- B5 - Modular Arithmetic I | 600 pts
- C1 - Doubling Powers | 200 pts
- C2 - Modular Inverse | 300 pts
- C3 - Modular Arithmetic II | 300 pts
- C4 - Modular Arithmetic III | 400 pts
- C5 - Modular Arithmetic IV | 600 pts
Reflections and Grover’s Algorithm
Reflections (QPC003, QPC004)
- B4 - Reflection Operator I | 200 pts
- B5 - Reflection Operator II | 200 pts
- B7 - Reflection Operator III | 300 pts
- A6 - Zeta / Moebius Transform I | 200 pts
- A7 - Zeta / Moebius Transform II | 300 pts
Grover’s Algorithm (QPC003)
- B6 - Grover’s Algorithm I | 300 pts
- B8 - Grover’s Algorithm II | 300 pts
Bit-Phase Conversion (QPC003)
- B2 - Convert Bit-Flip into Phase-Flip I | 200 pts
- Ex1 - Convert Bit-Flip into Phase-Flip II | 300 pts
Gate Manipulation
Notation (QPC003)
- B1 - Bra-Ket Notated Gate | 100 pts
Uniform Superposition (QPC004)
- B1 - Uniform Superposition | 100 pts
- B2 - Controlled Phase Shift | 200 pts
Error Correction (QPC006)
- A1 - Correction | 100 pts
- A2 - Encoding | 200 pts
- A3 - Detection | 500 pts
Challenge Problems
- Ex - Can you prepare |ω⟩? | 600 pts
- Ex2 - Find Hidden Number | 500 pts
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:
| Chapter | Recommended Problems |
|---|---|
| First Circuit | QPC001 A1, A2 |
| Single-Qubit Gates | QPC001 A3, A4, QPC003 A1 |
| Hadamard & Superposition | QPC001 A1, A2, QPC002 A1 |
| Phase | QPC002 B1, QPC003 A1 |
| Multi-Qubit Basics | QPC001 B1, B2 |
| CNOT & Entanglement | QPC001 A4, QPC003 A2, A3 |
| Two-Qubit Gates | QPC002 B3, QPC004 A2 |
| Oracles | QPC001 B3, B4 |
| Grover’s Algorithm | QPC003 B6, B8 |
| QFT | QPC002 B4, QPC005 B1 |
| Arithmetic | QPC002 B5-B8, QPC004 B3-B5 |
| Controlled Gates | QPC005 A2, A3, QPC006 C1-C6 |
| Error Correction | QPC006 A1-A3 |
Official QCoder Resources
- QCoder Main Site
- Q&A Page - Rules and format info
- Contest List
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:
- Initial state: Every qubit starts as \(|0\rangle\)
- Hadamard gate: Transforms \(|0\rangle\) into a superposition of \(|0\rangle\) and \(|1\rangle\)
- 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:
| Concept | Description | Example |
|---|---|---|
| Circuit | The sequence of gates to apply | h(0) |
| Statevector | The quantum state produced by the circuit | \(\frac{ |
| Measurement | The 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:
- Ask yourself: “What state should this circuit produce?”
- Run the code
- 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:
- Write a circuit
- Inspect the state
- Predict what measurement will show
- Measure to confirm
Try These On QCoder
- QPC001 A1: Generate State |1>
- QPC001 A2: Generate Plus state
- QPC002 B1: Generate State e^(i theta)|0>
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
0or1 - 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_multivectordisplays 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:
| Classical | Quantum |
|---|---|
| Heads or Tails | \( |
| The coin is one or the other | The coin can be in a superposition |
| Flipping reveals the result | Measuring 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:
- You get either \(|0\rangle\) or \(|1\rangle\)
- The probability of each outcome is determined by the amplitudes
- The state “collapses” to the measured value
- 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:
- Superposition: A qubit can be in a combination of \(|0\rangle\) and \(|1\rangle\)
- Entanglement: Multiple qubits can be correlated in ways classical bits can’t be
- 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
| Property | Classical Bit | Qubit |
|---|---|---|
| States | 0 or 1 | Any normalized superposition |
| Copying | Easy | Impossible (no-cloning theorem) |
| Reading | Destructive? | Destructive (collapses state) |
| Operations | Boolean logic | Unitary matrices |
| Determinism | Always deterministic | Probabilistic (when measured) |
A Note on Notation
This book uses Dirac notation throughout. Here’s a quick reference:
| Notation | Meaning |
|---|---|
| \( | 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:
- You get a classical result:
0or1 - The probability of each outcome comes from the amplitudes
- The state collapses to the measured value
Before: α|0⟩ + β|1⟩ ──MEASURE──> |0⟩ (prob |α|²)
|1⟩ (prob |β|²)
Two Different Questions
These are fundamentally different questions:
| Question | Tool | Answer |
|---|---|---|
| “What state is the qubit in?” | Statevector | Exact amplitudes and phases |
| “What do I observe when I measure?” | Sampling | Probabilistic 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:
- We control the probabilities through circuit design
- Interference can make “wrong” answers cancel out
- 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:
- Write the circuit
- Inspect Statevector()
- Are the amplitudes correct?
- Are the phases correct?
- Predict what measurement will show
- Add measurements and sample
- 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:
- Forces the system to choose
- Destroys superposition
- 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
StatevectorSamplersimulates 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
- QPC001 A3: Generate Minus state
- QPC003 B2: Convert Bit-Flip into Phase-Flip I
- QPC005 B1: Action in the Fourier Basis
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:
| Position | Statevector Index | Basis State | Qubit 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:
- Write down the basis label:
|101> - Break it into qubits: q2=1, q1=0, q0=1
- 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 - Verify with Statevector
Tables to Memorize
2-Qubit Basis States
| Label | Qubit 1 | Qubit 0 | Statevector Index |
|---|---|---|---|
|00⟩ | 0 | 0 | 0 |
|01⟩ | 0 | 1 | 1 |
|10⟩ | 1 | 0 | 2 |
|11⟩ | 1 | 1 | 3 |
3-Qubit Basis States
| Label | Qubit 2 | Qubit 1 | Qubit 0 |
|---|---|---|---|
|000⟩ | 0 | 0 | 0 |
|001⟩ | 0 | 0 | 1 |
|010⟩ | 0 | 1 | 0 |
|011⟩ | 0 | 1 | 1 |
|100⟩ | 1 | 0 | 0 |
|101⟩ | 1 | 0 | 1 |
|110⟩ | 1 | 1 | 0 |
|111⟩ | 1 | 1 | 1 |
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:
| Situation | What Goes Wrong |
|---|---|
| Preparing arithmetic states | ` |
| Reading statevectors | Index doesn’t match expected basis state |
| QCoder problems | Problem states might use different conventions |
| Copying circuit diagrams | Gate 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
- Prepare
|10>on two qubits and verify the statevector. - Prepare
|101>on three qubits. - Explain why
qc.x(0)on two qubits gives|01>. - Create a table showing all 2-qubit basis states with their qubit values.
- 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
- QPC001 A4: Generate State (|10> + |11>)/sqrt(2)
- QPC003 A2: Generate State (|10> + |01>)/sqrt(2)
- QPC003 A3: Generate State (|100> + |010> + |001>)/sqrt(3)
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)))
| Gate | Rotation Axis | Angle |
|---|---|---|
| X | x-axis | 180° |
| Y | y-axis | 180° |
| Z | z-axis | 180° |
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:
| State | Name | Definition |
|---|---|---|
| \( | +\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:
- Interference: Phases combine when states recombine
- Entanglement: Phase correlations create entangled states
- 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
- QPC002 A1: Generate state (−|0⟩−|0⟩)
- QPC002 B1: Generate State e^(iθ)|0⟩
- QPC002 B2: Phase Shift Oracle
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
| Gate | Axis | Description |
|---|---|---|
rx(theta) | x-axis | Rotation around the x-axis |
ry(theta) | y-axis | Rotation around the y-axis |
rz(theta) | z-axis | Rotation around the z-axis |
p(theta) | z-axis | Phase (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 Angle | Effect |
|---|---|
| \(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
| Gate | Matrix | Action |
|---|---|---|
| \(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
| Identity | Meaning |
|---|---|
| \(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:
| Basis | States | Created 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:
| Gate | Transform |
|---|---|
| \(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:
- Phase kickback: Use basis changes to move phase information
- Measurement basis: Measure in different bases to extract different information
- 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:
| Gate | Code | Purpose |
|---|---|---|
| X | x(q) | Bit flip |
| Y | y(q) | Bit + phase flip |
| Z | z(q) | Phase flip |
| H | h(q) | Create superposition / basis change |
| P | p(θ, q) | Add phase |
| RX | rx(θ, q) | Rotation around x-axis |
| RY | ry(θ, q) | Rotation around y-axis |
| RZ | rz(θ, 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
- QPC001 A3: Generate Minus state
- QPC002 B1: Generate State e^(i theta)|0>
- QPC003 B3: Generate State tensor_i (cos T_i |0> + sin T_i |1>)
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:
| Input | Output |
|---|---|
|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:
| Index | Basis State | Value |
|---|---|---|
| 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
0with 50% probability → qubit 1 is also0 - You see
1with 50% probability → qubit 1 is also1
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
| Mistake | Result |
|---|---|
cx(1, 0) instead of cx(0, 1) | Wrong correlation direction |
| Forgetting to put control in superposition | No entanglement, just classical correlation |
| Measuring before CNOT | Destroys 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:
| Name | Definition | Created 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:
- Quantum teleportation — transferring quantum states
- Superdense coding — sending 2 classical bits with 1 quantum bit
- Quantum key distribution — secure communication
- Quantum algorithms — speedups in computation
Without entanglement, quantum computers would be no more powerful than classical computers.
Common Misconceptions
| Misconception | Reality |
|---|---|
| “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
- QPC001 A4: Generate State (|10> + |11>)/sqrt(2)
- QPC003 A2: Generate State (|10> + |01>)/sqrt(2)
- QPC003 A3: Generate State (|100> + |010> + |001>)/sqrt(3)
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\]
| Input | Output |
|---|---|
|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
| Gate | Effect 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\]
| Input | Output |
|---|---|
|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
| Gate | Symbol | Effect | Code |
|---|---|---|---|
| CNOT | ■───⊕ | Flip target if control=1 | cx(c, t) |
| CZ | ■───■ | Phase flip if both=1 | cz(c, t) |
| SWAP | ×───× | Exchange qubit states | swap(a, b) |
| iSWAP | iSWAP | Swap with phase | iswap(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:
- Name the target state in mathematical notation
- Identify probabilities for each basis state
- Use RY to set branch probabilities
- Use CNOT to route amplitude to correct basis states
- Add phases with Z or CZ to distinguish states
- 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
- QPC001 A5: Generate State (sqrt(3)/2)|100> + (1/2)|011>
- QPC003 A3: Generate State (|100> + |010> + |001>)/sqrt(3)
- QPC003 A4, A5, A6
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:
- Encode classical information in phases
- Transfer phase information between qubits
- Create controlled operations from simpler ones
- 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
- QPC003 B2: Convert Bit-Flip into Phase-Flip I
- QPC003 Ex1: Convert Bit-Flip into Phase-Flip II
- QPC005 B1: Action in the Fourier Basis
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
- Compute auxiliary values
- Use the values
- 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
- QPC002 B2: Phase Shift Oracle
- QPC003 B2: Convert Bit-Flip into Phase-Flip I
- QPC003 Ex1: Convert Bit-Flip into Phase-Flip II
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\):
- Prepare \(|\psi\rangle\) from \(|0\rangle\)
- Apply \(Z\) on ancilla
- 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:
- Oracle marks target states (flips sign)
- Diffusion reflects about uniform superposition
- 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
- QPC003 B4: Reflection Operator I
- QPC003 B5: Reflection Operator II
- QPC003 B7: Reflection Operator III
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:
- Creates uniform superposition over all items
- Uses an oracle to mark the target
- 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:
- Computational basis: states labeled by binary strings
- 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:
- Apply Hadamard to most significant qubit
- Apply controlled phases with decreasing angles
- Repeat on remaining qubits
- 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
- QPC002 B4: Quantum Fourier Transform
- QPC005 B1: Action in the Fourier Basis
- QPC005 B2: Cyclic Shift in the Fourier Basis
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:
| Operation | Mapping |
|---|---|
| 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:
- Write the circuit constructor
- Inspect
Statevector() - Predict the state
- Compare actual vs predicted
- If wrong: debug using the checklist
- 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:
| Type | Description | Qiskit Package |
|---|---|---|
| Quantum Kernels | Measure similarity between quantum states | qiskit-machine-learning |
| Variational Models | Parameterized circuits optimized for tasks | qiskit-machine-learning |
Installing the Package
uv add qiskit-machine-learning
Quantum Kernels
The workflow:
- Encode classical data into quantum states (feature map)
- Prepare states and measure overlap (kernel)
- 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:
- Feature map (encodes data)
- Ansatz (trainable parameters)
- 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 Concept | Relevant Chapter |
|---|---|
| Feature maps | State preparation, basis changes |
| Kernel computation | Phase, interference |
| Ansatz design | Single/two-qubit gates |
| Gradient computation | Measurement, 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
| Pitfall | Symptom |
|---|---|
| Too many qubits | Barren plateaus, noise |
| Feature/ansatz confusion | Wrong parameters optimized |
| Classical preprocessing | Data not encoded properly |
| Shot noise | Inconsistent 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:
| Level | Problems | Focus |
|---|---|---|
| Beginner | QPC001 A-series | State preparation |
| Intermediate | QPC003 A-series | Complex state prep |
| Intermediate | QPC002 B-series | Phase and oracles |
| Advanced | QPC003 B-series | Reflections, Grover |
| Advanced | QPC005 | Fourier 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:
- Get an IBM Quantum account (free tier available)
- Run circuits on real quantum hardware
- Learn about transpilation (circuit rewriting for hardware)
- Study noise models and error mitigation
The Standard to Maintain
Don’t settle for “I read about it.” Hold yourself to:
- State the transformation — Can you write the intended unitary?
- Implement it — Can you write the circuit in Qiskit?
- Test it — Can you verify correctness on small inputs?
- Explain it — Can you explain why it works?
That standard keeps you honest.
Recommended Resources
| Resource | Type | Level |
|---|---|---|
| Qiskit Documentation | Official docs | All levels |
| Qiskit Textbook | Tutorials | Beginner-Intermediate |
| QCoder | Practice problems | All levels |
| Mike & Ike | Textbook | Advanced |
Official API References
For quick reference while working through the chapters:
Core Qiskit
Gates
- Single-qubit gates
- Pauli gates (X, Y, Z)
- Hadamard gate
- CNOT (CX) gate
- CZ gate
- SWAP gate
- Rotation gates (RX, RY, RZ)
- Phase gate (P)
IBM Learning Courses
- Basics of Quantum Information
- Fundamentals of Quantum Algorithms
- Introduction to Quantum Superposition
- Entanglement
- Multi-qubit circuits
- Phase kickback
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!