Two clients opened my booking platform, picked the same 2:00 PM slot at the same salon, and both hit "Confirm" within a couple hundred milliseconds of each other.
Both requests should have failed for one of them. Both succeeded.
Why this is scary and not just a bug: this is exactly the kind of failure a business owner doesn't notice until a customer shows up to a chair someone else is already sitting in. No error, no crash, no log line screaming at you — just a quietly overbooked afternoon.
What I tried first (and why it didn't work)
My first fix was the obvious one: check for a conflict, then insert the booking if there isn't one.
That works in every manual test I ever ran. It falls apart under real concurrency, because the check and the insert are two separate steps. Two requests can both check, both see nothing, and both proceed. Classic race condition — the fix looked right and was wrong.
The actual fix
I moved the conflict check into the database itself, as a trigger that runs in the same transaction as the write. Instead of "check, then maybe insert," it's "the database refuses the insert if a conflict exists — atomically, no gap for a second request to sneak through."
One extra wrinkle I didn't expect: bookings can have no assigned staff member yet (NULL), and in SQL, NULL = NULL isn't true — it's neither true nor false. A plain uniqueness check silently stops protecting anything the moment a field is optional. Postgres has a specific NULL-safe comparison operator for exactly this (IS NOT DISTINCT FROM), and that one detail was the actual fix, not the trigger itself.
What I took away from this
I'm not a formally trained backend engineer — I built this product mostly by directing Claude Code and thinking through the logic myself. The lesson that stuck with me: the bugs that scare me most now are the ones with no error message. A crash tells you something's wrong. A silent double-booking doesn't — you only find out when a real customer is standing in the wrong place at the wrong time.
Since then, my personal rule is: anything touching money, availability, or a shared resource gets a five-minute "what happens if this runs twice at once" pass before it ships. Not a formal audit — just one deliberate question I used to skip.
Pronto is the open-source booking/CRM/POS platform this happened in, if anyone wants to see the actual trigger: github.com/SGrappelli/pronto
Curious how other solo/non-technical founders building with AI coding tools catch this class of bug before it reaches production — do you have a checklist, or did you learn it the same way I did?
The IS NOT DISTINCT FROM detail is the real twist here. Most people would catch the race condition eventually, but the NULL comparison silently breaks after you fix it - that's the kind of thing that surfaces weeks later in production when someone first creates a booking without assigning staff. Great catch documenting that second layer. The "one deliberate question before shipping" rule for shared resources is solid - I'd extend it to anything where the cost of the bug compounds (bookings, payments, inventory reservations).
the silent ones are always the worst: no crash, no log, just a furious customer two days later. classic read-then-write race; the fix almost always has to live in the database (unique constraint or a row lock on the slot), because app-level checks lose that millisecond gap every time. did a DB constraint end up being your one-liner, or did you solve it in code?
This is a good example of why production bugs are often business problems, not just engineering problems.
The interesting part isn't only fixing the race condition — it's building the habit of asking what happens when two real users do the same thing at the same time.