Simulating a Waiting Line: How It Is Built and How It Is Checked
How the lab plays out every customer at random. Plus the checks that show the moving picture is a real working model, not just decoration.
Why simulate a line when there’s a formula?
The Queueing & Staffing Lab answers staffing questions with a formula, Erlang C. A formula is fast, but it rests on assumptions. And an average hides what any one day looks like. A simulation plays out every customer instead: when each one arrived, how long they waited and which counter served them.
So a simulation is a way to check the formula. In a more detailed model, it can also handle cases the formula can’t, like breaks, priorities or customers who give up and leave. But it’s only useful if the simulation itself is right. A busy animation of dots moving through a line can look convincing and still get the numbers wrong. This note covers how I built the lab’s simulation and how I check it.
What the lab simulates
The lab simulates one shared line feeding c identical servers, like several counters with a single line in front of them. It’s first come, first served. There’s unlimited room to wait, and nobody gives up and leaves. The gaps between arrivals are drawn at random from an exponential distribution, with an average rate λ. That means lots of short gaps and now and then a long one. It also makes arrivals a Poisson process: purely random, with no pattern. Each customer’s service time is drawn the same way, with rate μ. Those choices match the M/M/c assumptions behind the formula-based queueing lab.
Why the randomness has to be done right
Some shortcuts look right but aren’t. One adds a fraction of a customer at every tick of the clock. It lets someone in whenever the fractions add up to a whole person. The line looks busy, but the arrivals aren’t truly random, so it isn’t a Poisson process. Another moves the clock forward in fixed steps. That rounds off service times, and events inside the same step happen in an arbitrary order. So the simulation is event-driven instead. The clock jumps straight from one arrival or service finish to the next. A binary heap, a data structure that always keeps the soonest event on top, tracks what comes next. Gaps between arrivals and service times both use the formula −ln(1 − U)/rate, where U is a random fraction between zero and one. Engineers call this the inverse transform. Arrivals and service times come from two separate random streams, each started from a seed. So with the same seed, the same customers arrive with the same service needs, however many servers you pick.
One record drives the picture and the numbers
Each customer is either waiting in line or being served, and the model keeps track of which. The animation you see and the reported line lengths and waits both come from that same record. That’s better than animating decorative dots that have nothing to do with the model being measured. The animated figures just move toward wherever the model says they should be. The walk from the door to the counter is only animation. It isn’t modelled travel time. The model’s clock doesn’t depend on the animation either. It keeps running when you scroll away or when your browser can’t draw the graphics (WebGL), so the charts and tables stay live.
One run isn’t an answer
Changing how often customers arrive, how fast they’re served or how many servers there are changes how crowded the line gets. But one random run can’t give you a reliable answer on its own. Take ρ = 0.8, where ρ is the share of time the servers are busy. Separate 2000-hour runs of the same line still scatter by about ±10% around the true average wait. So the live run shows how the line works, not the final answer. It does show a rough range for the average wait, using a method called batch means. It splits the run into chunks of customers and compares the average wait in each chunk. That range only appears after the run has gone on for at least 50 relaxation times. A relaxation time is roughly how long the line takes to settle down after a bump. Even then, in testing, the range caught the true average in only 83–89% of runs. That’s why it’s labelled rough. The replication panel shows the minimum it takes to turn a simulation into a real estimate. You need separate seeds, a warm-up period that’s thrown away, and a confidence interval (a margin of error). Studies used for real decisions need more than that minimum.
Checking against the formula
Set the simulation up as a stable M/M/c line, where the servers can keep up and the arrival rate doesn’t change over time. Then run it long enough, over several repeated runs. Its long-run averages should land close to what Erlang C predicts, give or take normal random scatter. Little’s Law gives another check: the average number in line should equal the arrival rate times the average wait. That’s why the formula lab and the simulation lab are useful together.
How I check it
With zero arrivals, nothing should get served once the line empties. Adding service capacity should make the line less crowded on average, though not in every short random run. The same seed should replay the exact same run. And the line’s rules (like first come, first served), the number of servers, event timing and units should all be testable without the animation.
What it isn’t
This browser demo isn’t a professional simulation package. Its repeated-runs analysis is a fixed, bare-minimum setup. It uses 20 separate seeds, throws away the first 100 hours as warm-up and measures 500 hours. Then it builds t-intervals, a standard margin of error for a small number of runs. It isn’t a full tool for analyzing simulation output. It also can’t use time patterns built from your own measurements, work calendars, priorities or who has which skills. I’m saying so up front instead of hiding those gaps behind nice visuals.