ZB Field Notes

Parking apps for employees: a fairness ledger to the rescue

Parking apps for employees: a fairness ledger to the rescue

I've been building an employee parking app — the kind where a company has one underground lot, more badge holders than spaces, and someone in facilities who is tired of being the referee. The booking part was the easy weekend: lots, slots, reservations, a calendar. The part worth writing about is the question the CRUD politely avoids: when there aren't enough slots, who gets one?

No code in this post. Just the idea I ended up committed to, because I think it generalises well beyond parking: desks, GPU hours, on-call weekends off — any scarce thing a team shares.

First-come-first-served allocates alarm clocks

Almost every parking system starts as first-come-first-served, because FCFS doesn't feel like a policy at all. Everyone gets the same starting line each morning; the system just processes clicks in order. What could be fairer?

Except FCFS rewards exactly one skill — clicking fastest at seven — and it has a deeper flaw that takes a month to see: it's memoryless. Every morning it forgets who won yesterday. The race is fair; the season isn't.

Table showing monthly outcomes under first-come-first-served: Alice 19 slot-days, Bob 9, Dave 2 — Dave does school drop-off and always arrives at 8:30
Zoom out and the accumulation appears: no single day broke a rule, yet Alice ends the month with 19 slot-days and Dave — school drop-off, arrives 8:30 — with 2.

Nobody can point at the day the system was unfair, because there isn't one. The unfairness lives entirely in the accumulation, and FCFS has no organ that can even see accumulation.

Give the system memory

The fix I landed on is old accounting technology: a ledger. Every time you get a slot, a line is written: grant, +1.0. Every time you release a slot you'd reserved — the day before, in time for someone else to use it — another line: release credit, −0.5. Your balance is the sum of your lines over the last 30 days.

A fairness ledger with dated entries: grants of +1.0 for Alice and Dave, and a release credit of −0.5 for Bob
The whole model: getting a slot adds a point, giving one back early subtracts half of one, and your balance is a 30-day window over the lines.

That balance is the missing memory. It's not a score to maximise — it's closer to a tab. The lower your balance, the stronger your claim next time slots are contested.

Stop racing, start requesting

Memory only matters if something reads it, so contested days stop being a race and become a draw. During the day, anyone can request tomorrow — requests are wishes, not bookings, and the order you ask in doesn't matter. At a published cutoff (18:00 in my build), one allocation pass ranks every requester by balance, lowest first, ties going to whoever asked earlier. Winners get real reservations; everyone else is waitlisted. And each award immediately writes its +1.0 back into the ledger.

Pipeline: request a day, cutoff at 18:00, rank by balance ascending, award slots, waitlist the rest
Decisions happen once, at a published time, by a published rule — not at 07:00:04.

That last property is worth underlining. Under FCFS, the allocation decision is smeared across a thousand anxious clicks. Under a draw, it happens once, at a time everyone knows, by a rule everyone can read. You can disagree with a rule like that, but you can't be ambushed by it.

Winning today costs you tomorrow

The elegant part — the reason I like this design more the longer I sit with it — is that the loop is self-correcting. Every win raises your balance, which lowers your priority for the next contested day. Every loss leaves your balance flat, so you rise until you win.

Tomorrow's draw for the last slot: Dave with balance 2.0 ranks first, Bob 9.0 second, Alice 19.0 third
The same three people from the FCFS month, re-ranked by the ledger: Dave's low balance now makes him first in line.

Nobody has to manage the rotation — no spreadsheet, no facilities referee, no "it's B-team's week". The ledger is the rotation. Dave's two-slots-a-month problem dissolves without anyone having noticed it, decided to fix it, or negotiated who owes whom.

The scarcest thing is the freed slot

Here's the operational secret of every corporate lot: it runs full on paper and half-empty in reality. The binding constraint isn't the concrete — it's reserved-but-unused slots. Which is why the release credit isn't a gimmick; it's the mechanism that makes the whole system liquid. Freeing your slot the day before earns you −0.5, so good behaviour compounds into easier future draws. And the freed slot doesn't sit idle: the best-ranked waitlisted colleague is promoted automatically. No dispatcher, no email thread, no "is anyone using A-04 today?" in the team channel.

A ledger, not a counter

You could squint and say this is just a counter per person. The reason I want the full ledger — dated, line-item, append-only — is everything a single number can't hold. Entries can be weighted: a Friday slot isn't a Monday slot. They can decay: wins from six weeks ago stop counting against you. They can be recorded without counting: accessibility and carpool grants belong in the history, but a works council may decide they shouldn't cost anyone priority. All of that is policy, and policy changes — the ledger absorbs the changes; a counter would have to be reinvented for each one.

And the killer feature isn't algorithmic at all. In Luxembourg, where I'm building this, every company above fifteen employees has a délégation du personnel — an employee delegation — and a parking policy is exactly the kind of thing that lands on its table. HR doesn't need a clever allocator. HR needs to defend the outcome to the person who lost. An append-only ledger means "why not me?" is answered with lines, not with a shrug: here are your grants, here is your balance, here is who ranked above you and why. A counter produces a score. A ledger produces reasons.

Fairness is memory plus transparency

That's the whole idea, and I think it's the honest definition. A system is fair when it remembers what it already gave you, and when it can show its reasoning to the people it disappoints. Everything else in my build — the nightly draw, the release credits, the waitlist promotion — is plumbing around those two properties.

Where it goes next: no-show penalties (a reserved slot that stays empty should cost more than a released one), decay curves, and maybe a weighted lottery instead of a strict sort, so low-balance folks are likely rather than guaranteed to win — strict determinism has its own failure mode once people learn to game the calendar. But those are tunings. The load-bearing decisions are the ledger and the published rule, and I'd reuse both for any scarce resource a team shares.