Skip to content
All essays
AIMarch 27, 202418 min

Qiskit: Practical Quantum Programming with IBM's Framework

Learn quantum programming with Qiskit, from basic quantum circuits to advanced algorithms and real quantum hardware

Ü
Ümit Uz
Mobile & Full Stack Developer

Qiskit is IBM's open-source quantum computing framework that enables developers to write quantum programs and run them on real quantum computers. In this guide, we'll explore Qiskit from the ground up and learn how to build practical quantum applications.

What is Qiskit?

Qiskit is a Python SDK for quantum computing that provides:

  • Quantum Circuits: Build quantum algorithms with intuitive APIs
  • Simulators: Test your quantum programs locally
  • Real Hardware: Run on IBM's quantum computers via the cloud
  • Algorithms: Pre-built quantum algorithms
  • Visualization: Tools for understanding quantum circuits

Setting Up Your Environment

Installation

bash
# Install Qiskit
pip install qiskit

# Install with visualization tools
pip install qiskit[visualization]

# Install for Aer simulator
pip install qiskit-aer

# Install for IBM Quantum provider
pip install qiskit-ibm-runtime

Python Environment Setup

python
# Import Qiskit components
from qiskit import QuantumCircuit, transpile
from qiskit_aer import AerSimulator
from qiskit.visualization import plot_histogram, plot_bloch_multivector
from qiskit_ibm_runtime import QiskitRuntimeService
import numpy as np

# Initialize simulator
simulator = AerSimulator()

print("Qiskit environment ready!")

Basic Quantum Circuits

Creating Your First Quantum Circuit

python
from qiskit import QuantumCircuit
from qiskit_aer import AerSimulator

# Create a quantum circuit with 2 qubits and 2 classical bits
qc = QuantumCircuit(2, 2)

# Apply Hadamard gate to qubit 0 (creates superposition)
qc.h(0)

# Apply CNOT gate (qubit 0 controls qubit 1)
qc.cx(0, 1)

# Measure qubits
qc.measure([0, 1], [0, 1])

# Visualize circuit
print(qc.draw())

# Output:
#      ┌───┐     
# q_0: ┤ H ├──■──
#      └───┘┌─┴─┐
# q_1: ─────┤ X ├
#           └───┘
# c: 2/════════════

Running on Simulator

python
from qiskit_aer import AerSimulator

# Initialize simulator
simulator = AerSimulator()

# Execute circuit
job = simulator.run(qc, shots=1000)
result = job.result()
counts = result.get_counts()

print("Measurement results:", counts)
# Output: {'00': ~50%, '11': ~50%}

# Plot histogram
plot_histogram(counts)

Creating Bell States

python
def create_bell_state():
    """Create an entangled Bell pair"""
    qc = QuantumCircuit(2, 2)
    
    # Create superposition
    qc.h(0)
    
    # Entangle qubits
    qc.cx(0, 1)
    
    # Measure
    qc.measure([0, 1], [0, 1])
    
    return qc

# Create and run Bell state circuit
bell_qc = create_bell_state()
result = simulator.run(bell_qc, shots=1000).result()
counts = result.get_counts()

print("Bell state measurements:", counts)
# Should see approximately 50% '00' and 50% '11'
# No '01' or '10' due to entanglement

Quantum Gates in Qiskit

Single-Qubit Gates

python
from qiskit import QuantumCircuit
import numpy as np

# Create circuit with visualization
qc = QuantumCircuit(1)

# Pauli-X gate (quantum NOT)
qc.x(0)
qc.barrier()

# Pauli-Y gate
qc.y(0)
qc.barrier()

# Pauli-Z gate (phase flip)
qc.z(0)
qc.barrier()

# Hadamard gate (superposition)
qc.h(0)
qc.barrier()

# Rotation gates
qc.rx(np.pi/4, 0)  # Rotation around X-axis
qc.ry(np.pi/4, 0)  # Rotation around Y-axis
qc.rz(np.pi/4, 0)  # Rotation around Z-axis

# Phase gates
qc.s(0)   # S gate (π/2 phase)
qc.sdg(0) # S-dagger gate
qc.t(0)   # T gate (π/4 phase)
qc.tdg(0) # T-dagger gate

print(qc.draw())

Multi-Qubit Gates

python
# Two-qubit gates
qc = QuantumCircuit(2)

# CNOT gate (controlled-X)
qc.cx(0, 1)
qc.barrier()

# CY gate (controlled-Y)
qc.cy(0, 1)
qc.barrier()

# CZ gate (controlled-Z)
qc.cz(0, 1)
qc.barrier()

# SWAP gate
qc.swap(0, 1)

print(qc.draw())

Controlled Rotations

python
# Controlled rotation gates
qc = QuantumCircuit(2)

# Controlled rotation
qc.crx(np.pi/3, 0, 1)  # Controlled X-rotation
qc.cry(np.pi/3, 0, 1)  # Controlled Y-rotation
qc.crz(np.pi/3, 0, 1)  # Controlled Z-rotation

# Controlled-U gate
qc.cu3(np.pi, np.pi/2, np.pi/4, 0, 1)  # Controlled U3 rotation

print(qc.draw())

Building Quantum Algorithms

Quantum Teleportation

python
def quantum_teleportation():
    """Implement quantum teleportation protocol"""
    # Create circuit: 3 qubits (message, alice, bob), 3 classical bits
    qc = QuantumCircuit(3, 3)
    
    # Step 1: Create Bell pair between Alice and Bob
    qc.h(1)
    qc.cx(1, 2)
    qc.barrier()
    
    # Step 2: Alice prepares message (optional state)
    qc.ry(np.pi/3, 0)  # Prepare arbitrary state
    qc.barrier()
    
    # Step 3: Bell measurement
    qc.cx(0, 1)
    qc.h(0)
    qc.measure([0, 1], [0, 1])
    qc.barrier()
    
    # Step 4: Classical communication and correction
    qc.z(2).c_if(0, 1)  # Apply Z if measurement result is 1
    qc.x(2).c_if(1, 1)  # Apply X if measurement result is 1
    
    return qc

# Run teleportation
teleport_qc = quantum_teleportation()
result = simulator.run(teleport_qc, shots=1000).result()
counts = result.get_counts()

print("Teleportation results:", counts)
# Bob's qubit (qubit 2) should match Alice's message

Deutsch-Jozsa Algorithm

python
def deutsch_jozsa(oracle):
    """Deutsch-Jozsa algorithm"""
    n = oracle.num_qubits - 1
    
    qc = QuantumCircuit(n + 1, n)
    
    # Initialize ancilla qubit to |1⟩
    qc.x(n)
    qc.h(n)
    
    # Put input qubits in superposition
    qc.h(range(n))
    
    # Apply oracle
    qc.append(oracle, range(n + 1))
    
    # Apply Hadamard to input qubits
    qc.h(range(n))
    
    # Measure
    qc.measure(range(n), range(n))
    
    return qc

# Create constant oracle (always returns 0)
def constant_oracle(n):
    qc = QuantumCircuit(n + 1)
    return qc.to_gate(label="Constant Oracle")

# Create balanced oracle (returns 0 for half inputs, 1 for other half)
def balanced_oracle(n):
    qc = QuantumCircuit(n + 1)
    qc.cx(0, n)  # CNOT with first qubit as control
    return qc.to_gate(label="Balanced Oracle")

# Test with 2-qubit input
n = 2
dj_constant = deutsch_jozsa(constant_oracle(n))
dj_balanced = deutsch_jozsa(balanced_oracle(n))

# Run both
result_const = simulator.run(dj_constant, shots=1000).result()
result_bal = simulator.run(dj_balanced, shots=1000).result()

print("Constant function:", result_const.get_counts())
# Should see all measurements as '00' (all zeros)

print("Balanced function:", result_bal.get_counts())
# Should NOT see all measurements as '00'
python
def grover_search(num_qubits, target_state):
    """Grover's search algorithm"""
    qc = QuantumCircuit(num_qubits, num_qubits)
    
    # Initialize to uniform superposition
    qc.h(range(num_qubits))
    
    # Number of iterations
    iterations = int(np.pi/4 * np.sqrt(2**num_qubits))
    
    for _ in range(iterations):
        # Oracle: mark target state
        for qubit in range(num_qubits):
            if not (target_state >> (num_qubits - 1 - qubit)) & 1:
                qc.x(qubit)
        
        # Multi-controlled Z
        qc.h(num_qubits - 1)
        qc.mcx(list(range(num_qubits - 1)), num_qubits - 1)
        qc.h(num_qubits - 1)
        
        # Uncompute
        for qubit in range(num_qubits):
            if not (target_state >> (num_qubits - 1 - qubit)) & 1:
                qc.x(qubit)
        
        qc.barrier()
        
        # Diffusion operator
        qc.h(range(num_qubits))
        qc.x(range(num_qubits))
        qc.h(num_qubits - 1)
        qc.mcx(list(range(num_qubits - 1)), num_qubits - 1)
        qc.h(num_qubits - 1)
        qc.x(range(num_qubits))
        qc.h(range(num_qubits))
        qc.barrier()
    
    # Measure
    qc.measure(range(num_qubits), range(num_qubits))
    
    return qc

# Search for state |111⟩ in 3-qubit system
grover_qc = grover_search(3, 7)  # 7 = 111 in binary
result = simulator.run(grover_qc, shots=1000).result()
counts = result.get_counts()

print("Grover search results:", counts)
# Should see high probability for '111'
plot_histogram(counts)

Quantum Fourier Transform

python
def qft(n):
    """Quantum Fourier Transform circuit"""
    qc = QuantumCircuit(n)
    
    for qubit in range(n):
        # Apply H gate
        qc.h(qubit)
        
        # Apply controlled rotations
        for other_qubit in range(qubit + 1, n):
            angle = np.pi / 2**(other_qubit - qubit)
            qc.cp(angle, other_qubit, qubit)
        
        qc.barrier()
    
    # Swap qubits to reverse order
    for i in range(n // 2):
        qc.swap(i, n - 1 - i)
    
    return qc

# Create QFT circuit
n = 3
qft_circuit = qft(n)

# Visualize
print(qft_circuit.draw())

# Test QFT
qc = QuantumCircuit(n, n)
qc.h(range(n))  # Initialize to uniform superposition
qc.append(qft_circuit.to_gate(), range(n))
qc.measure(range(n), range(n))

result = simulator.run(qc, shots=1000).result()
counts = result.get_counts()

print("QFT results:", counts)

Running on Real Quantum Hardware

IBM Quantum Account Setup

python
from qiskit_ibm_runtime import QiskitRuntimeService

# Save your API token (first time only)
# QiskitRuntimeService.save_account(channel='ibm_quantum', token='YOUR_API_TOKEN')

# Load service
service = QiskitRuntimeService(channel='ibm_quantum')

# List available backends
print("Available backends:")
for backend in service.backends():
    print(f"  - {backend.name}")

Running on Quantum Computer

python
# Select backend
backend = service.backend('ibmq_manila')

# Create simple circuit
qc = QuantumCircuit(2, 2)
qc.h(0)
qc.cx(0, 1)
qc.measure([0, 1], [0, 1])

# Transpile for backend
from qiskit import transpile
qc_transpiled = transpile(qc, backend=backend)

# Run on real quantum computer
job = backend.run(qc_transpiled, shots=1000)

# Wait for job to complete
result = job.result()
counts = result.get_counts()

print("Real quantum computer results:", counts)

# Compare with simulator
sim_result = simulator.run(qc, shots=1000).result()
sim_counts = sim_result.get_counts()

print("Simulator results:", sim_counts)

Monitoring Jobs

python
# Submit job
job = backend.run(qc_transpiled, shots=1000)

# Check job status
print(f"Job status: {job.status()}")

# Get job ID
job_id = job.job_id()
print(f"Job ID: {job_id}")

# Retrieve job later
retrieved_job = service.job(job_id)
result = retrieved_job.result()

Visualizing Quantum States

Bloch Sphere Visualization

python
from qiskit.visualization import plot_bloch_multivector

# Create state
qc = QuantumCircuit(1)
qc.h(0)

# Get statevector
from qiskit.quantum_info import Statevector
state = Statevector(qc)

# Plot on Bloch sphere
plot_bloch_multivector(state)

Circuit Visualization

python
from qiskit.visualization import circuit_drawer

# Text drawing
print(qc.draw())

# Matplotlib figure
circuit_drawer(qc, output='mpl', style='iqp')

# LaTeX code
qc.draw(output='latex')

State Visualization

python
from qiskit.visualization import plot_state_city, plot_state_histogram

# City plot
plot_state_city(state)

# Histogram
plot_state_histogram(state)

Advanced Qiskit Features

Parametrized Circuits

python
from qiskit.circuit import Parameter

# Create parameter
theta = Parameter('θ')

# Build parametrized circuit
qc = QuantumCircuit(1)
qc.ry(theta, 0)
qc.measure_all()

# Bind parameter
bound_circuit = qc.bind_parameters({theta: np.pi/4})

# Run
result = simulator.run(bound_circuit, shots=1000).result()
counts = result.get_counts()

print("Parametrized circuit results:", counts)

Noise Models

python
from qiskit_aer.noise import NoiseModel
from qiskit_aer.noise.errors import depolarizing_error

# Create noise model
noise_model = NoiseModel()

# Add depolarizing error
error = depolarizing_error(0.01, 1)  # 1% error rate
noise_model.add_all_qubit_quantum_error(error, ['h', 'x', 'y', 'z'])

# Run with noise
result_noisy = simulator.run(qc, noise_model=noise_model, shots=1000).result()
counts_noisy = result_noisy.get_counts()

print("Noisy simulation:", counts_noisy)
print("Ideal simulation:", counts)

Optimization

python
# Transpile optimization
qc_optimized = transpile(qc, 
                         backend=backend, 
                         optimization_level=3)

print("Original gates:", qc.count_ops())
print("Optimized gates:", qc_optimized.count_ops())

Qiskit Applications

VQE Implementation

python
from qiskit.algorithms.minimum_eigensolvers import VQE
from qiskit.algorithms.optimizers import COBYLA
from qiskit.primitives import Estimator
from qiskit.circuit.library import TwoLocal

# Define ansatz
ansatz = TwoLocal(2, ['ry', 'rz'], 'cz', reps=2)

# Define optimizer
optimizer = COBYLA(maxiter=100)

# Create VQE
estimator = Estimator()
vqe = VQE(estimator, ansatz, optimizer)

# Define Hamiltonian (simplified)
from qiskit.quantum_info import SparsePauliOp
hamiltonian = SparsePauliOp.from_list([
    ("II", 1.0),
    ("IZ", 0.5),
    ("ZI", -0.5),
    ("ZZ", 0.3)
])

# Run VQE
result = vqe.compute_minimum_eigenvalue(hamiltonian)
print(f"Ground state energy: {result.eigenvalue.real:.6f}")

Best Practices

  1. 1Always test on simulator first before running on real hardware
  2. 2Use transpilation to optimize circuits for target hardware
  3. 3Account for noise in real quantum computers
  4. 4Use appropriate optimization levels for your needs
  5. 5Monitor job queue for efficient resource usage

Conclusion

Qiskit makes quantum programming accessible to developers. With its intuitive API, powerful simulators, and access to real quantum hardware, you can start building quantum applications today.

Start with simple circuits, explore pre-built algorithms, and gradually work your way up to more complex quantum programs. The quantum future is waiting, and Qiskit is your gateway to it!

Enjoyed this essay? Share it.

Related essays

Next essay
Quantum Algorithms: From Theory to Implementation