← all work

Software · 01 / 06 · Java · Spring Boot · React

Showtime: Event Ticketing

A BookMyShow-style ticketing app with live seat maps, where a seat is never sold twice however many people go for it at once.

59,000+hold attempts in a 60 s rush
1.6 msp95 hold latency
18 / 18cross-instance browser races passed
0seats sold twice, audited in SQL

Why I built this

In the real world

When a big concert or an IPL final goes on sale, thousands of people click the same seats in the same second. If two of them both get a confirmation, one turns up at the venue to find someone in their seat, and the ticketing company eats the refund and the anger.

What I wanted to find out

How do you let thousands of people grab seats at the same moment without ever selling one twice, and still show everyone the seat map changing live? I wanted to build the full app, Java backend to React frontend, and then stampede it.

What I built

A full ticketing app: browse events, pick seats on a live seat map, hold them for ten minutes, pay, get a booking. Spring Boot with JWT auth and roles on the backend, React and TypeScript on the front.

Seats are rows in PostgreSQL. Holding locks them with FOR UPDATE SKIP LOCKED, so two buyers racing get an instant yes and an instant no. Payment is confirmed by an HMAC-signed webhook, processed exactly once, and every browser on either server sees seats change as they happen.

How it works

BrowserReact seat map
nginxtwo API instances
Spring BootJWT, holds, checkout
PostgreSQLrow locks + NOTIFY
SSElive seats to every browser

Each API instance LISTENs to PostgreSQL, so a seat taken through one server shows up instantly for buyers connected to the other.

How it stays correct

A seat is never sold twice

Seat status lives on one row with check constraints; holds lock with SKIP LOCKED and never wait.

Holds expire by themselves

An expired hold counts as free everywhere, so correctness never depends on a background job.

Payments confirm exactly once

Webhooks are HMAC-verified and deduplicated by event id in the same transaction as the booking.

Every screen stays live

pg_notify to LISTEN on each instance, then Server-Sent Events to each browser.

Measured

Rush
200 buyers for 60 s at one 1,000-seat event: 59,119 hold attempts, 424 bookings
Audit
0 seats sold twice; 50 buyers racing for one seat got exactly 1 hold
Webhooks
The same webhook sent 5 times at once created 1 booking
Tests
24 integration tests (95.6% lines), 25 web tests, Playwright races 18/18