I made my factory 8% faster and 25 times slower
I spent two months building a factory simulator. The most useful thing it taught me is not about factories, and I found it by making a mistake.
My production line has five stations, each feeding the next through a buffer. I can pay cash to make those buffers bigger. Bigger buffers mean a station spends less time waiting for parts, so the line gets closer to its theoretical output. That is true, and it is intuitive, and I did it. Here is the full bill.
| Buffer size | Throughput /min | % of theoretical max | Average WIP | Cycle time |
|---|---|---|---|---|
| 12 (default) | 51.6 | 92% | 11 | 13 s |
| 20 | 52.8 | 94% | 19 | 21 s |
| 36 | 54.1 | 96% | 33 | 37 s |
| 1000 | 55.8 | 99% | 305 | 328 s |
Output rose 8%. The time any single item takes to get through the factory rose by a factor of twenty-five.
Every number there is measured, not asserted — the simulation runs headless over five fixed seeds and reports what actually happened, and those bands are locked in the test suite so CI tells me when a tuning change breaks them. That is its own post. What matters here is that I was watching the throughput counter, the throughput counter said I was winning, and it was telling the truth about the only thing I had asked it.
One disclosure, because it matters: that table is a controlled measurement. To isolate the buffer axis I turned off scrap and made demand infinite, so the only thing varying down those rows is buffer size. It is the honest way to measure one variable, and it is not the game as played.
With finite demand switched back on, it gets worse. The same sweep on the real model produces 7.4 units/min at every buffer size — dead flat — while WIP climbs from 35 to 2,903 and cycle time goes from 288 seconds to 23,647. The buffer upgrade buys no throughput at all and still charges the full inventory bill. The controlled table above is the version that is generous to my mistake.
The harness also reported the bottleneck as 82% blocked and 0% starved. Hold onto that pair; it turns out to be the most useful thing on the whole readout, and I did not understand it at the time.
The short version of why
That is a buffer table, but it is really a utilization table in disguise. Bigger buffers mean the bottleneck station starves less often, so it is busy a greater fraction of the time — and that is precisely why output went up. Utilization is climbing toward 100% as you read down those rows. Everything else in the table is what that costs.
Queue time at a busy station scales with u / (1 − u), where u is utilization.
That term is 4 at 80% busy, 9 at 90%, 19 at 95%, and 99 at 99%. It is a
hyperbola, so there is no point on it where the next percent is cheap; the curve
is made of the next percent getting more expensive. Multiply that by how
irregular your work is — and something always is: my stations break down 6% of
the time, which is a good availability number, and it alone pushes effective
variability up by about 40%.
I am not going to re-derive Kingman's equation here, because I already wrote that page: Kingman's equation: why a balanced line still stalls has the VUT form, the measured variability of this simulation against Hopp and Spearman's reference curves, and the three-buffers framing — inventory, capacity, or time, pick one, you are paying with one of them whether you choose or not.
The part I want to talk about is the last column.
Little's Law sends the invoice
WIP = throughput × cycle time. Rearranged: at a fixed throughput, inventory
and delay are the same quantity. Not correlated. Not commonly seen together.
Algebraically the same thing, viewed from two directions. (The longer
version.)
That is why the WIP column and the cycle-time column in my table move in lockstep, and why my purchase was worse than it looked. I thought I was spending cash to buy throughput. I was spending cash to buy 8% throughput and a 25× increase in how long every item sat inside the factory — in a game where items only become money when they come out the far end. My line got faster and my business got slower.
This is not about factories
Kingman's equation does not know what is in the queue. It applies to anything where work arrives at a resource that is sometimes already busy, which is a complete description of every part of a software organisation.
A team planned to 100% of capacity is not an efficient team. It is a team whose lead times are, by this equation, unbounded. Each additional point of allocation buys a shrinking amount of output and a growing amount of delay, and the slack that looks like obvious waste on a capacity plan is the only reason anything ships predictably. The quarter that is completely full is the quarter that slips, and it does not slip because anyone was lazy.
The Little's Law half is worse, because it is measurable and nobody measures it. Your WIP is open branches, unreviewed PRs, half-finished migrations, features sitting behind a flag nobody has switched on. All of that is cycle time wearing a disguise. Starting more work cannot raise throughput past your constraint — the constraint is what sets throughput, that is what the word means. Starting more work only lengthens the queue in front of it, and unlike steel, code in a queue rots: it conflicts, it needs rebasing, its author forgets it, its reviewer loses context.
This is also, for what it is worth, the entire mechanism behind WIP limits, and the reason so many teams adopt a board and get nothing out of it. The board is not the mechanism. The limit is the mechanism.
And the sharpest one is the diagnostic I skipped past earlier. When I ran that sweep with real demand switched on, the harness reported the bottleneck as 82% blocked and 0% starved. Those two numbers mean opposite things. Starved is a station standing idle because nothing arrived — a supply problem, upstream, fixable with capacity. Blocked is a station that has finished work and has nowhere to put it — a demand problem, downstream, and completely immune to capacity. Every instinct I had was calibrated for starved. The line was blocked.
That distinction is the one I would actually take to work. A team that is blocked is not short of engineers. It is producing something at a rate nobody downstream can absorb — features arriving faster than anyone can onboard, review, support, sell, or ask for. Hiring into that makes the numbers worse in exactly the way my buffer purchase did: more in flight, more aging, more carrying cost, identical output. The uncomfortable version is that "we need more capacity" is the most common diagnosis in software and it is only correct when the constraint is starved, which is a question almost nobody thinks to ask.
The reason I found it in a game and not at a job is that the game prints both numbers next to each other, every tick, for free. Most organisations measure how busy people are and never measure whether the work they finish has anywhere to go.
What I would actually do
Find the constraint, then stop optimising anything else. Capacity added anywhere but the bottleneck does not raise output. In my sim, doubling an upstream station past the bottleneck moved throughput by exactly zero and multiplied in-transit inventory sevenfold. That one has its own ugly table and its own post coming.
Track cycle time, not utilization. Utilization is the metric that walks you into this, because it looks better and better right up until everything is late. Cycle time and WIP are the two that tell you what your utilization is costing.
Buy variability reduction before you buy capacity. Cutting variability shifts the whole curve down, and it is usually cheaper than another machine or another headcount. In the sim, preventive maintenance raises availability to ~99% and roughly halves the variance the breakdowns were adding — two effects, one purchase. The software versions are unglamorous and well known: smaller batches, faster feedback, fewer things half-done at once.
None of this is new. Kingman and Little both published in 1961. It is simply not taught to most of the people who spend their working lives inside queues.
I build Flowline, a factory sim where these equations are the game rather than the theme — the constraint moves, variability is the antagonist, and every number above comes out of the real engine. If you want the version of this post you can argue with, Deep Freeze is a puzzle where capacity is genuinely fine and the line stalls anyway; buying more machines is a proven non-fix. It runs in a browser, free, no account.
Further reading: Factory Physics (Wallace Hopp & Mark Spearman) for the VUT form and the variability classes, and The Goal (Eliyahu Goldratt) for the constraint. Not affiliated with or endorsed by their authors or publishers.