Engineering2026-03-20

The race condition that kept us up for three nights — and what we learned from it(updated)

S

Saad

Co-Founder · Engineering

8 min read

Reading time

Hiddit · Sports SaaS

Project context

It was a Thursday afternoon in Kigali, six days before the Hiddit app was scheduled to go live. A tester from the client side sent a message we dreaded: 'Two users booked the same court at the same time.' The receipt said both bookings were confirmed. That sent us down a three-night debugging spiral that changed how we think about concurrency for good.

The setup

Hiddit is a real-time sports court booking app. Players can see live court availability, pick a slot, and book — all in under 30 seconds. We had built the booking flow with optimistic locking: check availability, create a booking if the slot is free, return confirmation. Simple. Except under concurrent load, 'check then write' doesn't mean what you think it means.

At the same millisecond, two players checked court #4 at 6pm. Both queries returned 'available'. Both proceeded to write. PostgreSQL's default READ COMMITTED isolation level allowed both transactions to complete — and we had a double booking.

typescript
  async function handleSubmit(e: React.FormEvent) {
    e.preventDefault()
    setError("")
    setLoading(true)
    try {
      const data = await apiFetch<{ access_token: string }>("/auth/login", {
        method: "POST",
        body: JSON.stringify({ password }),
      })
      setToken(data.access_token)
      router.push("/admin/dashboard")
    } catch (err) {
      setError(err instanceof Error ? err.message : "Invalid password.")
    } finally {
      setLoading(false)
    }
  }
S

Saad

Engineer at ASYNCC. Shipping software that works in the real world, not just in dev.

Previous post

New post

◄ Read it