Hello World in Q#
Write your first quantum program in Q#, put a qubit in superposition, build a Bell state, and run it from Python using the qsharp package.
Circuit diagrams
What Is Q# and Why Does It Exist?
Q# is not a library bolted onto an existing language. It is a standalone, domain-specific programming language built by Microsoft specifically for expressing quantum algorithms. Where frameworks like Qiskit give you a Python library that constructs circuits as data structures, Q# takes a fundamentally different approach: quantum primitives such as Qubit, Result, and quantum operations are part of the language itself, enforced by the compiler.
This matters for several practical reasons. The Q# compiler can verify qubit lifetimes at compile time, preventing common bugs like using a qubit after it has been released. It can also feed your algorithm directly into Azure Quantum’s resource estimator, which tells you how many physical qubits and how much time a fault-tolerant run would require, all without touching real hardware.
Q# compiles to QIR (Quantum Intermediate Representation), an LLVM-based format that targets multiple backends: local simulators for development, Azure Quantum cloud simulators for larger circuits, and real hardware from providers like IonQ and Quantinuum.
If you know C#, TypeScript, or Rust, Q#‘s syntax will feel familiar. It uses static typing, pattern matching, and explicit resource management.
The Q# Type System: Qubits Are Handles, Not Values
Before writing code, you need to understand one key concept that separates Q# from classical programming. A Qubit in Q# is not a value you can inspect or copy. It is a handle to a physical (or simulated) quantum resource. You cannot print a qubit, assign its state to a variable, or duplicate it. This is not a limitation of the language; it reflects the physics. The no-cloning theorem forbids copying arbitrary quantum states, and Q# enforces this at the type level.
The lifecycle of a qubit in Q# follows a strict pattern:
- Allocate it with
use, which gives you a handle to a qubit initialized to |0⟩ - Operate on it with gates like
H,CNOT,X,T, etc. - Measure it with
M(), which collapses its state and returns aResult(eitherZeroorOne) - Release it, either explicitly with
Resetor automatically when theuseblock ends
This is different from Python-based frameworks where you build a circuit as a list of instructions and then submit the whole thing to a backend. In Q#, you write imperative code that executes gate by gate, and the compiler handles the translation to whatever backend you target.
Setup and Installation
You need the .NET SDK and the qsharp Python package:
# Install .NET SDK (https://dotnet.microsoft.com/download)
# Then install the Python integration
pip install qsharp azure-quantum
Example 1: Putting a Qubit in Superposition
The simplest meaningful quantum program allocates one qubit, applies the Hadamard gate to create a superposition of |0⟩ and |1⟩, then measures the result. Each measurement collapses the superposition, giving either Zero or One with equal probability.
Superposition.qs
namespace HelloQuantum {
open Microsoft.Quantum.Intrinsic;
open Microsoft.Quantum.Measurement;
operation MeasureInSuperposition() : Result {
use q = Qubit(); // Allocate one qubit (starts in |0⟩)
H(q); // Apply Hadamard: |0⟩ → (|0⟩ + |1⟩)/√2
let result = M(q); // Measure and collapse the state
Reset(q); // Return qubit to |0⟩ before release
return result;
}
}
A few things to notice here. The use q = Qubit() statement allocates a qubit and guarantees it starts in |0⟩. The H(q) call applies the Hadamard gate in place, mutating the qubit’s quantum state. After measurement, Reset(q) returns the qubit to |0⟩ so it can be cleanly released. If you forget the reset, Q# will raise a runtime error, preventing you from leaking qubits in a dirty state.
The following Python script calls the Q# operation repeatedly and collects statistics.
Python host (run.py)
import qsharp
qsharp.eval(open("Superposition.qs").read())
results = qsharp.run("HelloQuantum.MeasureInSuperposition()", shots=10)
print("Results:", results)
zeros = results.count(qsharp.Result.Zero)
ones = results.count(qsharp.Result.One)
print(f"Zero: {zeros}/10, One: {ones}/10")
Expected Output
Results: [1, 0, 1, 1, 0, 1, 0, 0, 1, 0]
Zero: 5/10, One: 5/10
Each run independently collapses the superposition. The roughly 50/50 split emerges statistically over many shots. With only 10 shots you will see natural variance; run 1000+ shots for a tighter distribution.
Example 2: Building a Bell State
A Bell state is the simplest form of entanglement. It requires two qubits: the Hadamard gate puts the first qubit into superposition, then a CNOT gate entangles the two qubits so their measurement outcomes are perfectly correlated.
BellState.qs
namespace HelloQuantum {
open Microsoft.Quantum.Intrinsic;
open Microsoft.Quantum.Measurement;
operation BellState() : (Result, Result) {
use (q0, q1) = (Qubit(), Qubit());
H(q0); // Superposition on q0
CNOT(q0, q1); // Entangle: q1 flips if q0 is |1⟩
let (r0, r1) = (M(q0), M(q1));
ResetAll([q0, q1]);
return (r0, r1);
}
}
Python host
import qsharp
qsharp.eval(open("BellState.qs").read())
results = qsharp.run("HelloQuantum.BellState()", shots=8)
for r in results:
print(r)
Expected Output
(Zero, Zero)
(One, One)
(Zero, Zero)
(One, One)
(One, One)
(Zero, Zero)
(One, One)
(Zero, Zero)
The results are always correlated: (Zero, Zero) or (One, One), never (Zero, One) or (One, Zero). This is entanglement in action. Measuring one qubit instantly determines the other’s outcome, regardless of any hypothetical distance between them.
Understanding Measurement: What M(q) Actually Returns
When you call M(q), Q# performs a projective measurement in the computational basis. The return type is Result, which is a discriminated union with exactly two values: Zero and One. This is not an integer or a boolean; it is its own type, and Q# provides pattern matching to work with it:
let outcome = M(q);
if outcome == One {
// qubit collapsed to |1⟩
}
You cannot call M on a qubit and then keep using that qubit in superposition. Measurement is irreversible: it collapses the quantum state. This is why the language separates Qubit (a quantum handle you operate on) from Result (a classical value you can store, compare, and return). You can never “read” a qubit directly; you can only measure it, destroying its quantum information in the process.
Q# Patterns: use, within, and apply
Q# provides several patterns for structured qubit management that go beyond simple allocation.
The use statement allocates qubits that are automatically released at the end of the enclosing scope. You have already seen this in the examples above.
The within/apply pattern is particularly elegant. It lets you express a common quantum idiom: apply some setup operations, perform a core computation, then automatically reverse the setup. The compiler generates the adjoint (inverse) of the within block for you.
operation ApplyControlledRotation(q : Qubit, target : Qubit) : Unit {
within {
H(q); // Setup: put q in superposition
CNOT(q, target); // Setup: entangle
} apply {
T(target); // Core operation: apply T gate
}
// Compiler automatically applies CNOT†(q, target) then H†(q)
// to undo the setup, preserving only the effect of T
}
This pattern eliminates a common source of bugs in quantum programming: forgetting to undo ancilla preparation or getting the reversal order wrong. The compiler handles it correctly every time.
Running Q# Programs: VS Code vs Command Line
VS Code with the Quantum Development Kit
The recommended development experience is VS Code with the Azure Quantum Development Kit extension. It provides:
- Syntax highlighting and IntelliSense for
.qsfiles - Inline circuit visualization that renders your operations as a gate diagram
- A built-in simulator that lets you run operations with a single click
- Integration with the Azure Quantum resource estimator
Install the extension by searching for “Azure Quantum” in the VS Code extensions marketplace. Open any .qs file and you will see a “Run” code lens above each @EntryPoint() operation.
Command Line
You can also run Q# programs from the terminal using the dotnet CLI:
# Create a new Q# project
dotnet new console -lang Q# -o MyQuantumProject
cd MyQuantumProject
# Run the project on the local simulator
dotnet run
For Python integration, use the qsharp package as shown in the examples above. This is especially useful for data analysis workflows where you want to collect measurement statistics in Python and visualize them with matplotlib or similar tools.
Q# vs Qiskit: Two Different Philosophies
If you are coming from Qiskit, Q# will feel quite different, and it is worth understanding why.
Qiskit is a Python library. You build circuits by appending gates to a QuantumCircuit object, then submit the circuit to a backend. The circuit is a data structure; Python is the host language. This gives you the full Python ecosystem (NumPy, SciPy, Jupyter) at the cost of no compile-time guarantees about your quantum logic.
Q# is a compiled language with its own type checker. Qubits are first-class types, not indices into an array. The compiler can verify qubit lifetimes, check that you reset qubits before releasing them, and generate adjoint operations automatically. The tradeoff is that you are working in a separate language with its own toolchain.
| Aspect | Q# | Qiskit |
|---|---|---|
| Type | Domain-specific language | Python library |
| Type safety | Compile-time qubit checks | Runtime only |
| Qubit model | Resource handles with lifetimes | Integer indices |
| Adjoint generation | Automatic via compiler | Manual |
| Hardware targets | Azure Quantum (IonQ, Quantinuum) | IBM Quantum, plus others via plugins |
| Resource estimation | Built-in | Separate tooling |
| Ecosystem | .NET, Python interop | Native Python |
Neither is strictly better. If you are building production algorithms targeting Azure Quantum hardware, or if you want the compiler to catch quantum-specific bugs, Q# is a strong choice. If you want rapid prototyping in Jupyter notebooks with direct access to IBM hardware, Qiskit is the natural fit.
Connecting to Azure Quantum Hardware
Q# integrates with Azure Quantum for execution on real hardware. You can set up a workspace through the Azure portal and submit jobs from Python:
import azure.quantum
workspace = azure.quantum.Workspace(
subscription_id="...",
resource_group="...",
name="...",
location="eastus"
)
Note: Submitting Q# jobs to Azure Quantum requires the
azure-quantumpackage (already installed above) and the Azure Quantum Q# integration. Theazure.quantum.qiskitmodule is Qiskit-specific and is not used for Q# workflows.
Use the Azure Quantum portal to create a workspace and access quantum hardware credits. Microsoft provides free credits for new accounts, which is enough to run the examples in this tutorial on real trapped-ion and superconducting hardware.
Next Steps
Now that you can allocate qubits, apply gates, measure results, and understand the Q# type system, you are ready to build a real algorithm. The next tutorial walks through Grover’s search algorithm in Q#, showing how to use oracle functions, amplitude amplification, and the within/apply pattern to search an unstructured space with a quadratic speedup.
Continue to Grover’s Algorithm in Q#.
Was this tutorial helpful?