0) Problem Restatement
Design course registration for a university (asked at JPMorgan). Students browse courses and sections, and register when their registration window opens. Popular sections fill in seconds. If a section is full, students join a waitlist, and when someone drops, the next waitlisted student gets the seat. The system must enforce prerequisites, no schedule conflicts and credit limits. The hardest moment: thousands of students clicking "register" at the same second when registration opens.
1) Requirements
- Browse courses and sections (time, room, instructor, seats left).
- Register and drop. Seat counts must be strictly correct (no over-enrollment).
- Waitlist with fair ordering and automatic promotion.
- Validate prerequisites, schedule conflicts and credit limits.
- Handle huge spikes at window openings.
2) Data Model
CREATE TABLE sections (
section_id BIGINT PRIMARY KEY, course_id BIGINT, term TEXT,
capacity INT, enrolled INT DEFAULT 0 CHECK (enrolled <= capacity),
meeting_times JSONB -- e.g. [{"day":"MON","start":"10:00","end":"11:15"}]
);
CREATE TABLE enrollments (student_id BIGINT, section_id BIGINT, status TEXT, -- enrolled, dropped
created_at TIMESTAMP, PRIMARY KEY (student_id, section_id));
CREATE TABLE waitlist (section_id BIGINT, student_id BIGINT, position BIGSERIAL, status TEXT, -- waiting, offered, expired
offered_until TIMESTAMP, PRIMARY KEY (section_id, student_id));
CREATE TABLE prerequisites (course_id BIGINT, required_course_id BIGINT);
CREATE TABLE registration_windows (student_group TEXT, opens_at TIMESTAMP); -- seniors first, etc.
3) Register Flow (in one transaction)
- Checks (in memory or cached): the window is open for this student, prerequisites are met (from the transcript), there's no time conflict with current enrollments, and credits stay within the limit.
- Take a seat atomically:
UPDATE sections SET enrolled = enrolled + 1 WHERE section_id = ? AND enrolled < capacity.
- 1 row updated → insert the enrollment and commit. Success.
- 0 rows → the section is full, so offer to join the waitlist.
CHECK (enrolled <= capacity) constraint is a last safety net.
Drop: in one transaction, set the enrollment to dropped and do enrolled - 1. Then trigger waitlist promotion.
4) Waitlist Promotion
- When a seat frees up, take the first waiting student (lowest position).
- Either auto-enroll them (if their checks still pass: no new conflicts, credit limit) or offer the seat for 24 hours. If they don't accept in time, move to the next person.
- The promotion runs in a transaction that locks the section row, so two drops don't promote the same student twice, and the seat count stays right.
5) Surviving the Opening Spike
Architecture Diagram
flowchart LR
ST["Students"] --> VQ["Virtual waiting room - fair queue"]
VQ --> API["Registration API - stateless, scaled"]
API --> CACHE[("Cached catalog + seat counts - read only")]
API --> DB[("Primary DB - atomic seat updates")]
DB --> EV[("Events: seat freed")]
EV --> WL["Waitlist promoter"]
WL --> DB- Stagger windows by student group (seniors at 8:00, juniors at 9:00, ...). The simplest and most effective control.
- Virtual waiting room: admit students into the registration flow at a controlled rate, in fair (random or arrival) order, and show their position.
- Read vs write split: browsing uses cached catalog and approximate seat counts. Only the "register" action touches the primary DB's atomic update.
- Short transactions: validation happens before the transaction, and the transaction is just the conditional update + insert, which keeps row locks brief.
- Hot sections: many students update the same row. That's fine at this scale (thousands, not millions per second) with short transactions. Load test the exact peak beforehand.
- Idempotency: a double click doesn't double-enroll (the primary key is
(student, section)).
6) Wrap-Up
Model sections with capacity and an enrolled count protected by a CHECK constraint, and register with a single conditional update (enrolled < capacity) plus an enrollment insert in one short transaction, after validating prerequisites, conflicts and credits. Drops trigger fair, transactional waitlist promotion (auto-enroll or a timed offer). Survive the opening spike with staggered windows, a virtual waiting room, cached read-only browsing and idempotent registration.