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.
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 queue. The first M says arrivals are Markovian, or Poisson, the second says copy times are Markovian, or exponential, and 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 , the load offered to the shop. The queue only settles into a steady state when is below 1.
04 Steady state
In the steady state, , the chance of finding the shop empty, is what makes all the probabilities add up to 1. From it follows , the chance of customers at the shop, inside and out.
With one copier, shrinks by a factor of with every extra customer, so the shop holds 4 or more 41.0% of the time.
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 follows from it.
Little's law, , turns lengths into times. is the average wait for a copier, adds the copy itself, and counts everyone at the shop.
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 or more people are already there. Past customers, each extra customer multiplies the chance by , so the chance of a full shop is the Erlang C chance times .
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 , the spare capacity of the copiers.
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 007 One copier
With one copier, , 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.
08 Two copiers
A second copier halves to 0.4. With two copiers has to be worked out first, with .
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.
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_total10 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 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