What quantum compilers and transpilers do to a circuit, how they decide one circuit is better than another, the main families of methods and tools, and where research is heading, including compilers for fault-tolerant machines. Part 1 of a series on quantum circuit optimization.
A quantum compiler takes the circuit you wrote and returns one that a particular machine can run, usually with fewer gates. This post is a field guide to what happens in between, for readers who know what qubits, gates and circuit diagrams are but have never looked inside a compiler.
I wrote it while working on the Classiq Quantum Circuit Challenge, the subject of Part 2. The task was a phase oracle, a circuit that puts a minus sign on the basis states that stand for the black pixels of the Classiq logo. When I ran the best off-the-shelf compilers on the challenge’s baseline circuit, they made it about 6 percent shorter, while a redesign of the same oracle was 56 times shorter. The reason is that a compiler must keep the circuit’s behaviour on every input and is never told the facts that would allow a much shorter circuit, as a later section explains. Before that come the jobs a compiler does, the costs it optimises, the main families of methods, the tools you can install today, and where research is going, including compilers for fault-tolerant machines, which count costs very differently.
A classical compiler reads source code, turns it into an intermediate representation (IR) that is easy to analyse, runs a sequence of passes that each rewrite or analyse the IR, and finally emits instructions for one target processor. Quantum toolchains copied this shape. The paper describing Qiskit calls its design “similar to classical compiler infrastructures such as LLVM”
The IR is usually the circuit itself, as a list or a dependency graph of gates. The common text format for exchanging circuits is OpenQASM. Its third version adds classical control flow and timing, so a program can measure a qubit mid-circuit and branch on the result
You will also meet the word transpiler. Qiskit uses it for the part of the compiler that takes a circuit and returns a circuit, as opposed to the part that turns a high-level program into a first circuit. Most of this post is about transpilers, because that is where most optimisation happens.
The target matters at every step. A real device runs only a few native gates, typically one kind of two-qubit gate (CZ, for example) plus a handful of one-qubit rotations, and on most superconducting chips a two-qubit gate is possible only between physically neighbouring qubits. Gate errors, coherence times and gate durations differ from qubit to qubit and are recalibrated often.
The Qiskit documentation lists six stages for Qiskit’s preset pass managers, which I also read off the installed library (Qiskit 2.5.2):
Only one stage is called optimization, but every stage makes choices that change the result. Layout picks which physical qubit holds each of the program’s qubits. Routing then inserts SWAP gates, which exchange the states of two neighbouring qubits, until every two-qubit gate acts on neighbours. A poor layout forces routing to add many SWAPs, and no later pass removes all of them. The preset pipelines also come in optimization levels, from 0 to 3, which trade compile time for circuit quality; level 3 tries more passes and more layouts. Compile time is a real cost for large circuits and for workflows that compile many circuits.
tket organises its compiler differently but does the same jobs
A compiler needs a number to minimise, and there are several candidates.
These numbers can point in different directions. I ran three tools on the baseline circuit from Part 2, an 18-qubit circuit with depth 5329 and 3502 CNOT gates (CX in the figures). I used my own reproduction of it, which scores the same, allowed every pair of qubits to interact, and checked every output for correctness:
Qiskit at level 3 and tket followed by Qiskit improve depth and CNOT count a little. PyZX, a tool based on the ZX-calculus described below, cuts the T-count from 4247 to about 3160 with either of two methods, and one of them, full_reduce, pays for it with 8576 CNOTs instead of 3502. Under a cost model that counts only T gates, the two PyZX results are the best of the six; under one that counts CNOTs, full_reduce is the worst. For today’s noisy hardware the tket result is the one to run. PyZX counts every phase that is not a multiple of \(\pi/2\) as one T gate. Eleven of the circuit’s rotations have angles that are not multiples of \(\pi/4\), and a fault-tolerant compiler would expand each of those into many T gates, so compiled for such a machine every circuit here would need more T gates than the chart shows.
Sometimes the input is a description of what the circuit should do: a matrix, a state to prepare, a Boolean function, or a list of Pauli rotations from a Hamiltonian. Turning that into gates is called synthesis. It is also a tool inside optimisation, because a block of an existing circuit can be cut out, described as a matrix, and synthesised again.
Any two-qubit gate can be built from at most three CNOTs and some one-qubit gates, and some two-qubit gates need all three
For \(n\) qubits, exact decompositions of an arbitrary unitary exist, but the number of CNOTs they need grows like \(4^n\)
Numerical synthesis also makes approximate compilation natural. If the target is a distance \(\varepsilon\) instead of exact equality, a shorter circuit is often possible. Madden and Simonetto compress a standard decomposition of arbitrary unitaries by a factor of two “without practical loss of fidelity”
When the specification has structure, a method that knows the structure does far better than generic unitary synthesis.
Many algorithms need an oracle: a circuit that computes a classical function \(f(x)\) of the input bits, either into an extra qubit or as a sign on each basis state, as in Grover’s algorithm. Part 2 builds one. Computing \(f\) usually needs intermediate results, which go on helper qubits (ancillas) that start at 0. Once the answer has been used, the circuit runs the same steps backwards to return the helpers to 0, which is called uncomputation. Without it the helpers stay entangled with the input and spoil the interference the algorithm relies on.
Logic synthesis, borrowed from classical chip design, supplies the forms. One writes \(f\) as an exclusive sum of products (ESOP), an XOR of AND terms, each of which becomes a multi-controlled NOT gate
Most of what a transpiler calls optimisation takes a correct circuit and returns a shorter one that implements the same unitary. The methods fall into four families, which differ in how much of the circuit they look at.
The oldest methods are peephole rewrites borrowed from classical compilers. Two CNOTs in a row cancel, two rotations about the same axis merge into one, and a gate can often move past another (a \(Z\) rotation commutes with the control of a CNOT) to bring a cancelling pair together. Nam and co-authors built a fast optimiser for large circuits from rules of this kind, including a rotation-merging pass that finds rotations far apart in the circuit that act on the same parity, the XOR of the same set of qubit values
Local rewrites are fast and safe, and they are the bulk of every optimisation stage. They also stop early, at the first circuit where no single rule applies.
The second family cuts out a block and synthesises it again from its matrix. The two-qubit case above is the everyday example. BQSKit does the same for blocks of three or four qubits with numerical synthesis, which finds savings no rule list contains but costs much more time
The third family translates the circuit into a different notation, simplifies it there, and translates back. The best-known example is the ZX-calculus, a graphical language in which a circuit becomes a graph of green and red nodes (“spiders”) carrying phases
The hard step is turning the simplified graph back into a circuit, called extraction
The fourth family treats optimisation as a search. Instead of applying whichever rule helps right now, it tries many sequences of rewrites and may accept a worse circuit on the way to a better one, as in row 4 of the figure. Tools of this kind are sometimes called superoptimisers, after classical tools that search for the shortest program equivalent to a given one. Quartz generates every small rewrite rule that is valid for a given gate set and verifies each one
These tools spend more compile time, from minutes to hours, for better circuits, and each paper reports results on its own benchmark set, so the percentages are not directly comparable. In production, learning so far shows up in narrow passes. IBM’s transpiler service, for example, uses reinforcement learning to synthesise small Clifford, linear (CNOT-only) and permutation blocks and to route
On most superconducting chips a two-qubit gate is possible only between neighbouring qubits. The compiler has to choose which physical qubit holds each of the program’s qubits (layout) and insert SWAP gates to bring distant pairs together (routing). A SWAP costs three CNOTs, so routing can multiply a circuit’s size.
Choosing layout and SWAPs optimally is NP-complete. Siraichi and co-authors, writing at a classical compiler conference, called it qubit allocation, by analogy with register allocation, the classical compiler’s job of assigning variables to a processor’s few registers
Exact methods based on SAT and SMT solvers, general-purpose solvers for logical constraints, can find optimal layouts for small circuits
On real devices, a noise-aware compiler chooses the layout and schedule from the latest calibration data, steering gates away from the noisiest qubits
How much of this you need depends on the hardware. Trapped ions and neutral atoms can connect any pair of qubits, either directly or by moving atoms, so SWAP routing largely disappears and is replaced by scheduling the moves
Most of the methods above are available in a handful of packages. These are the ones you are most likely to meet:
| tool | what it is | strong at |
|---|---|---|
| Qiskit | IBM’s SDK; staged transpiler with a Rust core | the default for most users; fast routing (LightSABRE) |
| tket | Quantinuum’s retargetable compiler | peephole optimisation, many back ends |
| BQSKit | Berkeley’s synthesis-first toolkit | numerical resynthesis, approximate compilation |
| PyZX | ZX-calculus library | T-count reduction, experiments with ZX rules |
| VOQC | optimiser with machine-checked proofs | passes proved to preserve the circuit’s meaning |
| Quartz, QUESO, GUOQ | research superoptimisers | squeezing a circuit hard, given time |
| MQT (QMAP, QCEC) | Munich toolkit | exact mapping, equivalence checking |
The tools can be chained. On the circuits of Part 2 my best gate-level results came from running tket first and Qiskit after it.
Compilers have bugs, and a buggy optimisation pass returns a circuit that is wrong but looks good. Giallar, a project that verified the passes of Qiskit with an automated prover, covered 44 of 56 passes and found 3 bugs along the way
I learned to check every output end to end. In the off-the-shelf runs above, one PyZX recipe returned depth 4668, better than every correct result, and that circuit was wrong: the helper qubits did not return cleanly to 0, and some pixels got the wrong sign. During the contest, a tket setting that lets the compiler permute qubits at the end, and Qiskit’s default assumption that every qubit starts at 0, also gave me wrong oracles. In each case the tool did what it was told, under an assumption I had not checked.
QASMBench
Everything so far assumes today’s noisy hardware, often called NISQ (noisy intermediate-scale quantum), where each gate adds a little error and the compiler’s job is to use fewer gates. A large share of current compiler research targets a different machine: one with error-corrected logical qubits. On such a machine each logical qubit is a patch of hundreds of physical qubits running an error-correcting code, usually the surface code. The cost model changes completely
An error-corrected machine runs a small fixed gate set exactly. The usual choice is the Clifford gates (H, S, CNOT) plus the T gate, a rotation by \(\pi/4\) about \(Z\). Clifford gates are comparatively cheap to run fault-tolerantly; each T gate needs a magic state, described below. Any other rotation must be approximated by a sequence of these gates. The generic Solovay–Kitaev algorithm does it with a number of gates that grows polylogarithmically in \(1/\varepsilon\)
Because T gates dominate the cost, T-count and T-depth became optimisation targets of their own. A circuit made of CNOT and T gates can be written as a phase polynomial, a sum of terms that each say which parity of the qubits receives which phase. Re-synthesising the polynomial gives a polynomial-time method for T-count and T-depth reduction
Fault-tolerant compilers also have to use measurement, which an optimiser that sees only unitaries cannot. A Toffoli gate can be done with four T gates instead of seven if the circuit may measure and correct
On a surface-code machine, logical operations between patches are done by lattice surgery, which merges and splits patches along paths through free space on the chip
Optimal lattice-surgery compilation is NP-hard
Each T gate consumes a magic state, a special state prepared on the side, and preparing magic states has long been the dominant cost in estimates of what a computation needs. Distillation, which turns many noisy copies into fewer good ones, became cheaper than it was assumed to be
A NISQ compiler’s output can be run today. A fault-tolerant compiler’s output mostly cannot yet, so its final score is a resource estimate: how many physical qubits and how much time the computation would need on a stated hardware model. Estimators such as Microsoft’s Azure Resource Estimator
Not everything has to be rebuilt for the new costs. A 2025 study found that ordinary NISQ optimisation passes do lower fault-tolerant resource estimates, if chosen per application
The off-the-shelf tools gained only about 6 percent on the challenge circuit, and I think the main reason is what a transpiler is given. It receives a list of gates and must return a list that implements the same unitary. It is not told that some helper qubits always start in 0, that a block of gates computes a particular Boolean function, or that the program only needs the result on some inputs, so it cannot use any of these facts. In the challenge, six helper qubits start at 0, so 63 of every 64 inputs a transpiler must preserve are never used. Structure can also be lost before the transpiler sees it. A 2026 preprint measured this across Qiskit, tket, Cirq and the MQT tools. After a circuit made a round trip through OpenQASM 3, high-level operations such as multi-controlled gates had become plain gates, and asking the next compiler to re-synthesise them had no effect in any of the 360 cases tested. In one pipeline, passing an eight-qubit Grover circuit through OpenQASM 2 raised its two-qubit gate count by 37.2 percent
A compiler that knows such facts works at a higher level, and these levels have their own tools. High-level synthesis systems, Classiq’s among them, take a functional description of a program, together with constraints and goals, and search for a circuit that meets them
In the challenge in Part 2, off-the-shelf gate-level tools took the baseline circuit from depth 5329 to 5004, and a rescheduler I wrote, which only reorders gates, took it to 4665. Logic synthesis from the logo’s truth table reached 1886. Designing the computation by hand, then searching over the designs, reached 95, with gate-level polishing doing the last stretch from 107.
In practice I would still run an off-the-shelf transpiler on any circuit. It is fast and removes routine waste, and its output can be checked against the input. When I needed far more than a few percent, the savings came from changing the description of the problem, and Part 2 shows how that went for the logo.
Surveys and one tutorial paper, roughly from broad to narrow: