DEV Community

RONI DAS
RONI DAS

Posted on Originally published at systemdesign.academy

Two People Tap Seat 14 at Once: Designing redBus Seat Locks

Two people tap seat 14 on the same bus at the same second. What happens?

That one question decides most seat-booking interviews. It does not matter if the product is a bus, a train, a cinema or a concert. I want to walk through it using redBus. redBus makes the question harder in a way most booking systems do not: the seats are not its own.

The numbers

MakeMyTrip owns redBus. In its SEC filing, it reported $501.9 million of bus ticket bookings for April to June 2026. The number of tickets was up 23.9% on the same quarter a year earlier. A March 2026 case study by an observability company that works with redBus gives another number. It says redBus sells more than 100 million tickets a year, across India, Southeast Asia and Latin America.

100 million tickets a year is only about 3 bookings a second on average. That sounds easy, and that is exactly why this problem fools people. The hard part is not volume. A typical bus has somewhere around 30 to 50 seats. Take a popular Friday night bus before a festival. Hundreds of people are fighting over a handful of rows in a database.

The race

Here is the simple version. The booking service reads "seat 14 is free". Then it writes "seat 14 is held". Two customers can both read "free" before either one writes. Both think they have the seat.

The fix is to make the check and the change one step:

UPDATE seats
   SET state = 'held', hold_id = $3,
       hold_expires_at = now() + interval '8 minutes'
 WHERE trip_id = $1 AND seat_no = $2
   AND (state = 'available'
        OR (state = 'held' AND hold_expires_at < now()));
-- 0 rows updated: someone else has the seat
Enter fullscreen mode Exit fullscreen mode

I ran this on PostgreSQL, with two sessions racing for the same seat. The first one holds it. The second waits for the first to finish. Then the database checks the condition again, on the updated row. It finds the seat already held, and changes nothing. The app can tell the second customer right away: that seat was just taken, pick another.

A hand-drawn sketch. Asha and Bala both tap seat 14. The seat service sends one update to a PostgreSQL seats table that holds the seat only if it is free. Asha's update changes one row, so Asha gets the seat. Bala's changes zero rows, so Bala is asked to pick another seat.

Split the seat data by trip. Then this fight only ever involves one bus's 30 to 50 rows. It stays fast, even when that bus is the most wanted trip of the weekend.

A hold, not a lock

Notice what the update does not do. It does not take a database lock and keep it while the customer pays. A payment with a bank OTP in the middle can take minutes. Keeping a real lock that long would be a disaster.

Instead, the seat moves into a "held" state with an expiry time. If the customer pays, it becomes "booked". If they close the app, the hold simply runs out. The condition above treats a held seat whose time has passed as free. So the system stays correct even if a cleanup job runs late. The cleanup job is only there to tidy up.

How long should the hold be? Too short, and honest customers who are slow at the OTP screen lose their seat. Too long, and seats stay off sale for people who have already left. On a sold-out bus, that looks like "no seats" while seats sit unpaid. A few minutes, with the countdown shown to the customer, is the usual balance.

The awkward case: paid after the hold ran out

The customer paid. But the bank's confirmation came after the hold ran out, and someone else now holds the seat. The system must never give one seat to two people. So the late payment cannot just book it.

The booking tries to confirm. The conditional update fails, because the hold no longer matches. The booking moves to "payment received, seat lost". The customer gets a refund automatically. Ideally, they are also offered another seat on the same bus. This is an expected state with a planned way out. It is not an error that leaves someone charged with no ticket.

One seat, many sellers

This is the part that is special to redBus. According to Wikipedia, redBus runs BOGDS. It is a cloud service where bus operators manage their buses. It also runs SeatSeller, which gives bus seats to other agents to sell. So the same seat can be sold in three places. It can be sold on the redBus app, by a travel agent, or at the operator's own counter.

If each seller kept its own copy of the seat map, they would sell seat 14 three times. Some operators use redBus's own software. For them, every seller goes through the same seat data and the same conditional hold. So there is one truth. Other operators use their own booking software. For them, the truth lives in their system. So a hold on redBus must also be placed there before the customer pays. And the booking must be confirmed there before the ticket is issued. That is slower, and it can fail. So each operator connection needs its own timeout and circuit breaker. One slow operator must never stall booking for everyone else.

What I would say in an interview

  • Split a cached search path, which is mostly reads, from an exact booking path.
  • Hold seats with a conditional update that has an expiry. Never use a long database lock.
  • Take payment during the hold. Refund automatically if the payment lands after it.
  • Keep one source of truth per seat, across every place that sells it.
  • Put each outside operator behind its own timeout and circuit breaker.

redBus has not published how its booking system is built inside. So this is a standard seat-booking design that fits the public facts. It is not a description of its code. The full version, with the data model, trade-offs and sources, is in the redBus system design walkthrough. The same seat problem shows up at IRCTC, at far larger scale during Tatkal. It also shows up at BookMyShow for cinema seats.

Top comments (0)