Why I Chose a Database-Level Exclusion Constraint Over Application Logic to Stop Double-Bookings
A booking race condition, two ways to fix it, and why the boring database-level answer beat the clever application-level one.
Two admins on Amber Car Rental can act on the same vehicle at nearly the same moment — one confirming a booking, another blocking it for maintenance. The naive fix is an application-level check: query for overlapping bookings before you insert, reject if you find one. It works until it doesn't.
Where the naive check breaks
Two requests can both read "no overlap" before either has written its own booking. Neither check saw the other's in-flight write. That's a race condition, and it gets worse under load — exactly when a rental agency's actual weekend traffic hits.
The fix: push the constraint into Postgres
Postgres supports exclusion constraints — a way of saying "no two rows in this table may overlap on this condition," enforced by the database itself, not by application code that can race.
ALTER TABLE bookings
ADD CONSTRAINT no_overlapping_bookings
EXCLUDE USING gist (
vehicle_id WITH =,
during WITH &&
);Now a conflicting insert doesn't get a chance to race — the database rejects it outright, and the application just needs to handle that rejection gracefully in the UI. The correctness guarantee moved from "code I hope is right" to "a constraint Postgres enforces on every write, always."
Why this is the right level of clever
It's tempting to reach for a locking scheme or a queue instead. For a single-table, single-agency booking system, that's solving a problem this system doesn't have yet. The exclusion constraint is a few lines, it's provably correct, and it doesn't add an operational moving part a solo developer has to babysit.
A car rental agency needed to move off manual, phone-and-spreadsheet bookings to a real-time online platform — built and run solo.