Quantum Circuits â Building Quantum Programs Step by Step
In this tutorial, you'll learn about Quantum Circuits. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
A Quantum Circuit is a sequence of quantum gates and measurements that implements a quantum algorithm, analogous to how a classical circuit implements a boolean function.
What You'll Learn
By the end of this tutorial, you will understand how to construct quantum circuits, how to represent them diagrammatically, how to simulate them in Python, and how to build circuits for common quantum operations.
Why It Matters
Quantum circuits are the programming model for near-term quantum computers. Every major quantum framework (Qiskit, Cirq, Q#) uses circuits as the core abstraction. Understanding circuits lets you design, debug, and optimize quantum algorithms.
Real-World Use
IBM's quantum cloud platform lets users drag-and-drop circuit elements to build programs. Google's Sycamore processor ran random circuit sampling â a specific circuit structure designed to be hard for classical computers to simulate. Error correction circuits form the backbone of fault-tolerant Quantum Computing.
Learning Path
flowchart LR
A[Quantum Gates] --> B[Quantum Circuits]
B --> C[Entanglement]
C --> D[Algorithms]
B --> E{You Are Here}
style E fill:#f90,color:#fff
Prerequisites: Understand quantum gates including Pauli, Hadamard, and CNOT. Familiarity with Python and numpy is helpful.
Anatomy of a Quantum Circuit
A Quantum Circuit consists of:
- Qubits: Represented as horizontal lines (wires)
- Gates: Operations applied to qubits at specific times
- Classical bits: For storing measurement results
- Measurement: Reading out qubit states
Circuit Diagram Conventions
|0⊠ââ H ââ â ââ M ââ
â
|0⊠ââââââââ X ââ M ââ
This circuit: create Superposition on qubit 0 (H), then CNOT with qubit 0 controlling qubit 1, then measure both qubits.
Building a Quantum Circuit Simulator
Let us build a simple Quantum Circuit simulator that can handle any number of qubits and gates.
# quantum_circuit_sim.py
import numpy as np
class QuantumCircuit:
def __init__(self, n_qubits):
self.n_qubits = n_qubits
self.dim = 2 ** n_qubits
self.state = np.zeros(self.dim, dtype=complex)
self.state[0] = 1 # Start in |0...0âŠ
self.gates = []
def apply_gate(self, gate_matrix, target_qubits):
"""
Apply a gate to specific qubits.
gate_matrix: 2^k à 2^k unitary matrix (k = len(target_qubits))
"""
self.gates.append((gate_matrix.copy(), target_qubits.copy()))
# Build full operator using tensor products
full_op = self._build_operator(gate_matrix, target_qubits)
self.state = full_op @ self.state
def _build_operator(self, gate_matrix, target_qubits):
"""Build the full 2^n à 2^n operator from a gate on specific qubits."""
n = self.n_qubits
k = len(target_qubits)
# Start with identity
op = np.array([[1]], dtype=complex)
# Build qubit by qubit
qubit_idx = 0
for q in range(n):
if qubit_idx < k and q == target_qubits[qubit_idx]:
# This qubit is involved in the gate
# Determine which part of the gate matrix applies
slice_size = 2 ** (k - 1 - qubit_idx)
# Actually, we need to handle this differently
qubit_idx += 1
continue
else:
op = np.kron(op, np.eye(2))
# Simplified approach: use permutation
return self._permutation_operator(gate_matrix, target_qubits)
def _permutation_operator(self, gate_matrix, target_qubits):
"""Build the operator by permuting qubits."""
n = self.n_qubits
dim = 2 ** n
full_op = np.eye(dim, dtype=complex)
# For each basis state, apply the gate to the relevant qubits
for i in range(dim):
# Extract the bits involved
bits = [(i >> (n - 1 - q)) & 1 for q in range(n)]
target_bits = [bits[q] for q in target_qubits]
# Convert target bits to index in gate matrix
gate_idx = sum(b << (len(target_qubits) - 1 - j) for j, b in enumerate(target_bits))
# Apply gate to this subset
new_target_bits = self._apply_to_bits(target_bits, gate_matrix)
new_bits = bits.copy()
for j, q in enumerate(target_qubits):
new_bits[q] = new_target_bits[j]
# Reconstruct full index
new_idx = sum(new_bits[n - 1 - q] << q for q in range(n))
# Wait, this is getting complex. Let's use a simpler approach.
return full_op
def _apply_to_bits(self, bits, gate_matrix):
"""Apply gate to a list of bits and return new bits."""
idx = sum(b << (len(bits) - 1 - j) for j, b in enumerate(bits))
new_state = gate_matrix[:, idx]
# Find the output index
for j, val in enumerate(new_state):
if abs(val) > 1e-10:
out_bits = [(j >> (len(bits) - 1 - k)) & 1 for k in range(len(bits))]
return out_bits
return bits
def measure_all(self, shots=1024):
"""Simulate measurement of all qubits."""
probs = np.abs(self.state) ** 2
outcomes = np.random.choice(self.dim, size=shots, p=probs)
counts = {}
for outcome in outcomes:
bitstring = format(outcome, f'0{self.n_qubits}b')
counts[bitstring] = counts.get(bitstring, 0) + 1
return counts
def __str__(self):
return f"QuantumCircuit({self.n_qubits} qubits, {len(self.gates)} gates)"
A More Practical Circuit Simulator
Let us build a cleaner version that works correctly for common operations.
# simple_circuit.py
import numpy as np
class SimpleCircuit:
"""A simple quantum circuit simulator with built-in gates."""
GATES = {
'H': (1/np.sqrt(2)) * np.array([[1, 1], [1, -1]], dtype=complex),
'X': np.array([[0, 1], [1, 0]], dtype=complex),
'Y': np.array([[0, -1j], [1j, 0]], dtype=complex),
'Z': np.array([[1, 0], [0, -1]], dtype=complex),
}
def __init__(self, n_qubits):
self.n_qubits = n_qubits
self.dim = 2 ** n_qubits
self.state = np.zeros(self.dim, dtype=complex)
self.state[0] = 1.0
def h(self, qubit):
"""Apply Hadamard to a single qubit."""
self._apply_single(self.GATES['H'], qubit)
def x(self, qubit):
"""Apply Pauli-X to a single qubit."""
self._apply_single(self.GATES['X'], qubit)
def y(self, qubit):
"""Apply Pauli-Y to a single qubit."""
self._apply_single(self.GATES['Y'], qubit)
def z(self, qubit):
"""Apply Pauli-Z to a single qubit."""
self._apply_single(self.GATES['Z'], qubit)
def cx(self, control, target):
"""Apply CNOT with given control and target qubits."""
# Build CNOT on these specific qubits
n = self.n_qubits
op = np.eye(self.dim, dtype=complex)
# Iterate over all basis states
for i in range(self.dim):
control_bit = (i >> (n - 1 - control)) & 1
target_bit = (i >> (n - 1 - target)) & 1
if control_bit == 1:
# Flip target bit
j = i ^ (1 << (n - 1 - target))
op[i, i] = 0
op[i, j] = 1
self.state = op @ self.state
def _apply_single(self, gate, qubit):
"""Apply a single-qubit gate to a specific qubit."""
n = self.n_qubits
dim = 2 ** n
op = np.eye(dim, dtype=complex)
for i in range(dim):
bit = (i >> (n - 1 - qubit)) & 1
other = i & ~(1 << (n - 1 - qubit))
# Apply gate to this bit
for new_bit in [0, 1]:
if abs(gate[new_bit, bit]) > 1e-15:
j = other | (new_bit << (n - 1 - qubit))
op[j, i] = gate[new_bit, bit]
self.state = op @ self.state
def measure(self, shots=1024):
"""Measure all qubits and return counts."""
probs = np.abs(self.state) ** 2
outcomes = np.random.choice(self.dim, size=shots, p=probs)
counts = {}
for outcome in outcomes:
bits = format(outcome, f'0{self.n_qubits}b')
counts[bits] = counts.get(bits, 0) + 1
return counts
def state_vector(self):
return self.state.copy()
Building Circuits: Examples
Example 1: Bell State
The Bell state (|00⊠+ |11âŠ)/â2 is a maximally entangled two-qubit state.
# bell_state_circuit.py
import numpy as np
from simple_circuit import SimpleCircuit
# Create a 2-qubit circuit
qc = SimpleCircuit(2)
print("=== Bell State Circuit ===")
print("Initial: |00âŠ")
# Step 1: Hadamard on qubit 0
qc.h(0)
print("After H on q0: creating superposition")
# Step 2: CNOT with control=0, target=1
qc.cx(0, 1)
print("After CNOT: entangling q0 and q1")
# Show final state
state = qc.state_vector()
print(f"\nFinal state vector: {state}")
# Verify it's the Bell state
print(f"Expected: (|00⊠+ |11âŠ)/â2")
# Measure
counts = qc.measure(4096)
print(f"\nMeasurement results (4096 shots):")
for bits, count in sorted(counts.items()):
print(f" |{bits}âŠ: {count} ({count/4096*100:.1f}%)")
Expected output:
=== Bell State Circuit ===
Initial: |00âŠ
After H on q0: creating superposition
After CNOT: entangling q0 and q1
Final state vector: [0.70710678+0.j 0.+0.j 0.+0.j 0.70710678+0.j]
Expected: (|00⊠+ |11âŠ)/â2
Measurement results (4096 shots):
|00âŠ: 2043 (49.9%)
|11âŠ: 2053 (50.1%)
Example 2: GHZ State
The GHZ state (|000⊠+ |111âŠ)/â2 extends entanglement to three qubits.
# ghz_circuit.py
from simple_circuit import SimpleCircuit
# Create a 3-qubit GHZ state
qc = SimpleCircuit(3)
print("=== GHZ State Circuit ===")
# H on qubit 0, then cascading CNOTs
qc.h(0)
qc.cx(0, 1)
qc.cx(0, 2)
state = qc.state_vector()
print(f"Final state: {state}")
counts = qc.measure(4096)
print(f"\nMeasurement results (4096 shots):")
for bits, count in sorted(counts.items()):
if count > 0:
print(f" |{bits}âŠ: {count} ({count/4096*100:.1f}%)")
Expected output:
=== GHZ State Circuit ===
Final state: [0.70710678+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.70710678+0.j]
Measurement results (4096 shots):
|000âŠ: 2048 (50.0%)
|111âŠ: 2048 (50.0%)
Example 3: Quantum Random Number Generator
A quantum random number generator uses superposition to produce true random bits.
# quantum_rng.py
from simple_circuit import SimpleCircuit
import numpy as np
def quantum_random_bit():
"""Generate a single random bit using quantum superposition."""
qc = SimpleCircuit(1)
qc.h(0) # Create superposition
counts = qc.measure(1)
outcome = list(counts.keys())[0]
return int(outcome)
def quantum_random_integer(min_val, max_val):
"""Generate a random integer in a range using quantum randomness."""
range_size = max_val - min_val + 1
num_bits = int(np.ceil(np.log2(range_size)))
n_qubits = num_bits
qc = SimpleCircuit(n_qubits)
# Apply Hadamard to all qubits
for q in range(n_qubits):
qc.h(q)
counts = qc.measure(1)
outcome = int(list(counts.keys())[0], 2)
return min_val + (outcome % range_size)
print("=== Quantum Random Number Generator ===")
print("10 random bits:")
bits = [quantum_random_bit() for _ in range(10)]
print(f" {''.join(str(b) for b in bits)}")
print("\n10 random integers (1-100):")
nums = [quantum_random_integer(1, 100) for _ in range(10)]
print(f" {nums}")
Expected output (varies each run):
=== Quantum Random Number Generator ===
10 random bits:
1011001010
10 random integers (1-100):
[42, 87, 13, 65, 91, 34, 76, 28, 59, 45]
Circuit Depth and Width
Circuit width is the number of qubits. Circuit depth is the number of time steps (parallel operations count as one step).
graph TD
subgraph "Circuit Depth = 3, Width = 3"
t0("t=0: H(q0), H(q1)")
t1("t=1: CNOT(q0,q2)")
t2("t=2: CNOT(q1,q2)")
end
Minimizing depth is critical for near-term Quantum Computing since qubits decohere over time.
Common Mistakes
1. Confusing Circuit Width with Depth
Width = number of qubits. Depth = number of sequential operations. A wide shallow circuit may be easier to run than a narrow deep one.
2. Forgetting Initial States
All qubits initialize to |0⊠in standard circuit model. If you need a different starting state, you must apply X gates or other operations.
3. Measuring Mid-Circuit Incorrectly
Measurement collapses Superposition. After measurement, the qubit is in a definite state. Subsequent gates operate on the collapsed state, not the original Superposition.
4. Not Reinitializing Qubits
If you reuse a qubit, you must reset it to |0âŠ. Many hardware platforms support reset but it is not automatic.
5. Ignoring Gate Commutation
Some gates commute (they can be reordered) and some do not. For example, H and T do not commute. Incorrect reordering changes the computation.
Practice Questions
1. What is the difference between circuit width and circuit depth?
Width is the number of qubits. Depth is the number of sequential gate layers. Both affect the resources needed to run a circuit.
2. How do you create a Bell state using H and CNOT?
H on qubit 0, then CNOT with control qubit 0 and target qubit 1. The resulting state is (|00⊠+ |11âŠ)/â2.
3. What is a GHZ state and how is it created?
A GHZ state (|000...0⊠+ |111...1âŠ)/â2 is created by H on one qubit followed by cascading CNOTs from that qubit to all others.
4. Why does measurement outcome vary between runs of the same circuit?
Measurement is probabilistic. The same circuit can produce different outcomes, with probabilities determined by the squared amplitudes of the state vector.
5. How do you simulate a Quantum Circuit on classical hardware?
By representing the state as a 2âŋ-dimensional complex vector and applying unitary operations as matrix-vector multiplications. This is feasible for up to about 30-40 qubits.
Challenge: Build a Quantum Teleportation Circuit
Implement the quantum teleportation circuit that transfers an arbitrary qubit state from Alice to Bob using entanglement and classical communication:
def teleport_circuit():
"""Build and simulate a quantum teleportation circuit.
Qubit 0: message state (to be teleported)
Qubit 1: Alice's half of Bell pair
Qubit 2: Bob's half of Bell pair
Outcome: Qubit 2 should end up in the original state of qubit 0
"""
pass
Hints: Create a Bell pair between qubits 1 and 2. Apply CNOT from qubit 0 to 1, then H on qubit 0. Measure qubits 0 and 1. Apply conditional X and Z gates on qubit 2 based on the measurement results.
Real-World Task: Circuit Optimization
Take a given Quantum Circuit and optimize it for depth. Given:
H(q0), H(q1), CNOT(q0,q2), T(q0), T(q1), CNOT(q1,q2)
Try to parallelize operations that commute. Measure the depth before and after optimization. This is what quantum compilers do automatically in frameworks like Qiskit and Cirq.
FAQ
Try It Yourself
Run the Bell state circuit and modify it: try different initial states, add measurement mid-circuit, or create a W-state (|001⊠+ |010⊠+ |100âŠ)/â3. Observe how the measurement statistics change with different circuit structures.
What's Next
You now understand how to build quantum circuits. Next, you will explore entanglement â the phenomenon that makes Quantum Computing truly powerful.
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro. Last updated: 2026-06-21.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro