How to Model a Queue in SimPy: The Complete Guide

Queues are the bread and butter of discrete event simulation. Customers waiting. Jobs pending. Packets buffering. If you can model a queue, you can model almost anything.

The Basic Queue

A queue needs: 1. Arrivals - Things entering the system 2. Server - Resource that serves them 3. Service - Time to process each arrival

import simpy
import random

def customer(env, name, server):
    arrival = env.now
    print(f"{name} arrives at {arrival:.2f}")

    with server.request() as req:
        yield req
        wait = env.now - arrival
        print(f"{name} waited {wait:.2f}, starts service at {env.now:.2f}")

        service_time = random.expovariate(1/5)  # Mean 5
        yield env.timeout(service_time)

    print(f"{name} leaves at {env.now:.2f}")

def arrivals(env, server):
    i = 0
    while True:
        yield env.timeout(random.expovariate(1/6))  # Mean 6
        env.process(customer(env, f"Customer {i}", server))
        i += 1

env = simpy.Environment()
server = simpy.Resource(env, capacity=1)
env.process(arrivals(env, server))
env.run(until=100)

That's an M/M/1 queue—Poisson arrivals, exponential service, one server.

Collecting Statistics

A simulation without statistics is just a story. Track what matters:

class QueueStats:
    def __init__(self):
        self.wait_times = []
        self.service_times = []
        self.queue_lengths = []

    def record_wait(self, wait):
        self.wait_times.append(wait)

    def record_service(self, service):
        self.service_times.append(service)

    def summary(self):
        return {
            'avg_wait': sum(self.wait_times) / len(self.wait_times),
            'max_wait': max(self.wait_times),
            'avg_service': sum(self.service_times) / len(self.service_times),
            'customers_served': len(self.wait_times)
        }

stats = QueueStats()

def customer(env, name, server, stats):
    arrival = env.now

    with server.request() as req:
        yield req
        stats.record_wait(env.now - arrival)

        service_time = random.expovariate(1/5)
        yield env.timeout(service_time)
        stats.record_service(service_time)

# After simulation
print(stats.summary())

Multiple Servers

Add capacity:

# 3 parallel servers
servers = simpy.Resource(env, capacity=3)

Now up to 3 customers can be served simultaneously.

Priority Queues

Some customers matter more:

server = simpy.PriorityResource(env, capacity=1)

def vip_customer(env, server):
    with server.request(priority=1) as req:  # Lower = higher priority
        yield req
        yield env.timeout(5)

def regular_customer(env, server):
    with server.request(priority=10) as req:
        yield req
        yield env.timeout(5)

VIPs jump the queue.

Balking (Refusing to Join)

Customers who leave when the queue is too long:

def customer(env, server, max_queue):
    if len(server.queue) >= max_queue:
        print(f"Customer balks at {env.now}")
        return

    with server.request() as req:
        yield req
        yield env.timeout(random.expovariate(1/5))

Reneging (Giving Up)

Customers who leave after waiting too long:

def customer(env, server, patience):
    arrival = env.now

    with server.request() as req:
        result = yield req | env.timeout(patience)

        if req in result:
            yield env.timeout(random.expovariate(1/5))
        else:
            print(f"Customer reneged after {env.now - arrival:.2f}")

Queue Length Over Time

Monitor the queue:

queue_log = []

def monitor(env, server, interval=1):
    while True:
        queue_log.append({
            'time': env.now,
            'queue_length': len(server.queue),
            'in_service': server.count
        })
        yield env.timeout(interval)

env.process(monitor(env, server))

Visualise:

import matplotlib.pyplot as plt
import pandas as pd

df = pd.DataFrame(queue_log)
plt.step(df['time'], df['queue_length'], where='post')
plt.xlabel('Time')
plt.ylabel('Queue Length')
plt.title('Queue Length Over Time')
plt.show()

Multi-Stage Queues

Customer visits multiple stations:

def customer(env, check_in, security, boarding):
    # Check-in
    with check_in.request() as req:
        yield req
        yield env.timeout(random.uniform(2, 5))

    # Security
    with security.request() as req:
        yield req
        yield env.timeout(random.uniform(3, 10))

    # Boarding
    with boarding.request() as req:
        yield req
        yield env.timeout(random.uniform(1, 3))

env = simpy.Environment()
check_in = simpy.Resource(env, capacity=4)
security = simpy.Resource(env, capacity=2)
boarding = simpy.Resource(env, capacity=1)

Classic Queue Notation

Notation Meaning
M/M/1 Poisson arrivals, exponential service, 1 server
M/M/c Same, but c servers
M/G/1 Poisson arrivals, general service, 1 server
G/G/c General arrivals and service, c servers

SimPy can model any of these.

Utilisation

How busy is the server?

def calculate_utilisation(stats, simulation_time, capacity):
    total_service = sum(stats.service_times)
    return total_service / (simulation_time * capacity)

utilisation = calculate_utilisation(stats, 100, 1)
print(f"Utilisation: {utilisation:.1%}")

Or track it over time:

busy_time = 0
last_check = 0

def track_utilisation(env, server):
    global busy_time, last_check
    while True:
        yield env.timeout(1)
        busy_time += server.count  # 1 if busy, 0 if idle
        last_check = env.now

Complete Example

import simpy
import random
import pandas as pd
import matplotlib.pyplot as plt

class MMcQueue:
    def __init__(self, env, num_servers, arrival_rate, service_rate):
        self.env = env
        self.server = simpy.Resource(env, capacity=num_servers)
        self.arrival_rate = arrival_rate
        self.service_rate = service_rate
        self.wait_times = []
        self.queue_log = []

    def customer(self, name):
        arrival = self.env.now
        with self.server.request() as req:
            yield req
            self.wait_times.append(self.env.now - arrival)
            yield self.env.timeout(random.expovariate(self.service_rate))

    def arrivals(self):
        i = 0
        while True:
            yield self.env.timeout(random.expovariate(self.arrival_rate))
            self.env.process(self.customer(f"C{i}"))
            i += 1

    def monitor(self, interval=1):
        while True:
            self.queue_log.append({
                'time': self.env.now,
                'queue': len(self.server.queue),
                'busy': self.server.count
            })
            yield self.env.timeout(interval)

    def run(self, until):
        self.env.process(self.arrivals())
        self.env.process(self.monitor())
        self.env.run(until=until)

    def report(self):
        print(f"Customers served: {len(self.wait_times)}")
        print(f"Average wait: {sum(self.wait_times)/len(self.wait_times):.2f}")
        print(f"Max wait: {max(self.wait_times):.2f}")
        print(f"90th percentile: {sorted(self.wait_times)[int(0.9*len(self.wait_times))]:.2f}")

# Run simulation
random.seed(42)
env = simpy.Environment()
queue = MMcQueue(env, num_servers=2, arrival_rate=1/3, service_rate=1/5)
queue.run(until=1000)
queue.report()

Summary

Queuing in SimPy: - Use Resource for servers - Use request() context manager - Track wait times, queue lengths, utilisation - Add balking/reneging for realism - Chain resources for multi-stage systems

Master the queue. Master simulation.

Next Steps


Discover the Power of Simulation

Want to become a go-to expert in simulation with Python? The Complete Simulation Bootcamp will show you how simulation can transform your career and your projects.

Explore the Bootcamp