Cram sheet — Section 3: Create quantum circuits
One-screen review layer for this section: short background primers per objective, then the key facts — each backed by an official source or by an executed proof from the question bank.
Construct basic quantum circuits
Only the classical bits you asked for
A circuit has exactly the classical bits you declared, and measure needs one to write into.
qc = QuantumCircuit(2, 2) # 2 qubits AND 2 clbits, registers auto-named q and c
qc.measure(0, 1) # qubit first, clbit second: qubit 0's result lands in c[1]
Building from named QuantumRegister(3, 'data') and ClassicalRegister(2, 'c') objects is purely organizational — it lets you write data[0] — and buys nothing at execution or transpile time. Auto-naming is why add_register(QuantumRegister(2, 'q')) on a QuantumCircuit(2, 2) fails on the duplicate name.
| Method | Measures | Writes into |
|---|---|---|
measure(qubit, cbit) | the qubits you name (ints, Bits, registers or parallel lists) | the clbits you name — the mapping is yours, and the keyword is cbit, not clbit |
measure_all() | every qubit | a FRESH ClassicalRegister named meas, sized to the qubit count |
measure_active() | only qubits carrying operations | the same fresh meas register |
Results are little-endian: bitstrings print as c_(n−1) … c_0, with classical bit 0 on the RIGHT.
Sources: construct-circuits · measure-qubits · qiskit.circuit.QuantumCircuit
compose returns, append mutates, tensor widens
compose is functional by DEFAULT: it returns a new circuit and leaves the caller untouched, so base.compose(add_on) with the result discarded is a silent no-op.
big = base.compose(sub, qubits=[0, 2]) # keep the return value...
base.compose(sub, inplace=True) # ...or mutate in place
| Call | Mutates the caller? | Returns | Notes |
|---|---|---|---|
a.compose(b) | only with inplace=True | a new circuit | b may be narrower; qubits= maps its qubits onto a positionally; front=True prepends instead of appending |
a.append(instr, qargs) | yes | an InstructionSet | — |
a.tensor(b) | no | a widened circuit | the CALLER a takes the HIGHER-index qubits, so an X in a against an identity in b prepares |10⟩ |
Sources: construct-circuits · qiskit.circuit.QuantumCircuit
Must know:
- Counts are little-endian — flipping only qubit 0 of a 3-qubit register and calling
measure_all()yields001on every shot. — ⚙️ proven ins3-q010 qc.compose(sub, qubits=[0, 2])mapssub's qubit 0 → 0 and qubit 1 → 2, sosub.cx(0, 1)becomes a CX with control 0 and target 2. — ⚙️ proven ins3-q012width()is qubits + clbits (3 + 2 = 5),size()counts all operations including measures (5), anddepth()is the critical path (4) — three different numbers. — ⚙️ proven ins3-q015
Traps:
QuantumCircuit(2)has 2 qubits and ZERO classical bits, someasure([0,1],[0,1])raisesCircuitError; naming the arguments never rescues it. — ⚙️ proven ins3-q047measure_all()appends its freshmeasregister even when clbits already exist — two registers, space-separated counts keys — and the barrier it inserts goes BEFORE the measurements. — ⚙️ proven ins3-q011
Construct dynamic circuits
Dynamic circuits: four context managers
A dynamic circuit is one whose later quantum operations depend, in real time, on a mid-circuit measurement result — a teleportation correction, syndrome handling. Sweeping an angle over many jobs or post-selecting shots afterwards is classical work outside the QPU, so neither needs one.
Qiskit supports exactly four such constructs, each a context manager on the circuit, and every condition is a (classical_resource, value) TUPLE.
with qc.if_test((cr[0], 1)): # one tuple, not two arguments
qc.x(1)
| Construct | Opened with | Notes |
|---|---|---|
if_test | with qc.if_test((cr[0], 1)): | the resource may be a single Clbit OR a whole ClassicalRegister |
| else branch | with qc.if_test((cr[0], 0)) as else_: then with else_: | the else branch is the manager's return value |
for_loop | with qc.for_loop(range(3)): | as i is optional; the body may hold any number of gates and runs three times |
switch | with qc.switch(cr[0]) as case: then with case(0): | add a fallback with case(case.DEFAULT):, which may be declared last |
while_loop | a context manager like the others | the fourth supported construct |
For anything richer than equality with a literal, build the condition from qiskit.circuit.classical.expr.
Sources: classical-feedforward-and-control-flow · circuit_classical · qiskit.circuit.QuantumCircuit
The old conditional API is removed, not deprecated
Everything the pre-2.0 conditional API offered is gone, so there is no older spelling to fall back on.
| What you might write | What Qiskit 2.x does |
|---|---|
instr.c_if(cr[0], 1) | AttributeError — InstructionSet.c_if no longer exists |
a condition= gate keyword | there is no such keyword |
assigning to .condition | there is no such attribute |
qc.if_test(cr[0] == 1) | TypeError: 'bool' object is not subscriptable, because Clbit.__eq__ returns an ordinary Python bool |
if cr[0] == 1: in plain Python | evaluates to False at BUILD time: no gate is added and nothing warns you |
The last row is the dangerous one, because it is not an error at all: the circuit you get back is simply missing the gate.
Sources: classical-feedforward-and-control-flow · qiskit.circuit.QuantumCircuit
Must know:
- Each control-flow construct is stored as a SINGLE composite instruction, so
count_ops()on a conditional circuit showsif_else: 1(orfor_loop,switch_case), never the gates inside the body. — ⚙️ proven ins3-q029 expr.equal(cr, 3)is a validif_testcondition: it compares the whole register as an integer and lifts the bare Python3for you, with noexpr.liftrequired. — ⚙️ proven ins3-q053- The drawer renders a
for_loopas ONE boxed region labelled with the index set verbatim (For-0 range(0, 3)), spanning every wire the body touches; a statement after the block is drawn outside the box. — ⚙️ proven ins3-q058
Traps:
- The condition is ONE tuple:
if_test((cr[0], 1))is right,if_test(cr[0], 1)— two positional arguments — is the classic wrong spelling. — ⚙️ proven ins3-q024 - A
switchvalue with no matchingcasefalls through to DEFAULT:c0 = 1against branchescase(0)andcase(case.DEFAULT)runs the DEFAULT body. — ⚙️ proven ins3-q052
Construct parameterized circuits
Parameters sort alphabetically; binding is functional
qc.parameters is a ParameterView sorted ALPHABETICALLY by name, not by insertion order: add gamma, then alpha, then beta, and you get ['alpha', 'beta', 'gamma']. ParameterVector elements are the one refinement — they sort by vector name and then by numeric index, so x[10] follows x[9].
t = Parameter('theta')
qc.rx(2 * t, 0) # a ParameterExpression, evaluated at bind time
bound = qc.assign_parameters({t: np.pi}) # qc keeps theta; bound has none
| Binding form | Behaviour |
|---|---|
assign_parameters([0.5, 1.5]) | binds POSITIONALLY against that sorted view; needs one value per parameter, and a short list raises ValueError |
assign_parameters({th: 1.0}) | binds only the parameters it names, so partial binding is legal |
| dict keys | Parameter objects or their name strings, but matched by IDENTITY: a different Parameter('th') object built elsewhere raises CircuitError |
{x: [0.1, 0.2, 0.3]} | binds a whole ParameterVector in one entry |
inplace=True | mutates the circuit instead of returning a new one |
bind_parameters(...) | removed in Qiskit 2.x — AttributeError, not a deprecation warning |
That sorted view is what makes a list treacherous and a dict safe.
Sources: construct-circuits · qiskit.circuit.Parameter · qiskit.circuit.ParameterVector
Must know:
ParameterVector('x', 3)creates the distinct parametersx[0],x[1],x[2]; it does not auto-grow, sox[3]raisesIndexError. — ⚙️ proven ins3-q022- Reusing ONE
Parameterobject on two gates keepsnum_parametersat 1 and binds BOTH gates at once — the weight-sharing mechanism behind tied ansätze. — ⚙️ proven ins3-q051 - Parameters survive transpilation: transpile once into an ISA circuit, then bind as many times as you like — the bound circuit keeps its layout and needs no second transpile. — ⚙️ proven in
s3-q050
Traps:
- A list binds positionally against the sorted view:
[0.5, 1.5]on a circuit withrx(theta)thenrz(phi)givesphi = 0.5andtheta = 1.5— the reverse of gate order. — ⚙️ proven ins3-q017 2 * this aParameterExpression, not aParameter:isinstance(2 * th, Parameter)is False, whileqc.parametersstill reports one freeParameter. Binding keeps the symbolic type — cast withfloat(...). — ⚙️ proven ins3-q054
Transpile and optimize circuits
ISA circuits and the six preset stages
Hardware runs ISA circuits only: every instruction must be in the backend's Target AND every two-qubit gate must sit on a pair connected in its CouplingMap. Both conditions, every time.
pm = generate_preset_pass_manager(optimization_level=2, backend=backend)
isa = pm.run(qc) # a single circuit in, a single circuit out
| Stage | What it does |
|---|---|
init | unroll to one- and two-qubit gates |
layout | choose which physical qubits the virtual ones start on |
routing | insert SWAPs so every two-qubit gate lands on a connected pair |
translation | rewrite every remaining gate into the basis |
optimization | simplify |
scheduling | timing; the slot exists even with no scheduling method and simply runs empty |
pm.stages reports exactly those six names in that order, and ROUTING — not layout — is what inserts SWAPs. Hand pm.run a LIST and you get a list back, not a circuit. A circuit wider than the device has no valid layout at all and fails in the layout stage.
Sources: transpile · transpiler-stages · transpile-with-pass-managers
Four optimization levels, one target
Levels run 0 through 3; anything else, including 4, raises ValueError. All four aim at the SAME target constraints, so they differ in effort, not in whether the output is a valid ISA circuit.
| Level | Layout | Optimization added |
|---|---|---|
| 0 | TrivialLayout | none at all |
| 1 | trivial or VF2 layout search | light: one-qubit optimization plus InverseCancellation |
| 2 | no trivial layout | CommutativeCancellation |
| 3 | as level 2 | KAK resynthesis of two-qubit blocks, plus unitarity-breaking passes on measurements |
Higher is not automatically better: a higher level costs compile time and is not guaranteed to give fewer two-qubit gates. The docs' own example shows identical two-qubit counts at levels 1 and 2, with only level 3 improving on them. The guide pages still call optimization_level a required positional argument, while the 2.5 signature carries a default.
Sources: set-optimization · transpile-with-pass-managers
Must know:
generate_preset_pass_managerdefaults tooptimization_level=2in Qiskit 2.5 (a signature default, backend-independent) — level 1 wastranspile()'s default back in 1.x. — ⚙️ proven ins3-q031- Measured routing cost: one
cx(0, 4)on a 5-qubit line gives 10 CX at level 0 (trivial layout[0,1,2,3,4]) versus 1 CX at level 1 — the win is LAYOUT, not gate cancellation. — ⚙️ proven ins3-q032 initial_layout=[2, 3, 4]is an ORDERED virtual→physical map (virtual 0 → physical 2, 1 → 3, 2 → 4), not a set of permitted qubits. — ⚙️ proven ins3-q043- Passes by stage in a level-2 preset:
VF2Layout/SabreLayoutinlayout;CheckMap/SabreSwap/VF2PostLayoutinrouting;BasisTranslatorintranslationandinit;Optimize1qGatesDecompositioninoptimization;ConsolidateBlocksininit. — ⚙️ proven ins3-q055 - Level 0 schedules no optimization pass at all: nine gates in, nine out, where level 2 returns five — BOTH repeated pairs (
cx-cxandz-z) cancel together, never one without the other. — ⚙️ proven ins3-q057
Traps:
decompose()is not a route to ISA: it rewriteshintou, which is outside the IBM basis. Anh, accx, a non-adjacentcxand an over-wide circuit all fail the check. — ⚙️ proven ins3-q033- A pass manager runs with
pm.run(qc);pm.transpile(qc),pm(qc)andqc.transpile(pm)do not exist. — ⚙️ proven ins3-q040 - Without a backend object, pass
basis_gates=ANDcoupling_map=;basis_gatesalone translates but does not route, so the output is not ISA. — ⚙️ proven ins3-q035
- Declare the classical bits before you measure —
QuantumCircuit(2)has none. - Read every counts bitstring right-to-left: classical bit 0 is the rightmost character.
- Assign the result of
composeandassign_parameters, or passinplace=True. - Write each condition as one
(resource, value)tuple inside awithblock;c_ifis gone. - Sort
qc.parametersalphabetically in your head before binding a positional list — or bind a dict. - Check BOTH ISA conditions on anything you send to hardware: basis gates and coupling map.
- Name the six preset stages in order, and say which one inserts the SWAPs.
- Run a pass manager with
pm.run(qc), at an optimization level between 0 and 3.
Every fact above is sourced: 📖 links go to official documentation, ⚙️ marks facts observed by executing code against the pinned Qiskit stack (the linked section page shows the proof evidence on its practice questions).