Testing Quantum Circuits with Qiskit Fake Backends
Use Qiskit fake backends to simulate real IBM quantum device noise locally, enabling fast and realistic circuit testing without using hardware credits.
Circuit diagrams
Real IBM quantum hardware is valuable and metered. The free tier allows a limited number of jobs per month, and every buggy circuit you submit wastes credits that you cannot get back. Before committing to a hardware run, you want confidence that your circuit actually works. Qiskit’s fake backends give you that confidence by replicating a real device’s coupling map, basis gates, and noise model, all running entirely on your local machine.
Why This Workflow Matters
Testing with fake backends serves four distinct goals, and each one requires a different tool:
-
Verify circuit logic. Does the circuit produce the correct probability distribution? Run it on an ideal simulator first, then on a noisy fake backend. If the ideal results are wrong, you have a logic bug. If the noisy results are unexpectedly bad, your circuit may be too deep for current hardware.
-
Verify transpilation. Are qubits routed correctly for the device topology? The transpiler inserts SWAP gates to connect qubits that are not physically adjacent. A fake backend lets you inspect the transpiled circuit and confirm that routing is correct before you pay for a hardware run.
-
Estimate noise impact. Will noise destroy the signal in your measurement results? Named fake backends carry calibration data from real devices, so their noise models reflect actual gate error rates, readout errors, and decoherence times. Running your circuit against this noise model tells you whether the signal-to-noise ratio is viable.
-
Run CI/CD tests without hardware access. Fake backends need no API key, no network connection, and no credits. They run in seconds on a laptop. This makes them ideal for automated test suites that validate circuit correctness on every commit.
These four goals appear throughout this tutorial. Each section addresses one or more of them.
Two Types of Fake Backends
Qiskit ships two approaches:
GenericBackendV2 (included in qiskit itself): Creates a synthetic backend with configurable qubit count, coupling map, and basis gates. No extra package required. Good for topology-aware transpilation testing when you do not need device-accurate noise.
Named device backends (FakeSherbrooke, FakeKyoto, etc.): Mirror specific IBM devices with calibration-derived noise parameters. Require pip install qiskit-ibm-runtime. Best for realistic noise estimation before a real hardware run.
The key difference: GenericBackendV2 uses a parameterized synthetic noise model, while named backends carry a snapshot of real calibration data. If you need to predict how a specific device will behave, use a named backend. If you just need “some realistic topology and noise,” GenericBackendV2 is simpler and faster to set up.
Installation
pip install qiskit qiskit-aer
# For named device backends (FakeSherbrooke, FakeKyoto, etc.):
pip install qiskit-ibm-runtime
Named Fake Backends Reference
The qiskit-ibm-runtime package includes fake versions of many IBM Quantum devices. Here are the most commonly used ones:
| Fake Backend | Real Hardware | Qubits | Processor | Notes |
|---|---|---|---|---|
FakeSherbrooke | ibm_sherbrooke | 127 | Eagle r3 | Heavy-hex topology |
FakeKyoto | ibm_kyoto | 127 | Eagle r3 | Heavy-hex topology |
FakeTorino | ibm_torino | 133 | Heron r2 | Newer generation |
FakeAlgiers | ibm_algiers | 27 | Falcon r5.11 | Smaller device, fast simulation |
FakeMontreal | ibm_montreal | 27 | Falcon r4 | Smaller device, fast simulation |
To list all available fake backends programmatically:
from qiskit_ibm_runtime.fake_provider import FakeProviderForBackendV2
provider = FakeProviderForBackendV2()
for backend in provider.backends():
print(f"{backend.name}: {backend.num_qubits} qubits")
The 27-qubit backends simulate much faster than the 127+ qubit backends. For quick iteration, start with a smaller device and move to a larger one when you need to test at scale.
GenericBackendV2: No Extra Package Required
GenericBackendV2 generates a synthetic backend for any qubit count. It applies a noise model derived from a parameterized gate error rate.
from qiskit import QuantumCircuit, transpile
from qiskit.providers.fake_provider import GenericBackendV2
from qiskit_aer import AerSimulator
# Create a 5-qubit synthetic backend
generic_backend = GenericBackendV2(num_qubits=5, seed=42)
# Build a GHZ state
qc = QuantumCircuit(3)
qc.h(0)
qc.cx(0, 1)
qc.cx(1, 2)
qc.measure_all()
# Wrap in AerSimulator to run with noise
sim = AerSimulator.from_backend(generic_backend)
# Transpile for the device topology
qc_transpiled = transpile(qc, backend=sim, seed_transpiler=42)
# Run locally with noise
job = sim.run(qc_transpiled, shots=4096)
counts = job.result().get_counts()
print(sorted(counts.items(), key=lambda x: -x[1])[:5])
# Output: [('000', ~2000), ('111', ~1800), ('001', ~60), ('110', ~60), ('010', ~40)]
# Noise causes a small fraction of shots to land in non-ideal states
The output contains mostly 000 and 111 (the GHZ state), with noise pushing a few percent of shots into other states.
Inspecting Backend Properties
Before running a circuit on a fake backend, you want to know what the device looks like. Understanding the coupling map, basis gates, and error rates helps you make informed choices about qubit placement and circuit design.
from qiskit_ibm_runtime.fake_provider import FakeSherbrooke
backend = FakeSherbrooke()
# Coupling map: which qubits are physically connected
print("Coupling map (first 10 edges):", list(backend.coupling_map)[:10])
# Basis gates: what gates are natively supported
print("Basis gates:", backend.operation_names)
# Number of qubits
print("Qubits:", backend.num_qubits)
For named fake backends, you can also inspect per-qubit calibration data:
from qiskit_ibm_runtime.fake_provider import FakeSherbrooke
backend = FakeSherbrooke()
props = backend.properties()
# T1 relaxation time for qubit 0 (in microseconds)
print(f"T1 for qubit 0: {props.t1(0):.1f} us")
# T2 dephasing time for qubit 0
print(f"T2 for qubit 0: {props.t2(0):.1f} us")
# CNOT error rate between qubits 0 and 1
print(f"CX error (0,1): {props.gate_error('cx', [0, 1]):.5f}")
# Readout error for qubit 0
print(f"Readout error qubit 0: {props.readout_error(0):.5f}")
These numbers come from the calibration snapshot embedded in the fake backend. On real hardware, calibration data drifts over time, but the fake backend always returns the same snapshot.
Selecting the Best Qubits
Real devices have non-uniform noise. Some qubits have lower error rates, longer coherence times, and better connectivity than others. You can use the fake backend’s calibration data to identify the best qubits for your circuit.
from qiskit_ibm_runtime.fake_provider import FakeSherbrooke
backend = FakeSherbrooke()
props = backend.properties()
# Rank qubits by T1 time (higher is better, means slower relaxation)
t1_times = [(q, props.t1(q)) for q in range(backend.num_qubits)]
best_qubits = sorted(t1_times, key=lambda x: x[1], reverse=True)[:5]
print("Top 5 qubits by T1:")
for qubit, t1 in best_qubits:
print(f" Qubit {qubit}: T1 = {t1:.1f} us")
Once you identify the best qubits, you can pass them to the transpiler using initial_layout:
from qiskit import QuantumCircuit, transpile
from qiskit_ibm_runtime.fake_provider import FakeSherbrooke
backend = FakeSherbrooke()
props = backend.properties()
# Select the 3 qubits with highest T1
t1_times = [(q, props.t1(q)) for q in range(backend.num_qubits)]
best_3 = sorted(t1_times, key=lambda x: x[1], reverse=True)[:3]
best_qubit_indices = [q for q, _ in best_3]
# Build a 3-qubit circuit
qc = QuantumCircuit(3)
qc.h(0)
qc.cx(0, 1)
qc.cx(1, 2)
qc.measure_all()
# Transpile with preferred qubit placement
qc_t = transpile(
qc,
backend,
initial_layout=best_qubit_indices,
optimization_level=1,
seed_transpiler=42,
)
print(f"Circuit mapped to physical qubits: {best_qubit_indices}")
print(f"Transpiled depth: {qc_t.depth()}")
Note that initial_layout is a suggestion, not a guarantee. The transpiler may need to reroute qubits if the selected qubits are not all directly connected in the coupling map. Check the transpiled circuit to confirm the final qubit assignment.
Comparing Ideal vs. Noisy Results
Running the same circuit on both an ideal simulator and a noisy backend shows you how much noise will degrade your results:
from qiskit import QuantumCircuit, transpile
from qiskit.providers.fake_provider import GenericBackendV2
from qiskit_aer import AerSimulator
qc = QuantumCircuit(2)
qc.h(0)
qc.cx(0, 1)
qc.measure_all()
# Ideal simulation
ideal_sim = AerSimulator()
ideal_counts = ideal_sim.run(transpile(qc, ideal_sim), shots=4096).result().get_counts()
# Noisy simulation
noisy_backend = GenericBackendV2(num_qubits=5, seed=0)
noisy_sim = AerSimulator.from_backend(noisy_backend)
noisy_counts = noisy_sim.run(transpile(qc, noisy_sim), shots=4096).result().get_counts()
print("Ideal:", sorted(ideal_counts.items()))
# Output: Ideal: [('00', ~2048), ('11', ~2048)]
print("Noisy:", sorted(noisy_counts.items()))
# Output: Noisy: [('00', ~1950), ('01', ~45), ('10', ~45), ('11', ~1900)]
# A few percent error rate from gate noise and readout errors
The difference between ideal and noisy counts estimates how much error you will see on real hardware. If the noisy simulation already shows unacceptable error rates, you need to simplify your circuit or apply error mitigation techniques before running on hardware.
The Noise Model Pipeline in Depth
When you call AerSimulator.from_backend(fake_backend), Aer automatically extracts a noise model from the backend’s calibration data. This noise model includes three types of errors:
- Gate error rates: Depolarizing error applied after each gate, with error probability specific to each gate type and each qubit (or qubit pair for two-qubit gates).
- Readout error rates: Probability of measuring
0when the qubit is actually in state|1>, and vice versa. These are classical bit-flip errors applied during measurement. - T1/T2 relaxation: Amplitude damping (T1) and dephasing (T2) that accumulate during circuit execution. Longer circuits suffer more from relaxation.
You can extract and inspect the noise model directly:
from qiskit_aer.noise import NoiseModel
from qiskit_ibm_runtime.fake_provider import FakeSherbrooke
backend = FakeSherbrooke()
noise_model = NoiseModel.from_backend(backend)
# Print a summary of the noise model
print(noise_model)
# List all quantum errors in the model
print("\nQuantum errors:")
for instruction, qubits in noise_model.noise_instructions:
print(f" {instruction} on qubits {qubits}")
Understanding the noise model helps you predict which parts of your circuit are most vulnerable. Two-qubit gates (like CX) typically have error rates 10x higher than single-qubit gates, so circuits with many CX gates are more noise-sensitive.
Building Custom Noise Models
Sometimes you want to study the effect of specific error rates on your algorithm rather than using a device’s calibration data. You can build a noise model from scratch:
from qiskit_aer import AerSimulator
from qiskit_aer.noise import NoiseModel, depolarizing_error, ReadoutError
from qiskit import QuantumCircuit, transpile
# Build a custom noise model
noise_model = NoiseModel()
# Add 1% depolarizing error to all single-qubit gates
error_1q = depolarizing_error(0.01, 1)
noise_model.add_all_qubit_quantum_error(error_1q, ['h', 'rx', 'ry', 'rz'])
# Add 5% depolarizing error to all CNOT gates
error_2q = depolarizing_error(0.05, 2)
noise_model.add_all_qubit_quantum_error(error_2q, ['cx'])
# Add readout error: 2% chance of flipping measurement result
readout = ReadoutError([[0.98, 0.02], [0.02, 0.98]])
noise_model.add_all_qubit_readout_error(readout)
# Run a circuit with the custom noise model
sim = AerSimulator(noise_model=noise_model)
qc = QuantumCircuit(2)
qc.h(0)
qc.cx(0, 1)
qc.measure_all()
result = sim.run(transpile(qc, sim), shots=4096).result()
counts = result.get_counts()
print("Custom noise results:", sorted(counts.items(), key=lambda x: -x[1]))
Custom noise models are useful for several purposes:
- Sensitivity analysis: Sweep the error rate from 0.1% to 10% and see where your algorithm breaks down.
- Worst-case testing: Set error rates higher than any current device to stress-test your error mitigation strategy.
- Simplified debugging: Use only readout error (no gate error) to isolate whether measurement noise is the main problem, or vice versa.
Optimization Levels and Their Effect
The transpile() function accepts an optimization_level parameter that controls how aggressively the transpiler optimizes your circuit. The levels range from 0 to 3:
- Level 0: Minimal work. Maps virtual qubits to physical qubits and decomposes gates into the basis gate set. No optimization passes.
- Level 1: Light optimization. Applies basic gate cancellation and simplification after routing.
- Level 2: Medium optimization. Adds commutativity-aware gate cancellation and more aggressive simplification.
- Level 3: Heavy optimization. Uses unitary synthesis, commutativity analysis, and resynthesis of gate sequences to minimize depth and gate count.
The difference in gate count between levels can be significant, especially for deeper circuits:
from qiskit import QuantumCircuit, transpile
from qiskit_ibm_runtime.fake_provider import FakeSherbrooke
backend = FakeSherbrooke()
# Build a circuit with several non-trivial operations
qc = QuantumCircuit(4)
qc.h(0)
qc.cx(0, 1)
qc.cx(1, 2)
qc.cx(2, 3)
qc.barrier()
qc.rx(0.5, 0)
qc.ry(0.3, 1)
qc.cx(3, 0) # Long-range connection, likely needs routing
qc.measure_all()
print(f"Original: depth={qc.depth()}, ops={dict(qc.count_ops())}\n")
for level in range(4):
qc_t = transpile(qc, backend, optimization_level=level, seed_transpiler=42)
ops = dict(qc_t.count_ops())
print(f"Level {level}: depth={qc_t.depth()}, cx={ops.get('cx', 0)}, "
f"total_gates={sum(v for k, v in ops.items() if k != 'measure' and k != 'barrier')}")
Higher optimization levels take longer to compile but produce shorter circuits. For circuits that will run on noisy hardware, the reduction in gate count directly translates to lower error rates. Level 1 is a good default; use level 3 when you need to squeeze out every possible gate.
Checking Transpilation for a Specific Topology
A key use of fake backends is verifying that your circuit transpiles correctly for the device’s coupling map:
from qiskit import QuantumCircuit, transpile
from qiskit.providers.fake_provider import GenericBackendV2
backend = GenericBackendV2(num_qubits=5, seed=0)
print("Coupling map:", backend.coupling_map)
qc = QuantumCircuit(3)
qc.h(0)
qc.cx(0, 2) # might not be a direct connection
qc.measure_all()
qc_t = transpile(qc, backend, optimization_level=3)
print("Original depth:", qc.depth())
print("Transpiled depth:", qc_t.depth())
print("Gates:", dict(qc_t.count_ops()))
If qubit 0 and qubit 2 are not directly connected, transpile inserts SWAP gates automatically. Each SWAP gate decomposes into 3 CX gates, so a single missing connection can add significant noise overhead.
Using Primitives with Fake Backends
Qiskit’s primitives provide a higher-level interface for running circuits. Both the Sampler and Estimator primitives work with fake backends.
StatevectorSampler (Ideal, No Noise)
For a quick ideal check with no noise at all, use StatevectorSampler:
from qiskit import QuantumCircuit
from qiskit.primitives import StatevectorSampler
qc = QuantumCircuit(2, 2)
qc.h(0)
qc.cx(0, 1)
qc.measure([0, 1], [0, 1])
sampler = StatevectorSampler()
result = sampler.run([(qc,)]).result()
counts = result[0].data.c.get_counts()
print(counts)
# Output: {'00': ~512, '11': ~512}
SamplerV2 with a Fake Backend (Noisy)
The SamplerV2 primitive from qiskit-ibm-runtime accepts fake backends directly:
from qiskit import QuantumCircuit
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import SamplerV2
from qiskit_ibm_runtime.fake_provider import FakeSherbrooke
backend = FakeSherbrooke()
qc = QuantumCircuit(2)
qc.h(0)
qc.cx(0, 1)
qc.measure_all()
# Transpile to the backend's ISA (instruction set architecture)
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuit = pm.run(qc)
# Run with noise using SamplerV2
sampler = SamplerV2(backend=backend)
job = sampler.run([isa_circuit])
result = job.result()
print(result[0].data.meas.get_counts())
EstimatorV2 with a Fake Backend
The Estimator primitive computes expectation values of observables. This is useful for variational algorithms (VQE, QAOA) where you need <psi|H|psi> under noise:
from qiskit import QuantumCircuit
from qiskit.quantum_info import SparsePauliOp
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import EstimatorV2
from qiskit_ibm_runtime.fake_provider import FakeSherbrooke
backend = FakeSherbrooke()
# Build a simple circuit
qc = QuantumCircuit(2)
qc.h(0)
qc.cx(0, 1)
# Transpile for the backend
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuit = pm.run(qc)
# Define an observable
observable = SparsePauliOp("ZZ")
# Map the observable to the physical qubit layout
isa_observable = observable.apply_layout(isa_circuit.layout)
# Compute expectation value under device noise
estimator = EstimatorV2(backend=backend)
result = estimator.run([(isa_circuit, isa_observable)]).result()
print(f"<ZZ> under noise: {result[0].data.evs:.4f}")
# Ideal value for Bell state: 1.0
# Noisy value will be slightly less than 1.0
The gap between the ideal expectation value (1.0 for <ZZ> on a Bell state) and the noisy result tells you how much noise affects your observable.
Comparing Multiple Fake Devices
When targeting hardware, you might have a choice of devices. You can compare noise levels across multiple fake backends to choose the best one for your circuit:
from qiskit import QuantumCircuit, transpile
from qiskit_aer import AerSimulator
from qiskit_ibm_runtime.fake_provider import FakeSherbrooke, FakeKyoto, FakeTorino
# Build a GHZ circuit
qc = QuantumCircuit(3)
qc.h(0)
qc.cx(0, 1)
qc.cx(1, 2)
qc.measure_all()
backends = [FakeSherbrooke(), FakeKyoto(), FakeTorino()]
ideal_states = {'000', '111'}
for backend in backends:
sim = AerSimulator.from_backend(backend)
qc_t = transpile(qc, backend, optimization_level=1, seed_transpiler=42)
job = sim.run(qc_t, shots=8192)
counts = job.result().get_counts()
# Compute fidelity metric: fraction of shots in ideal states
ideal_count = sum(v for k, v in counts.items() if k in ideal_states)
fidelity = ideal_count / 8192
print(f"{backend.name}: {fidelity:.1%} ideal outcomes, "
f"depth={qc_t.depth()}, cx_count={dict(qc_t.count_ops()).get('cx', 0)}")
The “ideal outcomes” percentage gives you a rough estimate of circuit fidelity on each device. Differences come from two sources: the device’s native noise levels and the transpiled circuit depth (which depends on the coupling map topology).
Writing Unit Tests with Fake Backends
Fake backends integrate naturally with pytest and any CI/CD system. They need no API key, no network, and run in seconds.
import pytest
from qiskit import QuantumCircuit, transpile
from qiskit.providers.fake_provider import GenericBackendV2
from qiskit_aer import AerSimulator
@pytest.fixture
def noisy_simulator():
"""Create a reproducible noisy simulator for testing."""
backend = GenericBackendV2(num_qubits=5, seed=42)
return AerSimulator.from_backend(backend)
def test_bell_state_has_no_01_or_10(noisy_simulator):
"""Bell state should produce only 00 and 11, even under noise."""
qc = QuantumCircuit(2)
qc.h(0)
qc.cx(0, 1)
qc.measure_all()
qc_t = transpile(qc, noisy_simulator, seed_transpiler=42)
counts = noisy_simulator.run(qc_t, shots=4096).result().get_counts()
# Even with noise, error states should be rare (<5%)
error_count = sum(v for k, v in counts.items() if k not in {'00', '11'})
assert error_count / 4096 < 0.05, f"Too many error states: {counts}"
def test_ghz_state_correlation(noisy_simulator):
"""GHZ state: all qubits should be in the same state."""
qc = QuantumCircuit(3)
qc.h(0)
qc.cx(0, 1)
qc.cx(1, 2)
qc.measure_all()
qc_t = transpile(qc, noisy_simulator, seed_transpiler=42)
counts = noisy_simulator.run(qc_t, shots=4096).result().get_counts()
# 000 and 111 should dominate
ideal_count = sum(v for k, v in counts.items() if k in {'000', '111'})
assert ideal_count / 4096 > 0.90, f"GHZ fidelity too low: {counts}"
def test_transpiled_circuit_uses_basis_gates():
"""Transpiled circuit should only contain basis gates."""
backend = GenericBackendV2(num_qubits=5, seed=42)
qc = QuantumCircuit(2)
qc.h(0)
qc.cx(0, 1)
qc.measure_all()
qc_t = transpile(qc, backend, optimization_level=1)
basis = set(backend.operation_names)
circuit_gates = set(qc_t.count_ops().keys()) - {'measure', 'barrier'}
assert circuit_gates.issubset(basis), (
f"Non-basis gates found: {circuit_gates - basis}"
)
These tests verify three different things: measurement distribution correctness, multi-qubit correlation, and transpilation validity. Each test runs in under a second on a laptop. Set the seed and seed_transpiler parameters to make tests deterministic.
Common Mistakes
Working with fake backends has a few pitfalls that catch many people. Here are the most common ones and how to avoid them.
Forgetting to transpile before running
If you skip the transpile() call, your circuit runs on the simulator but ignores the coupling map and noise model. The results will not reflect what happens on real hardware.
# Wrong: runs without transpilation, ignores topology
sim = AerSimulator.from_backend(fake_backend)
job = sim.run(qc, shots=4096) # No transpile!
# Correct: transpile first
qc_t = transpile(qc, fake_backend, optimization_level=1)
job = sim.run(qc_t, shots=4096)
Using GenericBackendV2 when you need device-accurate noise
GenericBackendV2 generates a synthetic noise model based on parameterized error rates. It does not reflect the actual noise characteristics of any real device. If your goal is to predict performance on a specific IBM system, use the corresponding named fake backend instead.
Assuming fake backend results predict hardware results precisely
Noise models in fake backends are snapshots of calibration data from a specific point in time. Real hardware drifts: error rates change between calibration cycles, cosmic rays cause transient errors, and neighboring qubits can interfere with each other in ways the model does not capture. Treat fake backend results as a useful estimate, not a precise prediction. Final validation always requires a run on real hardware.
Comparing ideal and noisy results without matching transpilation
A common mistake is comparing ideal AerSimulator results against noisy fake backend results when the two circuits have different structures. The transpiled circuit for a noisy backend has additional SWAP gates and a different depth, so the comparison is not apples-to-apples. For a fair comparison, transpile both circuits to the same backend:
from qiskit import QuantumCircuit, transpile
from qiskit.providers.fake_provider import GenericBackendV2
from qiskit_aer import AerSimulator
backend = GenericBackendV2(num_qubits=5, seed=42)
qc = QuantumCircuit(3)
qc.h(0)
qc.cx(0, 1)
qc.cx(1, 2)
qc.measure_all()
# Transpile once, run on both ideal and noisy simulators
qc_t = transpile(qc, backend, optimization_level=1, seed_transpiler=42)
ideal_sim = AerSimulator()
ideal_counts = ideal_sim.run(qc_t, shots=4096).result().get_counts()
noisy_sim = AerSimulator.from_backend(backend)
noisy_counts = noisy_sim.run(qc_t, shots=4096).result().get_counts()
print("Ideal (same circuit):", sorted(ideal_counts.items(), key=lambda x: -x[1])[:3])
print("Noisy (same circuit):", sorted(noisy_counts.items(), key=lambda x: -x[1])[:3])
This way both simulations run the same transpiled circuit, and the only difference is the noise model.
Treating fake backend runs as hardware validation
Running thousands of shots on a fake backend is fast and cheap, which makes it tempting to treat the results as a substitute for hardware validation. Fake backends are excellent for structural testing (does the circuit transpile correctly? is the output distribution approximately right?), but they are not a replacement for real hardware when you need precise numerical results. Use fake backends to catch bugs early, then validate on hardware for final results.
When to Use Fake Backends
| Scenario | Recommended Backend | Why |
|---|---|---|
| Testing transpilation on a topology | GenericBackendV2 | Fast, no extra install, configurable qubit count |
| Estimating noise before hardware run | Named fake (FakeSherbrooke etc.) | Calibration-based noise model |
| Unit testing circuit logic in CI/CD | GenericBackendV2 or ideal AerSimulator() | Deterministic with seed, no API key needed |
| Choosing between target devices | Multiple named fakes | Compare fidelity across devices |
| Studying effect of specific error rates | Custom NoiseModel | Full control over error parameters |
| Final pre-hardware validation | Named fake backend | Closest approximation to real device |
| Quick sanity check (no noise) | Ideal AerSimulator() | Fastest, isolates logic errors |
Fake backends are not a substitute for real hardware when you need accurate numerical results, but they dramatically reduce the trial-and-error cost of hardware experimentation by catching transpilation bugs, noise-sensitivity issues, and logic errors locally, all without spending a single hardware credit.
Was this tutorial helpful?