Ardian About Directory
Claude Code
Email me

Self-Service Copy Shop Queue

2025 — A simulation of whether a second copier keeps customers from waiting outside

01 Problem

A small copy shop has one self-service copier and room for four people inside, counting whoever is at the machine. Anyone who arrives when four are already in waits outside until someone leaves.

The owner wants fewer customers standing outside and is thinking about adding a second copier. The goal is to measure how much that helps, first with queueing theory and then with a simulation.

02 Customers

Customers arrive at random, 24 an hour on average, or one every 2.5 minutes. The gap to the next customer is exponential and does not depend on the last one, which makes arrivals a Poisson process.

A copy job takes 2 minutes on average, so one copier serves up to 30 customers an hour. Job lengths are exponential too, so most are quick, but a few run long and hold up everyone behind them. Customers are served in the order they arrive and never give up and leave.

λ=24 customers per hour
μ=12/60=30 customers per hour
lambda <- 24
mu <- 30
K <- 4
time_horizon <- 64
n_replications <- 64
scenarios <- c("Satu Mesin" = 1, "Dua Mesin" = 2)

03 Model

In Kendall notation the shop is an M/M/c queue. The first M says arrivals are Markovian, or Poisson, the second says copy times are Markovian, or exponential, and c is the number of copiers.

The model lets the line grow without limit, so the room for 4 inside is not a cap but the threshold past which customers stand outside. Everything depends on ρ, the share of time each copier is busy, and a, the load offered to the shop. The queue only settles into a steady state when ρ is below 1.

ρ=λcμ
a=λμ

04 Steady state

In the steady state, P0, the chance of finding the shop empty, is what makes all the probabilities add up to 1. From it follows Pn, the chance of n customers at the shop, inside and out.

With one copier, Pn shrinks by a factor of ρ=0.8 with every extra customer, so the shop holds 4 or more 41.0% of the time.

P0=[∑n=0c−1ann!+acc!(1−ρ)]−1
Pn={ann!P0,0≤n<cacρn−cc!P0,n≥c

05 Queues and waits

The chance that every copier is busy when a customer arrives, so they have to queue at all, is the Erlang C formula. The average line Lq follows from it.

Little's law, L=λW, turns lengths into times. Wq is the average wait for a copier, W adds the copy itself, and L counts everyone at the shop.

P(n≥c)=acc!(1−ρ)P0
Lq=P0acρc!(1−ρ)2=P(n≥c)ρ1−ρ
Wq=Lqλ
W=Wq+1μ
L=λW=Lq+a
lambda_rc <- 24
mu_rc <- 30
K_rc <- 4

compute_mmc <- function(lambda, mu, c, K) {
rho <- lambda / (c * mu)
a <- lambda / mu

sum_part <- sum(sapply(0:(c - 1), function(n) a^n / factorial(n)))
p0_inv <- sum_part + (a^c / (factorial(c) * (1 - rho)))
P0 <- 1 / p0_inv

erlang_c <- (a^c / (factorial(c) * (1 - rho))) * P0

Lq <- erlang_c * rho / (1 - rho)
Wq <- Lq / lambda

p_wait_outside <- erlang_c * (rho^(K - c))

list(rho = rho, Lq = Lq, Wq = Wq, P_wait_outside = p_wait_outside, P_wait_queue = erlang_c, P0 = P0)
}

rough_c1 <- compute_mmc(lambda_rc, mu_rc, 1, K_rc)
rough_c2 <- compute_mmc(lambda_rc, mu_rc, 2, K_rc)

06 Waiting outside

A customer waits outside when they arrive and K=4 or more people are already there. Past c customers, each extra customer multiplies the chance by ρ, so the chance of a full shop is the Erlang C chance times ρK−c.

The report's code goes one step further. The average number outside, divided by the rate of customers who end up there, gives the average time they spend outside, and it simplifies to one over cμ−λ, the spare capacity of the copiers.

P(n≥K)=P(n≥c)ρK−c
Lout=∑n>K(n−K)Pn=PKρ(1−ρ)2
Wout=LoutλP(n≥K)=1cμ−λ
Pk_val <- (a^c / factorial(c)) * rho^(K-c) * P0
Lq_outside <- Pk_val * rho / (1 - rho)^2

lambda_eff_q_outside <- lambda * p_wait_outside
Wq_outside <- if(lambda_eff_q_outside > 0) Lq_outside / lambda_eff_q_outside else 0

07 One copier

With one copier, ρ=0.8, so the copier is busy 80.0% of the time, and for a single copier the Erlang C chance of having to queue is ρ itself.

On average 3.20 people are in line, each waiting 8.0 minutes for the copier. 41.0% of customers find the shop full, and they wait 10.0 minutes outside.

ρ=241×30=0.8
P(n≥1)=ρ=0.8
Lq=0.8×0.81−0.8=3.20
Wq=3.2024≈0.133 h=8.0 min
P(n≥4)=0.8×0.83≈0.410
Wout=130−24 h=10.0 min

08 Two copiers

A second copier halves ρ to 0.4. With two copiers P0 has to be worked out first, with a=0.8.

Only 22.9% of customers have to queue at all, the average line is 0.152 people, and the wait for a copier drops to 22.9 seconds. The chance of a full shop falls to 3.7%, and those few wait 1.7 minutes outside.

ρ=242×30=0.4
P0=[(1+0.8)+0.822!(1−0.4)]−1≈0.4286
P(n≥2)=0.822!×0.6×0.4286≈0.2286
Lq=0.2286×0.41−0.4≈0.152
Wq=0.15224≈0.0063 h=22.9 s
P(n≥4)=0.2286×0.42≈0.037
Wout=12×30−24 h=1.7 min

09 Simulation

The report checks the theory with a discrete-event simulation that jumps from one event to the next. On an arrival, the customer comes in if fewer than four are inside and takes a free copier or the line inside, and otherwise joins the line outside. On a departure, the copier frees up, the first person outside steps in, and the next person inside starts copying.

Each setup runs for 64 hours, about 1,536 customers, and is repeated 64 times from an empty shop with seed 123. With one copier, the running estimate swings by several points over the first runs and settles at 40.5%, against the steady-state 41.0%.

if (next_event$type == "arrival") {
arrivals_total <- arrivals_total + 1

if (n_in_system < K) {
n_in_system <- n_in_system + 1
idle_servers <- which(server_status == 0)
if (length(idle_servers) > 0) {
server_id <- idle_servers[1]
server_status[server_id] <- 1
service_time <- rexp(1, mu)
departure_time <- clock + service_time
event_list <- rbind(event_list, data.frame(
time = departure_time, type = "departure", server_id = server_id, customer_id = next_event$customer_id
))
} else {
n_in_queue_internal <- n_in_queue_internal + 1
queue_internal_arrival_times <- c(queue_internal_arrival_times, next_event$customer_id)
}
} else {
n_queue_outside <- n_queue_outside + 1
queue_outside_arrival_times <- c(queue_outside_arrival_times, next_event$customer_id)
had_to_wait_outside <- had_to_wait_outside + 1
}
} else if (next_event$type == "departure") {
server_id <- next_event$server_id
server_status[server_id] <- 0

if (n_queue_outside > 0) {
admitted_cust_id <- queue_outside_arrival_times[1]
queue_outside_arrival_times <- queue_outside_arrival_times[-1]
n_queue_outside <- n_queue_outside - 1
total_wait_time_outside <- total_wait_time_outside + (clock - customer_data[[admitted_cust_id]]$arrival_time)
n_in_queue_internal <- n_in_queue_internal + 1
queue_internal_arrival_times <- c(queue_internal_arrival_times, admitted_cust_id)
} else {
n_in_system <- n_in_system - 1
}

if (n_in_queue_internal > 0) {
server_status[server_id] <- 1
next_cust_id_internal <- queue_internal_arrival_times[1]
queue_internal_arrival_times <- queue_internal_arrival_times[-1]
n_in_queue_internal <- n_in_queue_internal - 1
service_time <- rexp(1, mu)
departure_time <- clock + service_time
event_list <- rbind(event_list, data.frame(
time = departure_time, type = "departure", server_id = server_id, customer_id = next_cust_id_internal
))
}
}

P_wait_outside <- had_to_wait_outside / arrivals_total

10 Answer

With one copier, 40.5% of simulated customers find the shop full and wait outside. With two, only 3.6% do, about one in 28, so a second copier cuts the chance of waiting outside by a factor of 11.

The steady-state 41.0% and 3.7% both fall inside the simulation's 95% confidence intervals, 38.8% to 42.3% and 3.3% to 3.9%, so theory and simulation agree.

11 Time outside

Customers who do wait outside stand there 9.6 minutes on average with one copier and 1.6 minutes with two. At any moment, 1.64 people are outside on average with one copier, against 0.02 with two.

The report leaves this wait to the simulation, saying its formula is too complex, yet its own theory code already gives 10.0 minutes and 1.7 minutes, close to what the simulation measures.

12 Original report

The analysis was first written in R as an R Markdown report in Indonesian, dated November 21, 2025. It walks from the problem through the M/M/c formulas and a rough-cut calculation to the simulation, then a table of every metric for both setups with 95% confidence intervals.

It was published to RPubs and ends with a plain recommendation to add the second copier.

13 Limits

Customers arrive at a steady 24 an hour for all 64 hours, with no rush before a deadline. One copier at 80.0% busy has little slack, and at 28 an hour the steady-state share waiting outside climbs to 75.9% with one copier, against 6.5% with two. That close to 30 an hour, one copier needs far longer than 64 hours to settle, so the simulation falls short of the steady state there.

The simulated wait outside runs a little short, 9.6 minutes against 10.0 minutes with one copier. The report averages each run's own mean wait, so busy runs, where people wait longest, count no more than quiet ones, and every run starts from an empty shop. Longer runs close the gap.

The rates are assumptions, not counts from the shop, and exponential copy times let a few jobs run very long. If real jobs are more regular, both setups queue less. Customers also never give up and leave, however long the line outside.

Self-Service Copy Shop Queue

Waiting outside drops from 40.5% to 3.6% with a second copier

Self-Service Copy Shop Queue

System characteristics and assumptions

Arrivals
Poisson, at the rate set below
Copy jobs
Exponential in length, taken in arrival order
Waits outside when
The shop is already full on arrival
Customers
Never give up and leave
Each run
Starts from an empty shop

Parameters

Copy time
2 minutes on average
Run length
64 hours
Copiers
1 and 2, side by side

One copier serves at most 30 an hour, so the slider stops just short.

Result

Breakdown