Hello World in OpenQASM
Write your first quantum program in OpenQASM 3, the universal quantum assembly language used by IBM Quantum, Qiskit, and cross-platform toolchains.
Circuit diagrams
OpenQASM is the assembly language of quantum computing. Just as x86 assembly sits below C code, OpenQASM sits below Python APIs like Qiskit. When you write circuit.h(0) in Qiskit, that call eventually compiles to an OpenQASM instruction before reaching hardware. The compilation pipeline looks like this:
Python (Qiskit) → OpenQASM 3 → Hardware pulse sequences
You do not need to write QASM directly for most work. But knowing it gives you several concrete advantages:
- You understand what is actually running on the machine. Qiskit abstracts away gate decomposition, qubit routing, and optimization. Reading the QASM output shows you the real circuit after transpilation.
- You can write circuits that Qiskit cannot express. QASM 3 supports mid-circuit measurement with classical control flow, custom gate definitions, and timing constraints that have no direct Python API equivalent.
- You can debug transpiler output. When a circuit behaves unexpectedly on hardware, exporting to QASM lets you inspect the exact gate sequence the device receives.
- You can port circuits between platforms. QASM is a vendor-neutral format. A circuit written in QASM can run on IBM Quantum, Amazon Braket, Quantinuum, and IonQ without rewriting.
What is OpenQASM?
OpenQASM (Open Quantum Assembly Language) is an open standard for describing quantum circuits at the instruction level. It defines a text-based format where each line specifies a gate operation, a measurement, or a classical instruction.
The version number matters. QASM 2, released in 2017, was essentially a flat list of gate applications with no control flow, no variables, and no subroutines. It served its purpose as a simple interchange format, but it could not express algorithms that required classical feedback during execution. QASM 3, the current standard, is a proper programming language with variables, classical control flow (if, while, for), user-defined functions, gate definitions, and timing specifications. QASM 2 is now deprecated, though you will still encounter it in older tutorials and codebases.
This distinction causes a common point of confusion in Qiskit. The method QuantumCircuit.from_qasm_str() parses QASM 2 only. To load QASM 3, you need qiskit.qasm3.loads(). Mixing these up produces cryptic parse errors, especially when QASM 3 features like if statements or register syntax appear in your source.
# QASM 2 loading (deprecated format, still supported for legacy circuits)
circuit = QuantumCircuit.from_qasm_str(qasm2_string)
# QASM 3 loading (use this for all new work)
import qiskit.qasm3 as qasm3
circuit = qasm3.loads(qasm3_string)
Setup: Running QASM Programs
The easiest way to run QASM is through Qiskit, which can parse and execute QASM programs directly:
pip install qiskit qiskit-aer
This gives you qiskit.qasm3 for parsing QASM 3 source strings and qiskit-aer for local simulation.
Example 1: Bell State in OpenQASM 3
A Bell state is the simplest entangled quantum state: two qubits that are perfectly correlated, so measuring one instantly determines the outcome of measuring the other. This is the complete QASM 3 source for creating one:
// OpenQASM 3.0 - Bell State
OPENQASM 3;
include "stdgates.inc"; // Standard gate library (H, CX, etc.)
// Declare quantum and classical registers
qubit[2] q; // 2 qubits
bit[2] c; // 2 classical bits
// Apply gates
h q[0]; // Hadamard: |0⟩ → (|0⟩ + |1⟩)/√2
cx q[0], q[1]; // CNOT: entangle q[0] and q[1]
// Measure into classical register
c = measure q;
Line-by-line breakdown
OPENQASM 3; declares the language version. This must be the first non-comment line in every QASM 3 file. The parser uses this to determine which syntax rules to apply. Omitting it causes a parse error.
include "stdgates.inc"; imports the standard gate library. This file defines all the commonly used gates: h (Hadamard), cx (CNOT), x, y, z (Pauli gates), s, t (phase gates), rx, ry, rz (rotation gates), ccx (Toffoli), swap, and others. Without this include, the parser does not recognize any gate names. You would need to define every gate from scratch using gate blocks.
qubit[2] q; declares a quantum register named q containing 2 qubits. You access individual qubits as q[0] and q[1]. QASM 3 also supports declaring individual qubits with qubit q0; (no brackets), which creates a single qubit named q0. The register syntax is more common because most circuits operate on multiple qubits.
bit[2] c; declares a classical register of 2 bits. Classical bits store measurement results. The register size should match the number of qubits you plan to measure.
h q[0]; applies the Hadamard gate to the first qubit, creating an equal superposition of |0⟩ and |1⟩.
cx q[0], q[1]; applies the controlled-NOT (CNOT) gate with q[0] as control and q[1] as target. If q[0] is |1⟩, the gate flips q[1]. Because q[0] is in superposition, this entangles the two qubits into the Bell state (|00⟩ + |11⟩)/√2.
c = measure q; measures the entire quantum register q and stores the results in classical register c. This is whole-register measurement: c[0] receives the result of measuring q[0], and c[1] receives the result of measuring q[1]. You can also measure individual qubits with c[0] = measure q[0];, which is useful when you need mid-circuit measurements or want to measure qubits at different points in the circuit.
Run it from Python
import qiskit.qasm3 as qasm3
from qiskit_aer import AerSimulator
# Load QASM 3 string directly (use qiskit.qasm3.loads, NOT from_qasm_str which is QASM 2)
qasm_str = """
OPENQASM 3;
include "stdgates.inc";
qubit[2] q;
bit[2] c;
h q[0];
cx q[0], q[1];
c = measure q;
"""
circuit = qasm3.loads(qasm_str)
# Simulate
simulator = AerSimulator()
job = simulator.run(circuit, shots=1024)
counts = job.result().get_counts()
print("Bell State counts:", counts)
Expected Output
Bell State counts: {'00': 506, '11': 518}
The resulting states are only 00 and 11, never 01 or 10. The Hadamard gate creates superposition, and the CNOT gate correlates the two qubits so they always agree. The approximate 50/50 split between 00 and 11 reflects the probabilistic nature of quantum measurement.
Example 2: GHZ State
The GHZ (Greenberger-Horne-Zeilinger) state is the three-qubit generalization of the Bell state. Where a Bell state entangles two qubits into (|00⟩ + |11⟩)/√2, a GHZ state entangles three qubits into (|000⟩ + |111⟩)/√2. GHZ states are important in quantum error correction, quantum communication protocols, and tests of quantum nonlocality. The construction follows the same pattern as a Bell state: apply a Hadamard to the first qubit, then chain CNOT gates to propagate entanglement.
OPENQASM 3;
include "stdgates.inc";
qubit[3] q;
bit[3] c;
h q[0];
cx q[0], q[1];
cx q[0], q[2];
c = measure q;
ghz_qasm = """
OPENQASM 3;
include "stdgates.inc";
qubit[3] q;
bit[3] c;
h q[0];
cx q[0], q[1];
cx q[0], q[2];
c = measure q;
"""
circuit = qasm3.loads(ghz_qasm)
job = AerSimulator().run(circuit, shots=1024)
print("GHZ counts:", job.result().get_counts())
Expected Output
GHZ counts: {'000': 498, '111': 526}
All three qubits are entangled; only all-zeros or all-ones outcomes appear. You can extend this pattern to any number of qubits by adding more CNOT gates, creating an N-qubit GHZ state.
Core QASM 3 Syntax
Here is a structured reference for the essential QASM 3 syntax elements. Every QASM 3 program uses some combination of these.
// === Version Declaration (required, must be first line) ===
OPENQASM 3;
// === Standard Library Import ===
include "stdgates.inc"; // Provides h, cx, x, y, z, s, t, rx, ry, rz, etc.
// === Qubit Declaration ===
qubit[3] q; // Register of 3 qubits, accessed as q[0], q[1], q[2]
qubit ancilla; // Single qubit named "ancilla" (no index needed)
// === Classical Bit Declaration ===
bit[3] c; // Register of 3 classical bits
bit flag; // Single classical bit
// === Gate Application ===
h q[0]; // Single-qubit gate
cx q[0], q[1]; // Two-qubit gate (control, target)
ccx q[0], q[1], q[2]; // Three-qubit gate (Toffoli)
rz(3.14159) q[0]; // Parameterized gate (angle in radians)
rx(pi / 4) q[1]; // You can use pi as a built-in constant
// === Measurement ===
c = measure q; // Measure entire register at once
c[0] = measure q[0]; // Measure a single qubit into a specific bit
flag = measure ancilla; // Measure single qubit into single bit
// === Gate Definition ===
gate my_gate a { // Define a custom gate on one qubit
h a;
s a;
}
my_gate q[0]; // Apply the custom gate
QASM 3 vs Python API
The QASM syntax provides a distinct, standardized language, while Python APIs offer a higher-level, object-oriented approach to quantum circuit design. These two representations are equivalent:
# These are equivalent:
# Qiskit Python API
from qiskit import QuantumCircuit
circuit = QuantumCircuit(2, 2)
circuit.h(0)
circuit.cx(0, 1)
circuit.measure_all()
# OpenQASM 3 (what Qiskit compiles to)
# OPENQASM 3;
# include "stdgates.inc";
# qubit[2] q;
# bit[2] c;
# h q[0];
# cx q[0], q[1];
# c = measure q;
# Round-trip: export from Qiskit to QASM 3
import qiskit.qasm3 as qasm3
print(qasm3.dumps(circuit))
The round-trip capability is useful for debugging. If a Qiskit circuit behaves unexpectedly after transpilation, calling qasm3.dumps(transpiled_circuit) shows you the exact gate sequence that will run on hardware.
Defining Custom Gates
One of QASM 3’s most practical features is the ability to define reusable custom gates. A custom gate packages a sequence of operations into a single named instruction that you can apply like any built-in gate.
For example, applying H followed by S creates the state |+i⟩ = (|0⟩ + i|1⟩)/√2. If you need this preparation step multiple times in a circuit, you can define it as a gate:
OPENQASM 3;
include "stdgates.inc";
// Define a gate that prepares the |+i⟩ state from |0⟩
gate plus_i a {
h a; // |0⟩ → (|0⟩ + |1⟩)/√2
s a; // (|0⟩ + |1⟩)/√2 → (|0⟩ + i|1⟩)/√2
}
qubit[3] q;
bit[3] c;
// Apply the custom gate to multiple qubits
plus_i q[0];
plus_i q[1];
plus_i q[2];
c = measure q;
Custom gates can also take parameters and operate on multiple qubits:
// A parameterized two-qubit gate
gate entangle(theta) a, b {
ry(theta) a;
cx a, b;
}
entangle(pi / 3) q[0], q[1];
When the transpiler encounters a custom gate, it decomposes it into the hardware’s native gate set. Defining custom gates does not change what runs on the device, but it makes your source code more readable and less error-prone.
Classical Control Flow
QASM 3 introduces classical control flow, one of the biggest improvements over QASM 2. In QASM 2, you could only measure qubits at the end of a circuit. QASM 3 allows mid-circuit measurement followed by conditional operations based on the result. This is essential for quantum error correction, teleportation, and repeat-until-success protocols.
Here is a simple example: measure one qubit, then conditionally apply a gate to another qubit based on the outcome.
OPENQASM 3;
include "stdgates.inc";
qubit[2] q;
bit[2] c;
h q[0];
c[0] = measure q[0];
// Apply X to q[1] only if q[0] measured as 1
if (c[0] == 1) {
x q[1];
}
c[1] = measure q[1];
In this circuit, the final state of q[1] depends on the measurement result of q[0]. If q[0] measures as 0, q[1] stays in |0⟩. If q[0] measures as 1, the X gate flips q[1] to |1⟩. The expected output is an equal mix of 00 and 11, similar to a Bell state but produced through classical communication rather than entanglement.
QASM 3 also supports while and for loops for classical control, though hardware support for these features varies by platform. IBM Quantum supports if statements through dynamic circuits; loop support is more limited and depends on the backend.
Common Mistakes
These are the errors that trip up most beginners (and some experienced users) when working with OpenQASM.
Confusing QASM 2 and QASM 3 loaders
# WRONG: from_qasm_str() only accepts QASM 2
circuit = QuantumCircuit.from_qasm_str(qasm3_source) # Parse error!
# CORRECT: use qiskit.qasm3.loads() for QASM 3
import qiskit.qasm3 as qasm3
circuit = qasm3.loads(qasm3_source)
If you see unexpected parse errors when loading a QASM string, check which loader you are using. This is the single most common QASM-related issue in Qiskit.
Forgetting the version declaration
// WRONG: missing version line
include "stdgates.inc";
qubit[2] q;
h q[0];
// CORRECT: OPENQASM 3; must be the first non-comment line
OPENQASM 3;
include "stdgates.inc";
qubit[2] q;
h q[0];
Forgetting include "stdgates.inc";
OPENQASM 3;
// Missing include!
qubit[2] q;
h q[0]; // Error: gate "h" is not defined
Without the include, the parser does not know about any standard gates. You will see “gate not found” or “undefined gate” errors for h, cx, x, rz, and every other standard gate.
Using QASM 2 measurement syntax
// WRONG: QASM 2 measurement syntax
measure q -> c;
// CORRECT: QASM 3 uses assignment syntax
c = measure q;
The arrow syntax (->) belongs to QASM 2. QASM 3 uses the assignment form. If you are adapting code from an older tutorial, this is one of the first things to update.
Platform Support
OpenQASM is a near-universal quantum intermediate representation. Learning it is beneficial regardless of the specific hardware you plan to target.
| Platform | QASM Version | Notes |
|---|---|---|
| IBM Quantum | QASM 3 | Full support via Qiskit Runtime, including dynamic circuits (if statements) |
| Qiskit Aer | QASM 3 | Local simulation from QASM 3 strings via qiskit.qasm3.loads() |
| Amazon Braket | QASM 3 | Import via braket.circuits; some QASM 3 features (loops, subroutines) may not be supported on all Braket devices |
| Quantinuum | QASM 3 | Native support via pytket; Quantinuum’s H-Series hardware supports mid-circuit measurement and classical control flow |
| IonQ | QASM 2/3 | QASM access via Qiskit or Amazon Braket; native API uses a JSON circuit format, not QASM directly |
| Rigetti | QASM 2 | Limited QASM support; Rigetti’s native format is Quil. QASM circuits can be converted via third-party tools |
Next Steps
You now have a working foundation in OpenQASM 3. From here, you can:
- Inspect transpiler output. Build a circuit in Qiskit, transpile it for a real backend, and call
qasm3.dumps()to see what the hardware actually receives. - Experiment with custom gates. Define gates for common subroutines in your algorithms and reuse them across circuits.
- Try classical control flow. Build a simple quantum teleportation circuit using mid-circuit measurement and
ifstatements. - Read the specification. The full OpenQASM 3 specification is well-written and covers features like timing, stretches, and extern functions that go beyond what this tutorial covers.
Was this tutorial helpful?