Skip to content

Expert: design, speed and engines · Interview questions and practice projects · topic 61 of 62 · 40 min

Project: a railway booking database (IRCTC-like)

Explanation

Build and query a small railway booking database: stations, trains, the stops of each train in order, travel classes, passengers and bookings with a PNR and a status of CNF, RAC or WL. You will find trains between two stations in the right direction, count bookings by status, work out waiting-list positions and add up revenue per class. It is a simplified learning model, not how IRCTC really works.

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

TableColumnsNotes
stationscode (PK), name, cityMMCT, ST, BRC, ADI, RTM, KOTA, NDLS
trainstrain_no (PK), nameFour made-up trains, two in each direction
train_stopstrain_no, station_code, stop_order, arrival, departure, dayThe route; PK (train_no, stop_order). day 2 means the next day
classescode (PK), nameSL, 3A, 2A, CC
passengersid (PK), name, age, genderOne passenger per booking in this model
bookingspnr (PK), passenger_id, train_no, journey_date, class_code, from_code, to_code, status, booked_at, farestatus 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:

sql
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:50
Trains 10002 and 20004 also stop at both stations, but in the other order, so a.stop_order < b.stop_order drops them.
Careful: Sorting departure as text works here because times are written HH:MM. A real system must also handle trains that leave after midnight (the day column) and journey dates that refer to the day the train starts from its origin, not the day it reaches your station.

Bookings by status

sql
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
-- ...
Add SUM(CASE WHEN status = 'WL' THEN 1 ELSE 0 END) columns to pivot it into one row per train and date.

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:

sql
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 | 1

Revenue 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.

sql
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 | 760

Ideas 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

Project: railway bookings

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.

sql
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';
Vadodara to Mumbai Central: trains 10002 and 20004.

Practice (0/4 done)

Task 1 · Code · SQL

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.

Task 2 · Code · SQL

Count bookings per status for each train and journey date. Return train_no, journey_date, status and bookings (the count).

Task 3 · Code · SQL

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.

Task 4 · Code · SQL

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

Question 1

1.Train 10002 stops at Vadodara (stop 4) and Surat (stop 5). Does a search from Surat to Vadodara show it?

Question 2

2.Why is the waiting list numbered with PARTITION BY train_no, journey_date, class_code?

Question 3

3.What is the primary key of train_stops in this model?

Question 4

4.Which statement about this project is honest?

Read more (free)

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.

From Travel: stations, airports, hotels and directions

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.

From Joining three or more tables

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. 1 more topic in “Interview questions and practice projects” before its exam: Project: e-commerce analytics, and your next steps.
  2. Module 12 exam: interview questions and projects · 13 questions · 25 min, without help. 5 topics of this module still to finish first.
  3. 62 topics left in the course before the final exam.

← Previous: Theory questions you will be asked