
Your First Quantum Application
One particularly simple way to write your first quantum program is to generate a random number, which, as we will see, has a lot to do with the concept of superposition. We are not going to solve a complex optimization problem or implement one of the quantum algorithms that commonly appear in textbooks. We do not need any of that to get started. All we need is a qubit, a quantum gate, and a measurement.
My goal with this article is to demystify the complexity of quantum computing and show you, with a real example implemented in Python using IBM’s Qiskit library, how to run something genuinely useful.
If you are a programmer, you are probably familiar with the typical Hello, World! when trying out a new language or framework. This example is intended to serve roughly the same purpose for Qiskit: write a few lines, run them, and, above all, understand what is happening behind the scenes.
The interesting thing is that our little program already contains some of the fundamental ingredients of quantum computing: states, superposition, gates, measurement, circuits, and execution. You will see how they all come together to build something useful. Let’s take it one step at a time.
Qiskit and one qubit are all you need
Qiskit is an SDK maintained by IBM that lets us build and run quantum circuits from Python. Instead of working directly with the physical particulars of a quantum processor, we can describe a circuit using qubits and gates in a way that is quite similar to how we compose other abstractions in software.
Our random number generator will use a single qubit. When we create the circuit, that qubit starts in the computational state
(Here we go with the mathematical descriptions—but in quantum computing, it is useful to know the linear algebra involved and Dirac notation.)
Next, we apply a Hadamard gate, usually represented by . This operation transforms the previous state into
Wait, what is the Hadamard gate?
The Hadamard gate (H) is one of the most commonly used operations in quantum computing. Applied to a qubit in the state |0⟩, it places it in an equal superposition of |0⟩ and |1⟩. This means that when we measure it, we will get 0 or 1 with equal probability: 50% for each result. In our little program, this is the operation that lets us obtain a random bit. A simple analogy would be: it is not like tossing a coin and hiding the result, but rather preparing a quantum state that produces one of two equally likely outcomes when measured. This gate puts a qubit into a superposition state.
The expression above means that the probabilities associated with the two possible outcomes of a measurement in the computational basis are equal:
It is worth pausing here, because this is precisely the quantum part of the program. Before measurement, we should not interpret the expression above as meaning that the qubit has a 0 or a 1 stored in it that we simply do not know. The state is a superposition of both basis states, and until we measure it, we have no idea what state it really has (literally, the mysteries of quantum mechanics). When we measure, we obtain one of the possible outcomes according to the probabilities determined by the state’s amplitudes.
And with that, we have the mechanism we need to produce a random bit.
The first circuit
After installing Qiskit in a Python environment, we can define our circuit like this:
from qiskit import QuantumCircuit
circuit = QuantumCircuit(1, 1)
circuit.h(0)
circuit.measure(0, 0)
print(circuit.draw())
There is not much code, but quite a few concepts are hidden in those lines, and those are what I want to explain in this article.
QuantumCircuit(1, 1) creates a circuit with one qubit and one classical bit. We need the latter because the measurement result must be stored as classical information that our program can read later.
The call circuit.h(0) applies the Hadamard gate to the qubit with index zero (I would have implemented this function name as circuit.hadamard(0) to make things easier for newcomers).
Finally, circuit.measure(0, 0) measures that qubit and stores the result in the classical bit.
Remember: all quantum computing circuits start with an input of qubits, which, after being processed and measured, produce classical bits.
That is the entire circuit. Creating a quantum application does not necessarily mean writing hundreds of lines of code. The difficulty comes when we want to design circuits that transform amplitudes and interference in useful ways to solve specific problems.
Before real hardware: a simulator
We do not need a quantum computer to learn Qiskit. We can run circuits using classical simulators.
For this first example, we can use StatevectorSampler, which is included in Qiskit (which, incidentally, includes other types of simulators too):
from qiskit import QuantumCircuit
from qiskit.primitives import StatevectorSampler
circuit = QuantumCircuit(1, 1)
circuit.h(0)
circuit.measure(0, 0)
sampler = StatevectorSampler()
job = sampler.run([circuit], shots=100)
result = job.result()
counts = result[0].data.c.get_counts()
print(counts)
The shots=100 parameter means that we want to repeat the execution one hundred times. If everything goes as expected, we will get approximately as many zeros as ones. We should not necessarily expect 50 of each: this is a probabilistic process, and a finite sample might produce, for example, 47 zeros and 53 ones.
Allowing for the differences, if you toss a coin five times or just a few times, you will probably get an imbalanced result. But if you toss it many times, the result will gradually tend toward 50% heads and 50% tails.
There is an important nuance here. A quantum simulator is still software running on a classical computer—on your own laptop or PC, in other words. It mathematically simulates the circuit’s evolution, which is extraordinarily useful for learning, developing, and checking our programs, but it does not turn our computer into a quantum machine.
This distinction becomes especially important as circuits grow and, in real applications, both the complexity of the circuit being run and the preparation of the data being evaluated increase enormously. Simulating a general quantum system requires representing a state space whose size grows exponentially with the number of qubits (yikes!). A small circuit is trivial for our laptop; a sufficiently large one is not.
From bits to numbers
Getting a 0 or a 1 is fine for understanding the mechanism, but we can take it a little further. If we use several qubits in superposition and measure them, we will get bit strings that we can then interpret as integers.
For example, with eight qubits, the results range from 00000000 to 11111111, that is, from 0 to 255:
from qiskit import QuantumCircuit
from qiskit.primitives import StatevectorSampler
NUM_QUBITS = 8
circuit = QuantumCircuit(NUM_QUBITS, NUM_QUBITS)
circuit.h(range(NUM_QUBITS))
circuit.measure(range(NUM_QUBITS), range(NUM_QUBITS))
sampler = StatevectorSampler()
job = sampler.run([circuit], shots=1)
result = job.result()
counts = result[0].data.c.get_counts()
bit_string = next(iter(counts))
random_number = int(bit_string, 2)
print(bit_string)
print(random_number)
Again, the classical code that converts the binary string into an integer is not quantum at all. The quantum part ends at measurement. This separation is interesting because it clearly shows the hybrid nature of much of today’s quantum software: we prepare the experiment and process its results using classical software, while delegating specific operations to the quantum circuit.
In practice, this is the usual workflow: implement the solution, validate it with simulators, and finally run it on a real quantum computer.
Running it on an IBM quantum computer
So far, we have not needed real quantum hardware. Qiskit also lets us send circuits to IBM Quantum systems using qiskit-ibm-runtime.
You will need an IBM Quantum account and to configure the corresponding credentials. Do not put the token directly in code that you are going to publish or upload to GitHub. IBM’s documentation should be your reference for checking the current authentication procedure.
A basic workflow for selecting a physical backend might look like this:
from qiskit import QuantumCircuit
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import SamplerV2 as Sampler
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)
circuit = QuantumCircuit(1, 1)
circuit.h(0)
circuit.measure(0, 0)
pass_manager = generate_preset_pass_manager(
target=backend.target,
optimization_level=1,
)
isa_circuit = pass_manager.run(circuit)
sampler = Sampler(mode=backend)
job = sampler.run([isa_circuit], shots=100)
result = job.result()[0]
print(result.data.c.get_counts())
As we can see, this code snippet does not seem much more complicated if you read it carefully.
Wait, how is the circuit we implemented translated into the actual instructions run by a quantum computer? This is where the transpiler comes in.
The circuit we write is a logical description. A specific device has constraints on the operations it can perform and on how its qubits are connected. Before sending it, Qiskit transforms our circuit to adapt it to the instruction set and topology of the selected backend.
On a real processor, the results do not necessarily match the ideal distribution exactly either. In addition to the statistical variation inherent in measurement, current quantum hardware is subject to noise and errors. That is precisely why it is a good idea to start by developing and checking small circuits in simulators, and to use real hardware when we want to experiment with its physical behavior.
Do we have a quantum application now?
Yes, even if it is tiny and just an example.
We started with a qubit in , applied a unitary transformation with a Hadamard gate, created a superposition, and performed a measurement. Then we converted the quantum result into classical information that our program can use. Finally, we saw that the same circuit can be tested locally and prepared to run on a real physical backend.
It is all very basic, but I assure you that this is the real pattern behind actual applications and use cases in basic quantum computing.
Finally, we have not demonstrated any quantum advantage, and this little program is not intended to compete with the random number generators used in real systems. A quantum random number generator intended for serious use, especially cryptographic use, requires additional considerations about the device, noise characterization, bias, randomness extraction, and validation. Those are all well beyond the scope of this educational example (or at least that is what I have read :-)).
And that is precisely where I see the value of this Hello, World!: the circuit is so simple that it does not distract from the fundamental concepts. In just a few lines, we have already encountered many of the building blocks you will come across again when programming more interesting circuits.
The next time you see a circuit with lots of qubits and gates, remember this first one: prepare a state, transform it, measure it, and use the result. Of course, things get more complex from here, but the basic vocabulary is already laid out in those brief steps.