- Demos/
- Quantum Computing/
Introducing the Surface Code
Introducing the Surface Code
Published: July 13, 2026. Last updated: July 13, 2026.
The surface code is the gold standard for deploying quantum error correction (QEC) on hardware today. Not only does it provide a high circuit-level error threshold (around 1%), but it only requires local connectivity, making it amenable to hardware with 2D nearest-neighbor connectivity, such as superconducting qubits.
Beyond it being a fully-functioning QEC code, it serves as a great introduction to the world of fault-tolerant quantum computing (FTQC), because its working principles are ubiquitous in most modern QEC codes such as qLDPC codes that break the locality requirement and are more qubit efficient.
A lot has happened since its early inception in the 90s, so we are going to give a modern 2026 overview of its components in this demo. In particular, we are going to learn about stabilizers, logical operators, Pauli based computation via lattice surgery, and error detection & correction — all of which are also relevant in more general qLDPC codes.
Introduction
We are going to take a look at the planar, two-dimensional, rotated surface code. This is a modern variant of the Kitaev surface code [1] [2] and not to be confused with the toric code or one of the many other variants that generalize it.
The rotated surface code consists of alternating X (blue) and Z (yellow) squares that make up a lattice. The so-called data qubits sit on the vertices (highlighted in pink). These will be the qubits that encode the quantum information of a \(d \times d\) surface code patch that makes up a single logical qubit (we will use \(d=5\) throughout for simplicity):
We additionally have so-called syndrome qubits (in light gray) that sit in the middle of the squares or keystones of the arches. These syndrome qubits are used to continuously perform measurements on the surrounding data qubits in a non-destructive way. These operators being measured are called stabilizers and make up the backbone of almost all modern QEC codes. In the rotated surface code, they are alternating squares with a product of four \(X\) or \(Z\) operators. Additionally, there are weight-2 \(X\) and \(Z\) arches on the edges (more on that later). These suffice to detect any arbitrary \(X\), \(Y\), or \(Z\) error of weight up to \(d-1\).
These stabilizers on the data qubits are measured indirectly via the syndrome qubits. This is done by entangling the data qubits with the corresponding syndrome qubit and then measuring that (see also Fig. 2 in [4]).
The measurement result or $-1$ or $+1$ of a stabilizer measurement indicates whether or not an error has occurred, respectively. A \(d \times d\) surface code qubit is said to have code-distance \(d\) and can detect up to \(d-1\) errors, and correct up to \(\left\lfloor \tfrac{d-1}{2} \right\rfloor\). When more errors occur, they may go unnoticed or get corrected in the wrong way, as we will see later.
Before we get into more detail and expand on each of these components, we want to stress the difference between the rotated surface code (left) to the original planar surface code (right):
In the central image we see their correspondences. Note that the solid pink lines are merely a guide to the eye and do not represent physical connectivity. They are typically used in this way to discern face plaquette operators of \(Z\) stabilizers, and vertex or star operators of \(X\) stabilizers. They are very similar, but the rotated surface code effectively halves the required qubits from \(4d^2 - 4d + 1\) to \(2d^2-1\) for a given code-distance \(d\).
For the purpose of this demo, we shall refer to it just as the surface code.
Stabilizers
The surface code is a stabilizer code, which means that quantum information is encoded in the joint \(+1\) eigenspace of a set of commuting observables \(\mathcal{S}\) called stabilizers. These stabilizers \(S_i \in \mathcal{S}\) are products of four (or two) \(Z\) or \(X\) operators arranged in a checkerboard.
All these stabilizers commute with each other - that is one of their defining properties.
For the surface code layout shown here, you can easily verify this using the rule that two
Pauli words commute if and only if they anticommute on an even number of sites.
Take for example the simplest case, two sites [0, 1], such that we have \([X_0 X_1, Z_0 Z_1] = 0\).
The measurement outcome of such a stabilizer is binary \(\pm 1\) and we assume that the underlying quantum state \(|\psi\rangle\) that is encoded by the qubit of the surface code is in the so-called code (sub)space - the joint \(+1\) eigenspace of all stabilizer measurements. That means that the action of any stabilizer \(S_i \in \mathcal{S}\) on a state \(|\psi\rangle\) in the code space is equal to the identity, \(S_i |\psi\rangle = + 1 |\psi\rangle\).
By continuously measuring all stabilizers we can ensure that the state is not leaving the code space.
Because the stabilizer measurement itself is prone to error (the CNOT and Hadamard gates in the syndrome extraction circuit above are noisy),
we typically repeat this process, performing \(d\) rounds of syndrome extraction.
A single parallel measurement of all stabilizers is called a round. Repeating this
measurement \(d\) times builds a historical timeline that helps us isolate the root cause
of an error. A true data qubit error alters the system permanently, leading to a persistent,
repeated change in the error syndrome over time. In contrast, a measurement error behaves
like a temporary glitch, manifesting as an isolated, singular event in the syndrome history.
Once the former is identified across these \(d\) rounds, the system must account for it
and correct it. As we will see later, this correction is handled entirely in software,
bypassing the need to apply error correction operations physically.
Overall, these stabilizer measurements allow us to deterministically detect up to \(d-1\) single-qubit \(X\), \(Y\), or \(Z\) errors. Larger error strings that are equivalent to logical operators cannot be detected, as we will see in the next section.
Logical operators: Z and X edges
You may have noticed that we have weight-2 stabilizers (arches) at the edges of our surface code patch. These are crucial for defining logical operators on the surface code patch.
Logical operators \(Z_L\) or \(X_L\) need to commute with all stabilizers so they don’t move our state outside the code space. At the same time, they must not be stabilizers (or products thereof) themselves. On top of that, they of course need to satisfy the fundamental anti-commutation relation \(X_L Z_L = -Z_L X_L\).
On a rotated surface code, these operators form physical chains across the lattice. A logical \(X_L\) operator is a string of Pauli \(X\) operators on data qubits that connects the two edges with \(X\) arches (left and right here). And vice versa for a logical \(Z_L\) operator, as indicated below.
Multiplying a logical operator by a stabilizer does not change the underlying logical state, so the string on the right hand side is an equivalent logical operator (recall that \(Z^2=\mathbb{1}\)).
We can continue to deform the string to arrive at a logical \(Z_L\) operator that goes along the right edge.
This is why the left and right edges are called \(Z\) edges, which may be confusing because they contain \(X\) arches. The opposite is true for the top and bottom \(X\) edges with \(Z\) arches.
These arches define the logical operators, but they are also crucial from a mathematical point of view and restrict our \(d \times d\) patch to exactly one logical qubit. That is because the number of encoded qubits \(k\) is given by the difference of data qubits \(n\) and independent stabilizers \(s\), so \(k = n - s\). For the \(5 \times 5\) patch we are considering, we have \(n=25\) data qubits and \(16\) weight-4 stabilizers. Together with the \(8\) arches we get \(k = 25 - 16 - 8 = 1\).
The arches are also important for coverage of all possible errors. E.g., if a \(X\) error occurred on the top left data qubit, only the top left \(Z\) arch would catch it.
Error detection and correction
Let us first consider what actually happens if a single \(Z\) error occurs on one of the data qubits. The story works equivalently for \(X\) and \(Y\) errors. Before the \(Z\) error, each stabilizer measurement returns \(+1\), confirming the underlying state is in the correct code space. Now let us assume the central data qubit experiences a \(Z\) error. The surrounding \(Z\) stabilizers are unaffected by it, but the two \(X\) stabilizers yield a \(-1\) measurement - a defect, indicated by \(-1\) on the stabilizer square.
The tricky part about error correction is that we are only ever given the information of the syndrome measurements and do not know what actually has physically happened. The same error syndromes could have also occurred due to, e.g., the following error pattern.
This scenario is, however, exponentially more unlikely. A common decoding algorithm is Minimum-Weight Perfect Matching (MWPM). It pairs up the observed flipped stabilizers by finding the shortest, and therefore most likely, chain of physical errors that could have caused them. In the scenario in the image above, the (by far) most likely error string is simply the central \(Z\) error.
Consider the following situation, where two different weight-2 error strings lead to the same error syndrome.
Here, both errors have the same weight, so the choice for an MWPM decoder is ambiguous. Luckily, it does not matter which error we correct, as they are logically equivalent: they are the same error up to a \(Z\) stabilizer, in particular the one on the surface between the two defects.
In the following scenario, however, we will run into a real problem. Both error strings again lead to the same defect syndrome.
Now, if the (less likely) error with three errors occurs, but we correct for the (more likely) second scenario, we overall perform a logical \(Z_L\) operation, and introduce an undetected error in our computation. This is a manifestation of the fact that a distance \(d=5\) rotated surface code qubit can only correct
errors deterministically.
Intuitively, this makes sense. In a code with distance \(d\), logical operators are (at least) of weight \(d\). Logical operators change the logical state of the qubit without being noticed by any stabilizer. So the best we can do is detect errors up to \(d-1\). And we can only deterministically correct errors up to half the distance, because a wrong correction will make the total operation (error + correction) a logical operator that goes unnoticed.
Error correction is continuously performed during computation. Measuring all \(\mathcal{O}(d^2)\) stabilizers once (and typically in parallel) constitutes one round, which is often called a QEC clock cycle in hardware. A logical clock cycle corresponds to repeating these stabilizer measurements \(d\) times to ensure fault tolerance. We typically measure the runtime of a computation in logical clock cycles. Most notably, a Pauli product measurement can be performed in one logical clock cycle.
Finally, the actual error correction typically happens in software, and no physical correction terms are actively applied. Instead, one can track all detected errors based on their syndromes and then multiply them retrospectively with the final measurement results at the end of the computation. This reduces the runtime of the quantum computation as it avoids additional physical computations.
Quantum computation via lattice surgery
There are different variants of how to perform quantum computation with the surface code. Braiding is an older approach [5], but most modern approaches use lattice surgery [7] [6]. The concept is relatively simple: To measure \(Z_L \otimes Z_L\) between two surface code qubits, simply connect them via their \(Z\) edge (lattice merging), perform \(d\) rounds of measuring all stabilizers, including the intermediary ones, and finally destructively measure in between the two patches to split them again (lattice splitting). The logical \(Z_L \otimes Z_L\) measurement is indirectly determined via the product of the intermediate boundary stabilizers measured during those rounds of error correction. Note that this is different from terminal measurements where data qubits are measured directly. A \(X_L \otimes X_L\) measurement works analogously, but we stitch the two surface code patches along their \(X\) edges (see our lattice surgery demo for more details).
This is important because most modern surface code constructions are targeting Pauli based computation, where all logical operations can be reduced to such Pauli product measurements.
In fact, in the Game of Surface Codes [8], a popular framework for thinking about fault tolerant quantum computers, we forget about everything but the \(X\) and \(Z\) edges of our qubit patches. This results in rectangular boxes with solid (\(Z\)) and dotted (\(X\)) edges. The same \(Z_L \otimes Z_L\) measurement from above can be portrayed as
This diagram simply says, we measure qubits \(|q_1\rangle\) and \(|q_2\rangle\) along their \(Z\) edges via an intermediate auxiliary qubit region, indicated by the blue connection. So overall, this is just the joint measurement of \(Z_{q_1} \otimes Z_{q_2}\) via their \(Z\) edges.
Conclusion
In this demo, we have learned about the basics of modern rotated surface codes from qubit definitions and stabilizers to logical operators, error decoding, and computation. Most modern quantum error correction codes such as qLDPC codes work under the same principles, so you should be well-prepared for continuing your QEC journey down this path.
References
About the author
Korbinian Kottmann
Korbinian likes simulating quantum systems, whether it be via tensor network methods during his PhD, or with quantum computers at Xanadu. Currently, he works on quantum compilation to make simulation algorithms go brrr.