Skip to main content

How to read Qiskit figures

About one item in five of IBM's official sample test puts a picture in front of you: a circuit drawing, a histogram, a Bloch multivector, a q-sphere. Those items are cheap points if you can decode the picture at a glance — and expensive guesses if you cannot. This page is the decoder.

:::info Every figure here is an execution artifact Nothing on this page is hand-drawn illustration. Each figure is produced by running the code shown next to it against the pinned stack (Qiskit 2.5.0) and committing the SVG, exactly like the proof artifacts behind the practice questions. The generator lives at data/figures/guide/generate.py; it is run twice into separate directories and the two sets of SVGs are compared byte for byte, so anything time-dependent or randomly sampled would fail the check. The four broadcasting diagrams are the one exception: they are schematics drawn with matplotlib primitives, not Qiskit output. :::

1. Circuit diagrams (mpl)

from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister

qr = QuantumRegister(2, "q")
cr = ClassicalRegister(2, "c")
qc = QuantumCircuit(qr, cr)
qc.h(0)
qc.cx(0, 1)
qc.barrier()
qc.measure(0, 0)
with qc.if_test((cr[0], 1)) as else_:
qc.z(1)
with else_:
qc.x(1)
qc.measure(1, 1)

qc.draw(output="mpl")

Circuit diagram: H on q0, CX from q0 to q1, a barrier, a measurement from q0 into classical bit 0, an If/Else region on q1 conditioned on c_0 equals 1 with a Z in the If part and an X in the Else part, and a final measurement from q1 into classical bit 1.

Read it left to right, one wire per qubit:

  • Wire order. q_0 is drawn at the top by default — the opposite of the order in which qubits appear in a printed ket. draw(reverse_bits=True) flips the layout so the highest index is on top; it changes only the picture, never the circuit. (QuantumCircuit.reverse_bits() is a different thing: it returns a new circuit with permuted qubit indices, so its drawing still starts at q_0.)
  • Gate boxes carry the gate name; a filled dot is a control and the circled plus is the target of a CX. A parameterised gate shows its value inside the box, e.g. Rz(pi/2).
  • The barrier is the dashed vertical band. It is a scheduling/optimization fence, not an operation.
  • Measurements are the meter symbol, with a downward arrow into the classical wire and a small number naming the target classical bit.
  • The classical register is the doubled line at the bottom, labelled with the register name and a slash carrying its width (2 here). A single doubled wire can stand for many classical bits.
  • Control flow appears as a labelled, boxed region attached to the classical wire: an If part, and an Else part only when the code actually opened one. The condition is printed on the classical wire — c_0=0x1 means "classical bit c_0 equals 1". The other control-flow constructs render the same way: a switch draws a Switch box followed by one Case region per case (the default case included), and for_loop/while_loop draw their own labelled box — a For box carries its range, e.g. For-0 range(0, 3).
  • Transpiled circuits look different in one specific way: their wire labels read q_0 -> 0, that is virtual qubit to physical qubit. If you see those arrows, you are looking at post-layout output, and any SWAP you see was inserted by the routing stage.

Docs: Visualize circuits · Classical feedforward and control flow

2. Histograms

import numpy as np
from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector
from qiskit.visualization import plot_histogram

qc = QuantumCircuit(3)
qc.ry(2 * np.pi / 3, 0)
qc.cx(0, 1)
qc.x(2)

counts = {str(k): round(v * 1000)
for k, v in Statevector(qc).probabilities_dict().items()}
plot_histogram(counts)

Bar chart with two bars: outcome 100 at 250 and outcome 111 at 750, y axis labelled Count.

  • Bitstrings are little-endian. The rightmost character is qubit 0, so 100 means qubit 2 is 1 and qubits 1 and 0 are 0. A large share of histogram items are really endianness items in disguise.
  • Bars are sorted by outcome label by default (sort="asc"), not by height — so the tallest bar is not necessarily the last one.
  • The y axis follows your input: integer counts give a Count axis, probabilities give a Probabilities axis. plot_histogram has no shots parameter; normalization comes from the data itself.
  • bar_labels=True is the default, which is why every bar carries its value printed above it. Read the numbers before you compare heights.
  • number_to_keep=k keeps the k largest outcomes and pools everything else into one extra bar labelled rest, whose height is the sum of the folded counts (never their average, never the largest of them).
  • Multiple registers print with spaces: a key like '01 00' is two registers, with the last-declared register drawn leftmost. After measure_all() on a circuit that already had classical bits, expect exactly that shape.
  • plot_distribution is the same picture for quasi-probability data; legend= must be a list, one entry per execution.

Docs: Visualize results

3. Bloch multivector vs q-sphere

These two look superficially similar and encode completely different things. The exam exploits that.

Bloch multivector — one sphere per qubit

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

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

plot_bloch_multivector(Statevector(qc))

Two Bloch spheres side by side, titled qubit 0 and qubit 1; the qubit 0 arrow points along the +x axis and the qubit 1 arrow points down along the -z axis.

  • One sphere per qubit, titled qubit 0, qubit 1, … in index order (left to right), not in printed-ket order.
  • Each sphere shows that qubit's reduced state: the arrow is the Bloch vector of the single-qubit density matrix after tracing out the others.
  • Direction is the whole message. North is |0>, south is |1>, +x is |+>, +y is the state (|0> + i|1>)/sqrt(2). H takes north to +x; S is a quarter turn about z, so it takes +x to +y; Z maps +x to -x.
  • A missing arrow means entanglement (or mixture). For a Bell state the reduced Bloch vector of each qubit is zero, and the drawer renders both spheres with no arrow at all — the individual qubits have no definite direction. Two full-length arrows therefore mean a product state.
  • One sphere for a two-qubit state means somebody traced a qubit out first.

Q-sphere — one figure for the whole state

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

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

plot_state_qsphere(Statevector(qc))

Q-sphere with four markers: 000 at the north pole and 011 near the lower ring in one colour family, 001 and 010 on the upper ring; a phase colour wheel labelled 0, pi/2, pi, 3pi/2 is drawn at the lower right.

  • Position (latitude) is the Hamming weight of the basis state: all-zeros at the north pole, all-ones at the south pole, one ring per number of 1s in between. Longitude just spreads the states within a ring.
  • Marker size is the magnitude of that amplitude. Equal markers mean an even superposition; a missing marker means amplitude zero.
  • Marker colour is the phase, read against the colour wheel drawn beside the sphere (0 at the right, pi/2 at the top, pi at the left). Here t(0) puts a phase of pi/4 on every basis state whose qubit 0 is 1 — that is 001 and 011 — so those two markers differ in colour from 000 and 010.
  • Global phase is invisible: multiplying the whole state by a phase changes nothing in the picture. Only relative phases move colours.
  • show_state_phases=True prints the phase next to each label; it is off by default, which is exactly why a q-sphere item can hinge on colour alone.

:::caution Colour dependence Phase on a q-sphere is carried by colour and nothing else. Practice questions whose answer depends on it are marked color_essential and are dropped from the e-ink/EPUB build, where the alt text states the phases instead. :::

Docs: Plot quantum states · qiskit.visualization API

4. The four broadcasting patterns

An Estimator PUB is (circuit, observables, parameter_values), and the observables and the parameter values are broadcast against each other with the NumPy rules: compare shapes from the right, a length-1 axis stretches, a missing leading axis is padded with 1. The primitive input/output guide names exactly four patterns — learn to recognise them from the shapes.

Broadcast single observable

One observable, several parameter sets: observables shape () against parameter values shape (5,) gives (5,).

Diagram: a row of four shaded parameter-value boxes on top, a single shaded observable box below them inside a dashed row-wide outline captioned "the same observable, stretched", and a row of four expectation-value cells at the bottom with arrows pointing down into them.

Zip

Equal shapes pair element-wise: (5,) against (5,) gives (5,) — observable i is evaluated only at parameter set i.

Diagram: a row of four shaded parameter-value boxes, a row of four shaded observable boxes directly beneath them, and a row of four expectation-value cells, with one downward arrow per column showing element-wise pairing.

Zip and broadcast-single-observable produce the same result shape, so a result shape alone cannot tell them apart — look at the observables array: () means broadcasting a single observable, an array equal in shape to the parameter values means zip.

Outer / product

Complementary singleton axes — a column against a row: (4, 1) against (1, 6) gives (4, 6), every observable against every parameter set.

Diagram: a row of three shaded parameter-value boxes across the top, a column of two shaded observable boxes down the left, and a two-by-three grid of expectation-value cells between them, captioned expectation values (2, 3).

Standard nd generalization

Different ranks: parameter values (3, 6) against observables (2, 3, 1). The shorter shape is right-aligned and padded with a leading 1, then each length-1 axis stretches, giving (2, 3, 6).

Diagram: three rows of boxes showing shape algebra — parameter values (3, 6) padded to 1, 3, 6 with the leading box dashed and marked "padded"; observables 2, 3, 1 with the trailing box dashed and marked "stretched"; a rule; and the result 2, 3, 6.

Two shapes are not broadcastable when an axis pair disagrees and neither entry is 1 — (3,) against (4,) raises. And remember: a SparsePauliOp counts as one observable however many Pauli terms it holds, so a multi-term operator returns a single expectation value, not one per term.

Docs: Primitive inputs and outputs

Quick checklist for a figure item

  1. Circuit? Check the wire order first (q_0 top? q_v -> p labels?), then look for a boxed control-flow region and where each measurement lands.
  2. Histogram? Read the bitstrings right to left, read the printed bar values, and ask whether a rest bar should be there.
  3. Spheres? Count them. Many spheres means Bloch multivector (direction matters); one sphere with labelled markers means q-sphere (latitude, size and colour matter).
  4. Shapes? Write the observables shape above the parameter-values shape, right-aligned, and apply the three NumPy rules before naming the pattern.

Drill these on the Section 2 questions (visualization), Section 3 (transpiled-circuit drawings) and Section 4 (broadcasting).