Qubits, Superposition and Entanglement: Quantum Foundations without Quantum Physics
Every introduction to quantum computing seems to start with quantum mechanics, the equations Schrodinger and the wave-particle duality. The result is that most developers you get lost in the physical details before even understanding why a quantum computer is different from one classic. This article does the opposite: it explains the fundamental concepts — qubits, superposition, entanglement and decoherence — using accessible mathematical analogies and Qiskit code you can run now.
You won't need to know quantum mechanics. You will need vectors and probabilities.
What You Will Learn
- What is a qubit mathematically: vector in 2D Hilbert space
- The Bloch sphere: visualizing the state of a qubit
- Superposition: probability, not magic
- Entanglement: quantum correlation without communication
- Decoherence: why quantum computing is difficult
- Measure: the collapse of the state and its consequences
- All Qiskit code to visualize each concept
The Classic Bit vs the Qubit
A classical bit has exactly one of two values: 0 or 1. Physically, it is a transistor which conducts or does not conduct current. There is no ambiguity: the bit is always in a defined state.
A qubit is mathematically described by a vector in a 2-hilbert space dimensions. The general state of a qubit is:
|ψ⟩ = α|0⟩ + β|1⟩
dove:
|0⟩ e il vettore [1, 0] (stato base "zero")
|1⟩ e il vettore [0, 1] (stato base "uno")
α e β sono numeri complessi (ampiezze)
con il vincolo: |α|² + |β|² = 1
La probabilita di misurare 0 e |α|²
La probabilita di misurare 1 e |β|²
This is the critical point: α and β are not probabilities — they are amplitudes. The probabilities are obtained by squaring the magnitude of the amplitudes. This distinction and fundamental because the amplitudes can interfere (add or cancel each other) like waves, while classical probabilities do not.
# Visualizzare lo stato di un qubit con Qiskit
from qiskit import QuantumCircuit
from qiskit.visualization import plot_bloch_multivector, plot_histogram
from qiskit.quantum_info import Statevector
import numpy as np
# Stato |0⟩ — qubit "a zero"
qc_zero = QuantumCircuit(1)
sv_zero = Statevector.from_instruction(qc_zero)
print(f"Stato |0⟩: {sv_zero}")
# Statevector([1.+0.j, 0.+0.j], dims=(2,))
# probabilita di misurare 0: |1|^2 = 1.0 (100%)
# probabilita di misurare 1: |0|^2 = 0.0 (0%)
# Stato |1⟩ — applico gate X (equivalente al NOT classico)
qc_one = QuantumCircuit(1)
qc_one.x(0)
sv_one = Statevector.from_instruction(qc_one)
print(f"Stato |1⟩: {sv_one}")
# Statevector([0.+0.j, 1.+0.j], dims=(2,))
# Stato in superposizione — applico gate Hadamard
qc_plus = QuantumCircuit(1)
qc_plus.h(0) # Hadamard: |0⟩ -> (|0⟩ + |1⟩)/√2
sv_plus = Statevector.from_instruction(qc_plus)
print(f"Stato |+⟩ (superposizione): {sv_plus}")
# Statevector([0.707+0.j, 0.707+0.j], dims=(2,))
# probabilita 0: 0.707^2 = 0.5 (50%)
# probabilita 1: 0.707^2 = 0.5 (50%)
# Verifica le probabilita
probs = sv_plus.probabilities_dict()
print(f"Probabilita di misura: {probs}")
# {'0': 0.4999..., '1': 0.4999...}
The Bloch Sphere: Visualizing a Qubit
The Bloch sphere is a sphere of radius 1 where each point on the surface represents a state pure quantum (one qubit). The north pole is |0⟩, the south pole is |1⟩. The points on the equator they are equiprobable superpositions of |0⟩ and |1⟩.
# Visualizzare vari stati sulla Bloch sphere
from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector
from qiskit.visualization import plot_bloch_multivector
def show_state_on_bloch(circuit: QuantumCircuit, label: str) -> None:
sv = Statevector.from_instruction(circuit)
probs = sv.probabilities_dict()
print(f"\nStato: {label}")
print(f"Statevector: {sv.data}")
print(f"P(0)={probs.get('0', 0):.3f}, P(1)={probs.get('1', 0):.3f}")
# Polo Nord: |0⟩
qc_north = QuantumCircuit(1)
show_state_on_bloch(qc_north, "|0⟩ (polo nord)")
# Polo Sud: |1⟩
qc_south = QuantumCircuit(1)
qc_south.x(0)
show_state_on_bloch(qc_south, "|1⟩ (polo sud)")
# Equatore: |+⟩ = (|0⟩ + |1⟩)/√2 — Hadamard da |0⟩
qc_plus = QuantumCircuit(1)
qc_plus.h(0)
show_state_on_bloch(qc_plus, "|+⟩ (equatore, fase reale +)")
# Equatore: |−⟩ = (|0⟩ - |1⟩)/√2 — X poi Hadamard
qc_minus = QuantumCircuit(1)
qc_minus.x(0)
qc_minus.h(0)
show_state_on_bloch(qc_minus, "|−⟩ (equatore, fase reale -)")
# Stato arbitrario: rotazione di pi/4 attorno all'asse Y
qc_ry = QuantumCircuit(1)
qc_ry.ry(np.pi/4, 0) # Rotazione Y di 45 gradi
show_state_on_bloch(qc_ry, "RY(π/4)|0⟩ (stato intermedio)")
# P(0) ≈ 0.854, P(1) ≈ 0.146
The conceptual key: the Hadamard gate doesn't "put the qubit in superposition" as it were an ambiguous physical position. Rotates the state vector from the north pole to the equator of the Bloch sphere. The "superposition" is simply the qubit pointing in one direction different from |0⟩ and |1⟩.
Interference: the True Power of Quantum
Superposition alone is not useful — a qubit in superposition you measure alone a random bit. The true power of quantum computing comes frominterference: the amplitudes can add (constructive interference) or cancel each other out (destructive interference), allowing you to amplify paths that lead to the right answer and suppress the wrong ones.
# Interferenza: l'Hadamard applicato due volte
from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector
# H applicato due volte torna allo stato iniziale (interferenza perfetta)
qc_hh = QuantumCircuit(1)
qc_hh.h(0) # Prima H: |0⟩ -> |+⟩ = (|0⟩+|1⟩)/√2
qc_hh.h(0) # Seconda H: le ampiezze interferiscono
sv = Statevector.from_instruction(qc_hh)
print(f"H|H|0⟩ = {sv.data}")
# [1.+0.j, 0.+0.j] — torna esattamente a |0⟩!
# Perche? La matematica:
# Prima H: [1/√2, 1/√2]
# Seconda H: il gate applica la matrice [[1/√2, 1/√2], [1/√2, -1/√2]]
# Risultato: [1/√2*1/√2 + 1/√2*1/√2, 1/√2*1/√2 - 1/√2*1/√2]
# = [1, 0] = |0⟩
# Il termine per |1⟩ si annulla (interferenza distruttiva)!
# Gli algoritmi quantum sfruttano questa interferenza:
# - Amplificano stati con la risposta giusta (interferenza costruttiva)
# - Sopprimono stati con risposte sbagliate (interferenza distruttiva)
Entanglement: Quantum Correlation
Entanglement is the second fundamental concept. Two entangled qubits have correlated states: the measurement of one instantly determines the result of the measurement of the other, independently from distance. But — and this is the point that many articles get wrong — this is not allows faster-than-light communication.
The Bell state is the simplest example of 2-qubit entanglement:
# Creare e misurare il Bell State
from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector
from qiskit_aer import AerSimulator
from qiskit import transpile
# Crea il Bell State |Φ+⟩ = (|00⟩ + |11⟩) / √2
qc_bell = QuantumCircuit(2, 2)
qc_bell.h(0) # Superposizione sul qubit 0
qc_bell.cx(0, 1) # CNOT: se qubit 0 e |1⟩, nega qubit 1
# Lo stato e: (|00⟩ + |11⟩) / √2
# NON e separabile: impossibile scrivere come |a⟩ ⊗ |b⟩
sv = Statevector.from_instruction(qc_bell)
print(f"Bell State: {sv.data}")
# [0.707, 0, 0, 0.707] -> 50% |00⟩, 50% |11⟩
# Misura
qc_bell.measure([0, 1], [0, 1])
simulator = AerSimulator()
compiled = transpile(qc_bell, simulator)
result = simulator.run(compiled, shots=10000).result()
counts = result.get_counts()
print(f"Risultati (10000 shots): {counts}")
# Solo '00' e '11' — MAI '01' o '10'!
# Se qubit 0 e 0, qubit 1 e sempre 0. Se qubit 0 e 1, qubit 1 e sempre 1.
# Questo e l'entanglement: non si manifesta come comunicazione
# ma come CORRELAZIONE perfetta nelle misure.
# "Misuro il qubit 0: ottengo 0. So con certezza che qubit 1 sara 0."
# La correlazione vale anche se i due qubit sono a chilometri di distanza.
# Einstein chiamava questo "spooky action at a distance" — e reale e verificato.
Because entanglement does not allow communication: when you measure your qubit, you get a random result (0 or 1 with 50% probability each). Only when you communicate this result to the other person via classic channel, she understands what she has. The canal classical — which is limited by the speed of light — is always necessary to extract information useful.
Decoherence: The Enemy of Quantum Computing
The reason why building quantum computers is so difficult is decoherence: qubits interact with the environment (thermal photons, magnetic fields, vibrations) and leak their quantum properties, collapsing into classical states. This process happens in microseconds on current hardware.
# Simulare l'effetto della decoerenza con noise model
from qiskit_aer import AerSimulator
from qiskit_aer.noise import NoiseModel, depolarizing_error
from qiskit import QuantumCircuit, transpile
# Noise model semplice: errore di depolarizzazione dopo ogni gate
noise_model = NoiseModel()
error_1qubit = depolarizing_error(0.01, 1) # 1% di errore per gate a 1 qubit
error_2qubit = depolarizing_error(0.05, 2) # 5% di errore per gate a 2 qubit
noise_model.add_all_qubit_quantum_error(error_1qubit, ['h', 'x', 'ry'])
noise_model.add_all_qubit_quantum_error(error_2qubit, ['cx'])
# Test: Bell state con e senza noise
def run_bell(shots: int, noisy: bool = False) -> dict:
qc = QuantumCircuit(2, 2)
qc.h(0)
qc.cx(0, 1)
qc.measure([0, 1], [0, 1])
if noisy:
sim = AerSimulator(noise_model=noise_model)
else:
sim = AerSimulator()
compiled = transpile(qc, sim)
result = sim.run(compiled, shots=shots).result()
return result.get_counts()
ideal = run_bell(10000, noisy=False)
noisy = run_bell(10000, noisy=True)
print("Senza noise:")
for state, count in sorted(ideal.items()):
print(f" |{state}⟩: {count/10000*100:.1f}%")
print("\nCon noise (1% per gate, 5% per CNOT):")
for state, count in sorted(noisy.items()):
print(f" |{state}⟩: {count/10000*100:.1f}%")
# Output tipico con noise:
# |00⟩: ~49%, |11⟩: ~49%, |01⟩: ~1%, |10⟩: ~1%
# Gli stati '01' e '10' compaiono a causa del noise — "bit flip errors"
Developer Analogy for Key Concepts
- Qubits: a variable that can be 0, 1, or a distribution of probability over {0,1} — but when you "read" (measure) it, it collapses to 0 or 1
- Superposition: the variable is in an "not yet determined" state — like a Promise before resolve(). The measurement and the resolve()
- Entanglement: two variables with guaranteed perfect correlation — like two objects that share an immutable reference, but don't know the value until "reading"
- Interference: Computational paths add/cancel as waves — allows you to amplify the correct answer
- Decoherence: quantum bit rot — the system loses its properties quantum by interference with the environment
Measurement and Collapse
The measurement and operation that extracts classical information from a quantum state — e irremediably destroys the state. And a fundamental constraint of quantum computing: you can't "read" the intermediate state of a computation without destroying it.
# Il collasso della misura: dimostrazione pratica
from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector
# Crea superposizione
qc = QuantumCircuit(1)
qc.h(0)
sv_before = Statevector.from_instruction(qc)
print(f"Prima della misura: {sv_before.data}")
# [0.707+0j, 0.707+0j] — superposizione
# Misura (simulata 1000 volte)
qc_measure = QuantumCircuit(1, 1)
qc_measure.h(0)
qc_measure.measure(0, 0)
from qiskit_aer import AerSimulator
from qiskit import transpile
sim = AerSimulator()
compiled = transpile(qc_measure, sim)
result = sim.run(compiled, shots=1000).result()
print(f"Dopo 1000 misure: {result.get_counts()}")
# {'0': ~500, '1': ~500}
# Punto critico: ogni singola misura collassa il qubit
# Non puoi misurare |+⟩ e poi continuare a lavorarci
# Devi preparare di nuovo lo stato dall'inizio
# Questo e perche no-cloning theorem: non puoi copiare uno stato sconosciuto
Conclusions
The fundamental concepts of quantum computing — qubits, superposition, entanglement and decoherence — they are all describable with linear algebra and probability, without esoteric physics. Bloch sphere and Qiskit state vectors are concrete tools for visualizing and working with them.
The next article builds on these fundamentals: quantum gates as transformations linear, the construction of the first real algorithm (Bell state) and execution on IBM hardware.







