Expert: design, speed and engines · Interview questions and practice projects · topic 61 of 62 · 40 min
Project: a railway booking database (IRCTC-like)
Explanation
The idea
Indian Railways' booking system, run through IRCTC, is one of the busiest booking platforms in the world. Its real design is not public, and it handles things we will skip: quotas (Tatkal, ladies, senior citizen), seat-level availability per segment, charts, refunds and much more. Our model keeps the core questions a learner can answer with joins and window functions. Station codes are real; train numbers, times, PNRs and fares are made up.
The schema
| Table | Columns | Notes |
|---|---|---|
| stations | code (PK), name, city | MMCT, ST, BRC, ADI, RTM, KOTA, NDLS |
| trains | train_no (PK), name | Four made-up trains, two in each direction |
| train_stops | train_no, station_code, stop_order, arrival, departure, day | The route; PK (train_no, stop_order). day 2 means the next day |
| classes | code (PK), name | SL, 3A, 2A, CC |
| passengers | id (PK), name, age, gender | One passenger per booking in this model |
| bookings | pnr (PK), passenger_id, train_no, journey_date, class_code, from_code, to_code, status, booked_at, fare | status is 'CNF', 'RAC' or 'WL', enforced by a CHECK |
CNF means a confirmed berth. RAC (Reservation Against Cancellation) means you may travel but share a side berth until one frees up. WL means waiting list: you get a berth only if enough people ahead of you cancel. A real PNR can hold up to six passengers; we keep one passenger per booking so the queries stay simple.
Trains between two stations, in the right direction
The heart of the model is train_stops. A train goes from Surat to Vadodara only if it stops at both and Surat comes first: the stop_order of the "from" stop must be smaller than that of the "to" stop. Join train_stops to itself, once for each station:
SELECT t.train_no, t.name, a.departure, b.arrival
FROM train_stops a
JOIN train_stops b ON b.train_no = a.train_no AND a.stop_order < b.stop_order
JOIN trains t ON t.train_no = a.train_no
WHERE a.station_code = 'ST' AND b.station_code = 'BRC'
ORDER BY a.departure;
-- 20003 | Gujarat Intercity | 08:45 | 10:20
-- 10001 | Mumbai Delhi Express | 19:10 | 20:50Bookings by status
SELECT train_no, journey_date, status, COUNT(*) AS bookings
FROM bookings
GROUP BY train_no, journey_date, status
ORDER BY train_no, journey_date, status;
-- 10001 | 2025-11-14 | CNF | 4
-- 10001 | 2025-11-14 | RAC | 1
-- 10001 | 2025-11-14 | WL | 4
-- ...Waiting-list position
Waiting lists are kept per train, date and class, in the order people booked. ROW_NUMBER with PARTITION BY gives each WL booking its place in its own queue:
SELECT b.pnr, p.name, b.class_code,
ROW_NUMBER() OVER (PARTITION BY b.train_no, b.journey_date, b.class_code
ORDER BY b.booked_at) AS wl_position
FROM bookings b JOIN passengers p ON p.id = b.passenger_id
WHERE b.status = 'WL' AND b.train_no = '10001' AND b.journey_date = '2025-11-14';
-- 4501234566 | Arjun Reddy | 3A | 1 (booked 1 Nov, 07:10)
-- 4501234564 | Imran Khan | 3A | 2
-- 4501234565 | Sneha Patil | 3A | 3
-- 4501234568 | Karan Mehta | SL | 1Revenue per class
Count only CNF and RAC tickets as revenue: in our model, waiting-list tickets that never confirm are refunded. Join to classes for readable names.
SELECT c.name AS class, COUNT(*) AS bookings, SUM(b.fare) AS revenue
FROM bookings b JOIN classes c ON c.code = b.class_code
WHERE b.status IN ('CNF', 'RAC')
GROUP BY c.name
ORDER BY revenue DESC;
-- AC 3 Tier | 5 | 12350
-- AC 2 Tier | 1 | 4000
-- AC Chair Car | 2 | 1550
-- Sleeper | 1 | 760Ideas to extend it
- A coaches and berths table, and a seat_allocations table per segment (a berth from MMCT to BRC can be sold again from BRC to NDLS).
- A quota column (GN, TQ for Tatkal, LD, SS) and availability per quota.
- A trigger that moves the first RAC or WL booking up when a CNF booking is cancelled (module 11).
- A booking_passengers table so one PNR holds several passengers.
Common mistakes
- Forgetting the direction check, so a Surat → Vadodara search shows trains going the other way.
- Numbering the waiting list across all classes or dates. PARTITION BY train, date and class.
- Counting WL fares as revenue.
- Joining stops without matching train_no, which pairs stops of different trains.
Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
Project: a railway booking database (IRCTC-like) · 1/7
Our first project is a small railway booking database, IRCTC-like in spirit. It is a simplified learning model, not the real design.
Prefer a video by a teacher? Find free videos on “railway reservation system database design sql project” on YouTube ↗
In real life: What happens when you search "Surat to Vadodara"
On any train booking site, a station-to-station search must find trains that stop at both stations in the right order, then show availability per class. Real systems pre-compute and cache a lot of this because millions of searches arrive at peak times such as Tatkal opening. The self join on the route table you write here is the core idea; the production version adds indexes on (station_code, train_no), caching and seat inventory per segment.
SELECT t.train_no, t.name, a.departure, b.arrival
FROM train_stops a
JOIN train_stops b ON b.train_no = a.train_no AND a.stop_order < b.stop_order
JOIN trains t ON t.train_no = a.train_no
WHERE a.station_code = 'BRC' AND b.station_code = 'MMCT';Practice (0/4 done)
Find the trains that go from Surat ('ST') to Vadodara ('BRC') in that direction. Return train_no, name (from trains), departure (from Surat) and arrival (at Vadodara), sorted by departure time.
Count bookings per status for each train and journey date. Return train_no, journey_date, status and bookings (the count).
Work out waiting-list positions for train '10001' on '2025-11-14'. Each class has its own queue, ordered by booked_at. Return pnr, name (the passenger's name), class_code and wl_position for the WL bookings only.
Revenue per class, counting only 'CNF' and 'RAC' bookings. Return class (the class name from classes), bookings (the count) and revenue (the sum of fare), highest revenue first.
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- Project: e-commerce analytics, and your next stepsInterview questions and practice projects · same module
- Classic interview queries, part 2Interview questions and practice projects · same module
- Travel: stations, airports, hotels and directionsSpoken English: Zero to Fluent · Real-life conversations
- Interval problemsData Structures and Algorithms (DSA) for interviews · Heaps and greedy algorithms
- What is a backend?Backend Development · How the web and backends work
- Train, validation and test setsData Science, Machine Learning and AI · Evaluating models
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
Train 10002 stops at Vadodara (stop 4) and Surat (stop 5). Does a search from Surat to Vadodara show it?
Answer: No, because Surat's stop_order is not smaller than Vadodara's
It runs Vadodara → Surat; the direction check a.stop_order < b.stop_order removes it.
Why is the waiting list numbered with PARTITION BY train_no, journey_date, class_code?
Answer: Each train, date and class has its own separate queue
A 3A waiting list on one date has nothing to do with Sleeper or another date.
What is the primary key of train_stops in this model?
Answer: (train_no, stop_order)
A train has one stop at each position in its route, so the pair is unique.
Which statement about this project is honest?
Answer: It is a simplified learning model; real systems add quotas, per-segment seats, charts and much more
The model keeps the core ideas only. A real PNR can hold several passengers, and RAC means a shared berth.
At a ticket counter, the most natural request is...Related
Answer: One ticket to Pune, please.
"One ticket to Pune, please" is short, polite and correct: ticket to a place.
Trains: arrivals [1000, 1015] and departures [1015, 1030]. With the rule "arriving when another departs needs a new platform", how many platforms?Related
Answer: 2
At 10:15 the first train is still there as the second arrives, so both need a platform.
From Interval problems
Why use order_items.price rather than products.price for revenue?Related
Answer: order_items stores the price actually paid, which does not change when the product price changes later
History must keep the amount charged at the time.
Why must payment keys never be in frontend code?Related
Answer: Anyone can open the app or website and read them
Frontend code is delivered to every user, so anything inside it can be read. Secrets stay on the server.
From What is a backend?
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- 1 more topic in “Interview questions and practice projects” before its exam: Project: e-commerce analytics, and your next steps.
- Module 12 exam: interview questions and projects · 13 questions · 25 min, without help. 5 topics of this module still to finish first.
- 62 topics left in the course before the final exam.