Discrete-Event Simulation by Building
Week 1 — From blocking to queues to multiple servers
Quisim
Week 1: From blocking to queues
This week
Goal
Understand why queues exist, what a queue does, and how several servers change a system: by building every model yourself, and by running it.
Arrive → Leave → + Perform: blocking → Bypass: loss → + Queue: M/D/1 → random service: M/M/1 → capacity k: M/M/k
Every step has the same rhythm:
- Build it: a few blocks and connections on your own canvas
- Predict: write down a number before you press Start
- Run it: compare, and explain the difference
- Your turn: 5–10 minutes with an example, a table to fill in, a question to answer
How to follow along in the free demo
Run an example
- Pen icon: show the editor pane
- Examples tab: click e.g. M/M/1
- Type new values into the NumberInput fields, even while it runs
- Animate (film icon) on, then Start clean (Ctrl+Enter)
- Pause and Step for one event at a time
- Monitor tab ▸ Events: the event list
Build your own
- New model (Ctrl+N), then the Blocks tab
- The demo kit: Arrive, Queue, Perform, Leave, 2 × RandomExponential, 2 × RandomUniform, NumberInput, Chart, Statistics
- Drag from a socket onto another socket, or onto a socket of a block preview in the Blocks tab: a pipe (green) or wire (silver) appears
- Delete: select a block or pipe, then the trash icon
Full version: open Model code under a picture and paste it into the editor instead. Times are in ms; a run lasts 10 000 ms, about 10 s of animation.
Step 1: Arrive → Leave
Build it: arrivals, and nothing else
- New model, Blocks tab ▸ Entity: click Arrive
- Drag Arrive's outlet O onto the inlet of the Leave preview
- Drag Arrive's D socket, hover the Random sub-tab, drop it on V of RandomExponential
- Drag its µ socket, hover Value, drop it on N of NumberInput
- Type 500 into the field: 1/λ=500
Model codeOpen in Quisim
NumberInput { id:num0; initValue:500; label:"1/λ" }
Wire { from:num0.measureValue; to:rnd0.parameterMean }
RandomExponential { id:rnd0; seed:2 }
Wire { from:rnd0.measureRandomVariate; to:arrive0.parameterDuration }
Arrive { id:arrive0 }
Pipe { from:arrive0.outlet; to:leave0.inlet }
Leave { id:leave0 }Predict: how many entities in one run?
Predict
Mean time between arrivals 1/λ=500, run length 10 000. How many entities reach Leave? How many are inside the system at any moment?
Run it
- About 10000/500=20, plus one at time 0; hover Leave's socket N to read it
- Every entity leaves in the instant it arrives: nobody waits, nothing is congested
- The Arrive→Leave example in the Examples tab is the same model
Discuss
The model is correct, and useless. What is missing? Real customers are served.
Your turn: count the arrivals
Your model (or the Arrive→Leave example), 5 minutes
Predict first, then run each value twice and read N at Leave.
| 1/λ | 1000 | 500 | 250 | 100 |
|---|---|---|---|---|
| predicted N | ||||
| N, run 1 | ||||
| N, run 2 |
Why is N not exactly 10000⋅λ, and why do the two runs differ?
Step 2: A server, and blocking
Build it: Arrive → Perform → Leave
- Select Leave (click its label), trash icon: Leave and its pipe are gone
- Drag Arrive's O onto the inlet of the Perform preview
- Drag Perform's O onto the inlet of the Leave preview
- Drag Perform's D socket, hover Value, drop it on N of a NumberInput preview
- Type 600: every service takes D=600


Model codeOpen in Quisim
NumberInput { id:num0; initValue:500; label:"1/λ" }
Wire { from:num0.measureValue; to:rnd0.parameterMean }
RandomExponential { id:rnd0; seed:2 }
Wire { from:rnd0.measureRandomVariate; to:arrive0.parameterDuration }
Arrive { id:arrive0 }
Pipe { from:arrive0.outlet; to:perform0.inlet }
NumberInput { id:num1; initValue:600; label:"D" }
Wire { from:num1.measureValue; to:perform0.parameterDuration }
Perform { id:perform0 }
Pipe { from:perform0.outlet; to:leave0.inlet }
Leave { id:leave0 }Predict: what happens to the next customer?
Predict
A customer every 500 on average, a service takes 600. What happens to an entity that arrives while Perform is busy? How many reach Leave in 10 000?
Run it (Animate on)
- It cannot enter Perform: it stays in Arrive, and the socket turns red
- While it waits there, the next interarrival time does not even start
- About 14 instead of 21 entities get through (the run on the next slide: 14): one cycle lasts on average E[max(X,D)]=600+500e−1.2≈750, and 10000/750≈13, plus the first one
Key insight: blocking


Model codeOpen in Quisim
NumberInput { id:num0; initValue:500; label:"1/λ" }
Wire { from:num0.measureValue; to:rnd0.parameterMean }
RandomExponential { id:rnd0; seed:2 }
Wire { from:rnd0.measureRandomVariate; to:arrive0.parameterDuration }
Arrive { id:arrive0 }
Pipe { from:arrive0.outlet; to:perform0.inlet }
NumberInput { id:num1; initValue:600; label:"D" }
Wire { from:num1.measureValue; to:perform0.parameterDuration }
Perform { id:perform0 }
Pipe { from:perform0.outlet; to:leave0.inlet }
Leave { id:leave0 }Blocking
Without a place to wait, a busy server silently changes the arrival process: the model no longer has the demand we specified. Here 14 of the 21 customers of step 1 get through.
Your turn: how much demand gets through?
Your model, 1/λ=500, 7 minutes
Change D, run, read N at Leave. Watch for red sockets.
| D | 100 | 300 | 600 | 1000 |
|---|---|---|---|---|
| predicted N | ||||
| N at Leave | ||||
| red sockets: rarely / often? |
- What is the largest N this server can ever reach in 10 000, whatever the arrivals?
- Name a real system that blocks like this. Is that acceptable there?
Doctor's office that turns patients away, a phone line with a busy signal, a machine that stops while the next station is occupied.
Step 3: The wrong fix
The Bypass example: a side door


Model codeOpen in Quisim
NumberInput { id: number0; initValue:1000; label:"1/λ"; lower:1 }
Wire { from: number0.measureValue; to: rnd0.parameterMean }
RandomExponential { id:rnd0}
Wire { from: rnd0.measureRandomVariate; to: arrive0.parameterDuration }
Arrive { id:arrive0 }
Pipe { from:arrive0.outlet; to:leave1.inlet }
Leave { id:leave1; label:"Reject" }
Pipe { from:arrive0.outlet; to:perform0.inlet }
Perform { id:perform0 }
Pipe { from:perform0.outlet; to:leave0.inlet }
Leave { id:leave0 }Arrive has two pipes. The pipe defined last in the code is tried first, so an entity takes Perform (D=1000) when it is free, and Reject otherwise.
Predict: is the problem solved?
Predict
Open Examples ▸ Bypass: 1/λ=1000, D=1000. Will you see red sockets? Which share of the customers is served?
Run it
- No red sockets: every entity always has somewhere to go
- But roughly half of them leave through Reject, unserved; with only about 10 customers per run the share jumps around, so run it a few times
- Theory (Erlang loss, one server): served share =1+λD1=21
Key insight
We traded blocking for loss. What we need is a place to wait.
Your turn: how much do we lose?
Example Bypass, 7 minutes
Set 1/λ, run, read N at both Leave blocks.
| 1/λ | 2000 | 1000 | 500 | 250 |
|---|---|---|---|---|
| served (N at Leave) | ||||
| rejected (N at Reject) | ||||
| served share | ||||
| theory 1/(1+λD) | 67% | 50% | 33% | 20% |
Where would a company accept this loss on purpose? (Think of a full car park, or a call without a waiting line.)
Step 4: A queue, M/D/1
Build it: a place to wait
Back to your model from step 2.
- Click the pipe Arrive → Perform, trash icon
- Drag Arrive's O onto the inlet of the Queue preview
- Drag Queue's outlet O onto Perform's inlet I
- Keep 1/λ=500, D=600
Model codeOpen in Quisim
NumberInput { id:num0; initValue:500; label:"1/λ" }
Wire { from:num0.measureValue; to:rnd0.parameterMean }
RandomExponential { id:rnd0; seed:2 }
Wire { from:rnd0.measureRandomVariate; to:arrive0.parameterDuration }
Arrive { id:arrive0 }
Pipe { from:arrive0.outlet; to:queue0.inlet }
Queue { id:queue0 }
Pipe { from:queue0.outlet; to:perform0.inlet }
NumberInput { id:num1; initValue:600; label:"D" }
Wire { from:num1.measureValue; to:perform0.parameterDuration }
Perform { id:perform0 }
Pipe { from:perform0.outlet; to:leave0.inlet }
Leave { id:leave0 }Exponential arrivals (M), a fixed service time (D), 1 server: this is M/D/1.
Build it: measure the queue
- Drag Queue's L (queue length) into the Report sub-tab, onto P[0] of Chart
- Drag Leave's LS (sojourn time of the last entity) onto P[0] of Statistics
- Statistics shows the mean sojourn time of the run


Model codeOpen in Quisim
NumberInput { id:num0; initValue:500; label:"1/λ" }
Wire { from:num0.measureValue; to:rnd0.parameterMean }
RandomExponential { id:rnd0; seed:2 }
Wire { from:rnd0.measureRandomVariate; to:arrive0.parameterDuration }
Arrive { id:arrive0 }
Pipe { from:arrive0.outlet; to:queue0.inlet }
Queue { id:queue0 }
Pipe { from:queue0.outlet; to:perform0.inlet }
NumberInput { id:num1; initValue:600; label:"D" }
Wire { from:num1.measureValue; to:perform0.parameterDuration }
Perform { id:perform0 }
Pipe { from:perform0.outlet; to:leave0.inlet }
Leave { id:leave0 }
Wire { from: queue0.measureLoad; to: chart0.parameters[0] }
Chart { id: chart0; height:66; width:384 }
Wire { from: leave0.measureSojourn; to: statistics0.parameters[0] }
Statistics { id: statistics0; height:48; width:128; label:"Sojourn\ntime" }Predict: does the queue settle down?
Predict
Still 1/λ=500 and D=600, but now with a queue. Sketch the queue-length chart for the 10 s run. What would it look like after an hour?
Run it
- No red sockets, nobody lost, but once the queue has formed it never empties again: later customers wait much longer than the first ones
- The run on the previous slide: mean sojourn time about 2200, almost four times D
- 20 customers are few: type 1/λ=100, D=120 for the same ratio and five times the customers
Run it: 1/λ=100, D=120


Model codeOpen in Quisim
NumberInput { id:num0; initValue:100; label:"1/λ" }
Wire { from:num0.measureValue; to:rnd0.parameterMean }
RandomExponential { id:rnd0; seed:2 }
Wire { from:rnd0.measureRandomVariate; to:arrive0.parameterDuration }
Arrive { id:arrive0 }
Pipe { from:arrive0.outlet; to:queue0.inlet }
Queue { id:queue0 }
Pipe { from:queue0.outlet; to:perform0.inlet }
NumberInput { id:num1; initValue:120; label:"D" }
Wire { from:num1.measureValue; to:perform0.parameterDuration }
Perform { id:perform0 }
Pipe { from:perform0.outlet; to:leave0.inlet }
Leave { id:leave0 }
Wire { from: queue0.measureLoad; to: chart0.parameters[0] }
Chart { id: chart0; height:66; width:384 }
Wire { from: leave0.measureSojourn; to: statistics0.parameters[0] }
Statistics { id: statistics0; height:48; width:128; label:"Sojourn\ntime" }The queue climbs and does not come back (about 30 waiting at the end of this run): on average 1 arrival every 100, 1 departure every 120. A longer run makes it worse.
Utilization: the one number that decides
Utilization
ρ=μλ=λ⋅D=1/λDthe queue is stable only if ρ<1
| 1/λ | D | ρ | queue | |
|---|---|---|---|---|
| what we just ran | 500 | 600 | 1.2 | grows without bound |
| next slide | 500 | 400 | 0.8 | comes and goes |
Key insight
A queue absorbs short-term fluctuations, not a long-term lack of capacity.
Run it: a stable M/D/1, D=400


Model codeOpen in Quisim
NumberInput { id:num0; initValue:500; label:"1/λ" }
Wire { from:num0.measureValue; to:rnd0.parameterMean }
RandomExponential { id:rnd0; seed:2 }
Wire { from:rnd0.measureRandomVariate; to:arrive0.parameterDuration }
Arrive { id:arrive0 }
Pipe { from:arrive0.outlet; to:queue0.inlet }
Queue { id:queue0 }
Pipe { from:queue0.outlet; to:perform0.inlet }
NumberInput { id:num1; initValue:400; label:"D" }
Wire { from:num1.measureValue; to:perform0.parameterDuration }
Perform { id:perform0 }
Pipe { from:perform0.outlet; to:leave0.inlet }
Leave { id:leave0 }
Wire { from: queue0.measureLoad; to: chart0.parameters[0] }
Chart { id: chart0; height:66; width:384 }
Wire { from: leave0.measureSojourn; to: statistics0.parameters[0] }
Statistics { id: statistics0; height:48; width:128; label:"Sojourn\ntime" }ρ=0.8: waiting comes in waves that clear again. Long-run mean sojourn time (theory): D+2(1−ρ)ρD=400+800=1200. This run: about 940, from only 21 customers.
Your turn: find the boundary
Example M/D/1 (1/μ=D=100), 10 minutes
Predict whether the chart keeps returning to 0. Then set 1/λ, run, record.
| 1/λ | 200 | 125 | 110 | 100 | 90 |
|---|---|---|---|---|---|
| ρ=D/(1/λ) | 0.5 | 0.8 | |||
| chart returns to 0? | |||||
| mean sojourn time | |||||
| theory (long run) | 150 | 300 | 600 | ∞ | ∞ |
- Between which two values does the system tip over?
- Write the stability condition with λ and D.
Look inside: the event list
- Monitor tab ▸ Events, Animate on; Start clean, then Pause
- Step (top right) runs exactly one event; each row shows a time and the block that owns it
This is discrete-event simulation
- The state changes only at events: an arrival, the end of a service
- Between events nothing happens, so the clock jumps to the next one
- Take the earliest event, execute it, schedule new ones, repeat
Your turn: be the simulator
Your M/D/1 model, 1/λ=500, D=400, 7 minutes
Pause right after the start. Before each Step, predict which event comes next.
| step | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|---|---|---|---|---|---|---|---|---|
| clock time | ||||||||
| block (Arrive / Perform) | ||||||||
| events in the list after it |
- How many events are in the list while the server is idle? While it is busy?
- An end of service is always exactly 400 after... what?
Step 5: Random service, M/M/1
Build it: a random service time
- Select the D NumberInput, trash icon: it and its wire are gone
- Drag Perform's D into Random, onto V of RandomExponential
- Drag its µ onto N of a new NumberInput
- Type 400: the mean service time 1/μ is still 400


Model codeOpen in Quisim
NumberInput { id:num0; initValue:500; label:"1/λ" }
Wire { from:num0.measureValue; to:rnd0.parameterMean }
RandomExponential { id:rnd0; seed:2 }
Wire { from:rnd0.measureRandomVariate; to:arrive0.parameterDuration }
Arrive { id:arrive0 }
Pipe { from:arrive0.outlet; to:queue0.inlet }
Queue { id:queue0 }
Pipe { from:queue0.outlet; to:perform0.inlet }
NumberInput { id:num1; initValue:400; label:"1/µ" }
Wire { from:num1.measureValue; to:rnd1.parameterMean }
RandomExponential { id:rnd1; seed:5 }
Wire { from:rnd1.measureRandomVariate; to:perform0.parameterDuration }
Perform { id:perform0 }
Pipe { from:perform0.outlet; to:leave0.inlet }
Leave { id:leave0 }
Wire { from: queue0.measureLoad; to: chart0.parameters[0] }
Chart { id: chart0; height:66; width:384 }
Wire { from: leave0.measureSojourn; to: statistics0.parameters[0] }
Statistics { id: statistics0; height:48; width:128; label:"Sojourn\ntime" }Exponential arrivals, exponential service, 1 server: M/M/1.
Predict: same means, same waits?
Predict
Same arrivals, same mean service time 400, same ρ=0.8. Does the mean sojourn time go up, down, or stay at about 1200?
Run it
- It goes up, clearly: the run on the previous slide gives about 2000, the M/D/1 run about 940; long-run theory says 2000 instead of 1200
- A few very long services hold up everybody behind them
- Short services cannot make up for it: the server cannot work ahead of arrivals
Key insight: variability matters
Same averages, different behaviour. Randomness in the service alone makes the system worse.
The M/D/1 and M/M/1 examples side by side


Model codeOpen in Quisim
NumberInput { id: number0; initValue:125; label:"1/λ"; lower:1 }
NumberInput { id: number1; initValue:100; label:"1/µ"; lower:1 }
Wire { from: number0.measureValue; to: rnd0.parameterMean }
Wire { from: number1.measureValue; to:perform0.parameterDuration }
RandomExponential { id:rnd0 }
Wire { from:rnd0.measureRandomVariate; to:arrive0.parameterDuration }
Arrive { id:arrive0 }
Pipe { from:arrive0.outlet; to:queue0.inlet }
Queue { id:queue0 }
Pipe { from:queue0.outlet; to:perform0.inlet }
Perform { id:perform0 }
Pipe { from:perform0.outlet; to:leave0.inlet }
Leave { id:leave0 }
Wire { from: queue0.measureLoad; to: chart0.parameters[0] }
Statistics { id: statistics0; height:48; width:128; label:"Sojourn\ntime" }
Wire { from: leave0.measureSojourn; to: statistics0.parameters[0] }
Chart { id: chart0; height:66; width:384 }

Model codeOpen in Quisim
NumberInput { id: number0; initValue:125; label:"1/λ"; lower:1 }
NumberInput { id: number1; initValue:100; label:"1/µ"; lower:1 }
Wire { from: number0.measureValue; to: rnd0.parameterMean }
Wire { from: number1.measureValue; to: rnd1.parameterMean }
RandomExponential { id:rnd0 }
RandomExponential { id:rnd1 }
Wire { from:rnd0.measureRandomVariate; to:arrive0.parameterDuration }
Wire { from:rnd1.measureRandomVariate; to:perform0.parameterDuration }
Arrive { id:arrive0 }
Pipe { from:arrive0.outlet; to:queue0.inlet }
Queue { id:queue0 }
Pipe { from:queue0.outlet; to:perform0.inlet }
Perform { id:perform0 }
Pipe { from:perform0.outlet; to:leave0.inlet }
Leave { id:leave0 }
Wire { from: queue0.measureLoad; to: chart0.parameters[0] }
Statistics { id: statistics0; height:48; width:128; label:"Sojourn\ntime" }
Wire { from: leave0.measureSojourn; to: statistics0.parameters[0] }
Chart { id: chart0; height:66; width:384 }Both: 1/λ=125, 1/μ=100, ρ=0.8, about 80 customers per run. The only difference is one RandomExponential. Every run draws new random numbers, so your values will differ from these pictures.
Your turn: same means, different waits
Examples M/D/1 and M/M/1, 10 minutes
Run each example three times, record the mean sojourn time. Then set 1/μ=110 in both and repeat.
| M/D/1 | M/M/1 | M/D/1 | M/M/1 | |
|---|---|---|---|---|
| 1/μ | 100 | 100 | 110 | 110 |
| run 1 / 2 / 3 | ||||
| theory (long run) | 300 | 500 | 513 | 917 |
- Is M/M/1 worse in every single run, or only on average?
- Which grows faster when ρ goes from 0.8 to 0.88?
How much worse? Pollaczek–Khinchine
For one server with Poisson arrivals, cS = coefficient of variation of the service time:
Wq=21+cS2⋅1−ρρ⋅E[S]
| example | cS | Wq at ρ=0.8, E[S]=100 | W=Wq+E[S] |
|---|---|---|---|
| M/D/1 | 0 | 200 | 300 |
| M/M/1 | 1 | 400 | 500 |
Exponential service doubles the waiting time, and 1−ρρ explains the explosion near ρ=1.
Build it: something in between
In your M/M/1 model:
- Delete the service RandomExponential and its NumberInput
- Drag Perform's D onto V of RandomUniform
- Drag its max onto N of a NumberInput, type 800
- Service is uniform on [0,800): mean 400 again, cS2=1/3


Model codeOpen in Quisim
NumberInput { id:num0; initValue:500; label:"1/λ" }
Wire { from:num0.measureValue; to:rnd0.parameterMean }
RandomExponential { id:rnd0; seed:2 }
Wire { from:rnd0.measureRandomVariate; to:arrive0.parameterDuration }
Arrive { id:arrive0 }
Pipe { from:arrive0.outlet; to:queue0.inlet }
Queue { id:queue0 }
Pipe { from:queue0.outlet; to:perform0.inlet }
NumberInput { id:num1; initValue:800; label:"max" }
Wire { from:num1.measureValue; to:rnd1.parameterUpper }
RandomUniform { id:rnd1; seed:5 }
Wire { from:rnd1.measureRandomVariate; to:perform0.parameterDuration }
Perform { id:perform0 }
Pipe { from:perform0.outlet; to:leave0.inlet }
Leave { id:leave0 }
Wire { from: queue0.measureLoad; to: chart0.parameters[0] }
Chart { id: chart0; height:66; width:384 }
Wire { from: leave0.measureSojourn; to: statistics0.parameters[0] }
Statistics { id: statistics0; height:48; width:128; label:"Sojourn\ntime" }Predict with the formula: between 1200 (M/D/1) and 2000 (M/M/1), where? Theory: 400+32⋅4⋅400≈1470. This run: about 1170, between the M/D/1 run (940) and the M/M/1 run (2000).
Why does my run not show 300 and 500?
- A 10 s run sees only about 80 customers, and starts with an empty queue
- Two runs of the same model give noticeably different means
- Near ρ=1 the run is far too short to settle at all
Discuss
Which run is the "true" answer? How many runs would you trust? That is week 2.
Step 6: Several servers, M/M/k
Build it: give Perform a capacity
Back to your M/M/1 model (exponential service, 1/λ=500). Built the uniform one? Swap RandomUniform back for RandomExponential.
- Type 1/μ=800: now ρ=800/500=1.6
- Drag Perform's C (capacity) onto N of a NumberInput preview
- Type 2: Perform serves up to k=2 entities at once


Model codeOpen in Quisim
NumberInput { id:num0; initValue:500; label:"1/λ" }
Wire { from:num0.measureValue; to:rnd0.parameterMean }
RandomExponential { id:rnd0; seed:2 }
Wire { from:rnd0.measureRandomVariate; to:arrive0.parameterDuration }
Arrive { id:arrive0 }
Pipe { from:arrive0.outlet; to:queue0.inlet }
Queue { id:queue0 }
Pipe { from:queue0.outlet; to:perform0.inlet }
NumberInput { id:num1; initValue:800; label:"1/µ" }
Wire { from:num1.measureValue; to:rnd1.parameterMean }
RandomExponential { id:rnd1; seed:5 }
Wire { from:rnd1.measureRandomVariate; to:perform0.parameterDuration }
NumberInput { id:num2; initValue:2; label:"k" }
Wire { from:num2.measureValue; to:perform0.parameterCapacity }
Perform { id:perform0 }
Pipe { from:perform0.outlet; to:leave0.inlet }
Leave { id:leave0 }
Wire { from: queue0.measureLoad; to: chart0.parameters[0] }
Chart { id: chart0; height:66; width:384 }
Wire { from: leave0.measureSojourn; to: statistics0.parameters[0] }
Statistics { id: statistics0; height:48; width:128; label:"Sojourn\ntime" }Predict: how many servers do we need?
Predict
1/λ=500, 1/μ=800. With k=1 the queue explodes (ρ=1.6). With k=2? k=3? Does each extra server help equally?
Run it
- Utilization per server: ρ=kμλ; k=2 gives ρ=0.8: stable again
- k=2 rescues the system, but customers still wait (the run on the previous slide: about 2270); with k=3 the queue is mostly empty
- Long-run mean sojourn (Erlang C): k=2: 2222, k=3: 956, k=4: 830; the service alone takes 800
The M/M/k example


Model codeOpen in Quisim
NumberInput { id: number0; initValue:55; label:"1/λ"; lower:1 }
NumberInput { id: number1; initValue:500; label:"1/µ"; lower:1 }
Wire { from: number0.measureValue; to: rnd0.parameterMean }
Wire { from: number1.measureValue; to: rnd1.parameterMean }
RandomExponential { id:rnd0 }
RandomExponential { id:rnd1 }
Wire { from:rnd0.measureRandomVariate; to:arrive0.parameterDuration }
Wire { from:rnd1.measureRandomVariate; to:perform0.parameterDuration }
Arrive { id:arrive0 }
Pipe { from:arrive0.outlet; to:queue0.inlet }
Queue { id:queue0 }
Pipe { from:queue0.outlet; to:perform0.inlet }
NumberInput { id: number2; initValue:10; label:"Capacity"; lower:1 }
Wire { from: number2.measureValue; to: perform0.parameterCapacity }
Perform { id:perform0 }
Pipe { from:perform0.outlet; to:leave0.inlet }
Leave { id:leave0 }
Wire { from: queue0.measureLoad; to: chart0.parameters[0] }
Wire { from: perform0.measureLoad; to: chart0.parameters[1] }
Statistics { id: statistics0; height:48; width:128; label:"Sojourn\ntime" }
Wire { from: leave0.measureSojourn; to: statistics0.parameters[0] }
Chart { id: chart0; numParameters:2; height:66; width:384 }The chart shows two lines: queue length and busy servers. Default: 10 servers, ρ=500/(55⋅10)≈0.91.
Your turn: diminishing returns
Example M/M/k, 10 minutes
Set 1/λ=125 and 1/μ=200. Predict, then run each Capacity twice.
| Capacity k | 1 | 2 | 3 | 4 |
|---|---|---|---|---|
| ρ=λ/(kμ) | 1.6 | 0.8 | ||
| mean sojourn, run 1 / 2 | ||||
| busy servers on the chart: mostly … | ||||
| theory (Erlang C, long run) | ∞ | 556 | 239 | 208 |
- How much sojourn time does the 2nd server save? The 3rd? The 4th?
- When is a 4th server worth paying for?
Key insight: diminishing returns


Model codeOpen in Quisim
NumberInput { id: number0; initValue:125; label:"1/λ"; lower:1 }
NumberInput { id: number1; initValue:200; label:"1/µ"; lower:1 }
Wire { from: number0.measureValue; to: rnd0.parameterMean }
Wire { from: number1.measureValue; to: rnd1.parameterMean }
RandomExponential { id:rnd0 }
RandomExponential { id:rnd1 }
Wire { from:rnd0.measureRandomVariate; to:arrive0.parameterDuration }
Wire { from:rnd1.measureRandomVariate; to:perform0.parameterDuration }
Arrive { id:arrive0 }
Pipe { from:arrive0.outlet; to:queue0.inlet }
Queue { id:queue0 }
Pipe { from:queue0.outlet; to:perform0.inlet }
NumberInput { id: number2; initValue:3; label:"Capacity"; lower:1 }
Wire { from: number2.measureValue; to: perform0.parameterCapacity }
Perform { id:perform0 }
Pipe { from:perform0.outlet; to:leave0.inlet }
Leave { id:leave0 }
Wire { from: queue0.measureLoad; to: chart0.parameters[0] }
Wire { from: perform0.measureLoad; to: chart0.parameters[1] }
Statistics { id: statistics0; height:48; width:128; label:"Sojourn\ntime" }
Wire { from: leave0.measureSojourn; to: statistics0.parameters[0] }
Chart { id: chart0; numParameters:2; height:66; width:384 }Key insight
The first extra server rescues the system; each further one buys less. This is the core of capacity planning.
Look inside: parallel service
- M/M/k example, Capacity 3: Pause, open Monitor ▸ Events, Step
- Several service ends are scheduled at once: parallel service in the event list
- Select Perform, Monitor ▸ Entities: who is being served right now
Discuss
Banks, airport check-in and call centres use one shared queue for several counters. Why not one queue per counter? (Week 3 builds both.)
Wrap-up
What we built this week
Six models, each one fixing the last
- Arrive → Leave: correct, and useless
- + Perform: a busy server blocks the arrivals
- Bypass: no blocking, but customers are lost
- + Queue, M/D/1: stable only if ρ=λD<1; the event list drives it all
- M/M/1: same means, more variability, longer waits
- M/M/k: more servers help, with diminishing returns
Look back at your predictions: which one was furthest off?
Take-home
Example M/M/k at its defaults: 1/λ=55, 1/μ=500
- Set Capacity to 9. Predict, run, explain: what is ρ?
- Find the smallest Capacity that keeps the system stable. What is ρ then?
- Record the mean sojourn time for that Capacity and the next three:
| Capacity | 10 | 11 | 12 | 13 |
|---|---|---|---|---|
| mean sojourn, your runs | ||||
| theory (Erlang C) | 883 | 618 | 548 | 521 |
- Bonus, own model: M/M/2 with uniform service (as in step 5). Better or worse than exponential?
Next week
- Week 2: Statistical thinking. Why one run is not enough, replications, confidence intervals, simulation against the Erlang C formula: slides
- Course outline: Discrete-Event Simulation by Building
- More on queues: The M/M/1 Queue, Kendall notation, The Monitor tab
For the lecturer
Running the session
- Students build alongside you in their own browser; every block used in the build steps is in the demo kit
- Timing, about 100 minutes: Arrive→Leave 10, blocking 15, Bypass 10, M/D/1 25 (incl. event list), M/M/1 20, M/M/k 20
- Short on time? Keep the Build it and Predict slides; give Your turn: be the simulator and something in between as homework
- Ask for a written prediction before every run, and collect a few aloud: wrong ones are the most useful
Notes
- The demo code editor is read-only and examples cannot be extended: students build on an empty canvas, or change NumberInput values in an example
- Building, students' NumberInputs have no labels; the Model code boxes show which is which
- Short runs will not match the theory rows; let that discomfort lead into week 2
- 1/λ=500 gives only about 20 customers per run; switch to the examples (80 customers) or scale both times down by 5 when a pattern is hard to see
- Erlang loss, Pollaczek–Khinchine, Erlang C and Little's law (L=λW) are in the handbook