Altamar Labs
Back to Blog
architecture· 3 min read

Concurrency in bidding systems: when two users can't both win

A real-time auction system concentrates nearly every classic concurrent-write challenge into a single problem. What it teaches about designing for the exact moment two people compete for the same thing.

Few engineering problems expose concurrency design mistakes as fast as a real-time bidding system. Unlike most write operations, where two users rarely modify the exact same resource at the exact same instant, a popular auction is designed, by definition, for multiple people to attempt the same thing (winning the current bid) in the smallest possible window of time.

The problem isn't the individual bid, it's the race

Recording an isolated bid is trivial: validate the amount is higher than the current one, save it, done. The problem shows up when two bids for different amounts arrive almost at the same time, milliseconds apart. If the system reads the current amount, compares it, and then writes the new bid as three independent steps with no locking, both bids can read the same "current" amount before either finishes writing, and both can pass the "I'm higher than the current amount" check simultaneously. The result is a classic race condition that, in a system handling real money, isn't a cosmetic bug: it's a winning bid lost or an incorrect amount recorded as the highest.

Why "just use a transaction" isn't enough on its own

Wrapping the operation in a database transaction helps, but doesn't fully solve the problem if two concurrent transactions can read the same state before either commits, depending on the configured isolation level. The real solution requires one of two explicit strategies: pessimistic locking, which forces the second bid to wait for the first to finish before even reading the current amount, or optimistic locking, which lets both read the same state but rejects and forces a retry on whichever writes second, detecting the conflict at save time.

For a bidding system, where the conflict window is brief but the volume of attempts can be high in an auction's final seconds, the choice between these two strategies isn't an implementation detail. It determines whether the system behaves predictably at exactly the moment of highest traffic and highest business value: the auction's close.

The second problem: time is also a shared resource

A well-designed auction automatically extends its closing time if a bid arrives in the final seconds, to prevent someone from winning simply by timing their offer to the last millisecond. That time extension is, itself, another concurrently mutated resource: if two last-second bids arrive nearly together, the system has to consistently decide how much the close extends, without both requests calculating the extension independently and producing an inconsistent result across the different clients connected in real time.

The lesson

A bidding system concentrates, in a relatively small piece of code, nearly every classic concurrent-write challenge: reads and writes competing for the same state, the need to choose between pessimistic and optimistic locking based on the expected contention pattern, and a time window that is itself a mutable piece of data under the same concurrent pressure. Any engineer who's solved this correctly once has, in practice, a mental map that applies directly to limited inventory, seat reservations, or any scarce resource multiple users try to claim at the same time.