CASE STUDY

University Course Registration System

3 min read·535 words·Intermediate

Asked at

1 candidate report in Jan 2026

How to use this case study

SDE-2 / Mid

Design the schema (courses, sections, enrollments, waitlists) and the register/drop APIs, with seat counts that never go negative.

SDE-3 / Senior

Handle the registration-opening spike, atomic seat decrements, fair waitlist promotion, prerequisites and schedule conflicts.

Staff / Principal

Discuss fairness (time slots per student group, virtual queues), strong consistency requirements, and load testing for the peak.


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)

  1. 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.
  2. 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.
3. The 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.

More Case Studies

Practice with a Mock Interview

Apply what you learned in a live system design mock interview with our AI interviewer.

Start System Design Interview →