Knowledge Guide
HomeOO & Low-Level Design

OO Design Problems

Step 8 in the OO & Low-Level Design path · 16 concepts · 0 problems

0 / 16 complete

📘 Learn OO Design Problems from zero

Low-level design rewards a calm, repeatable process far more than memorizing patterns. Here is the expert loop:

  1. Read & restate. Say back what the system does in one sentence and confirm scope with the interviewer. "Design a parking lot" could mean a single garage or a multi-city network — pin it down before drawing anything.
  2. Clarify constraints & requirements. Enumerate the core functional use cases (book a ticket, park a car, post an answer) and explicitly mark non-goals. List the actors and the 3-5 must-have flows. This becomes your checklist and bounds the problem.
  3. Brute-force the model first. Extract nouns as candidate classes and verbs as candidate methods. Write the obvious objects with plain fields and the relationships between them, even if naive. A working object model beats a clever empty one.
  4. Spot the pattern. Re-read your model for the signals above: states & transitions, reactions to events, interchangeable behaviors, varied construction, undoable requests. Apply a pattern only where it removes real if/else sprawl or absorbs anticipated change — patterns are a response to variation, not decoration.
  5. Apply the SOLID principles. Push for single responsibility, composition over inheritance, and programming to interfaces (dependency inversion). Draw the class diagram with explicit relationships — association, aggregation, composition — and the cardinalities.
  6. Edge cases & concurrency. Double-booking, payment failure, capacity overflow, invalid state transitions, and thread-safety on shared managers. Name the concrete mechanism (lock, atomic counter, version check), not just "handle concurrency."
  7. Test by walking a use case. Trace one end-to-end flow through your classes aloud to prove the API hangs together and the methods compose.

Stay narrative — think out loud so the interviewer follows your reasoning. They are scoring your judgment and how you decompose responsibility, not a pixel-perfect UML.

✨ Added by the guide to build intuition — not from the source course.

🎯 Guided practice

Worked example: Design a Movie Ticket Booking System.

  1. Restate & scope. "Users browse movies and showtimes across cinemas, select seats, and pay to book a ticket." Confirm: single city vs. chain, assigned seats vs. general admission, refunds in scope. Assume a chain with assigned seats.
  2. Nouns → classes. Movie, Cinema, CinemaHall (screen), Show, Seat (physical), ShowSeat (a seat bound to one specific show — this is where availability and price live), Booking, Payment, User. Verbs → methods: searchMovies(), getAvailableSeats(), book(), pay(), cancel().
  3. Signal: the booking has states and transitions — Requested → Pending (seats held) → Confirmed → Canceled/Expired. That signal points to a State pattern (or an explicit state machine on Booking) so each transition is validated in one place, not scattered across booleans.
  4. Signal: payment is one of several interchangeable behaviors (credit card, wallet, UPI). That points to a Strategy pattern: a PaymentStrategy interface with concrete implementations chosen at runtime.
  5. The hard signal — concurrency. Two users grabbing the same seat is the crux of this problem. The standard technique is a short-lived seat hold on each ShowSeat with a timeout — e.g., a SeatLockProvider that places a pessimistic lock (or writes a hold row with a 5-minute TTL) before payment, so a second buyer sees the seat as unavailable. Then enforce the commit with a single atomic conditional update on the ShowSeat row (the optimistic-locking version check) so exactly one Booking can flip it to Booked even if two reach the database. Make the payment step idempotent (idempotency key) so a retry never double-charges, and release the hold on timeout.
  6. Signal: notify on events (booking confirmed → email/SMS) → Observer pattern: subscribers register on the booking lifecycle.
  7. Construction & wiring: a BookingService orchestrates the flow; use a Factory for Payment/PaymentStrategy creation, and keep collaborators injected (avoid a hard Singleton) for testability.

Optimal shape: a BookingService that (1) holds the chosen ShowSeats via the lock provider, (2) drives Booking through its state machine, (3) delegates charging to a PaymentStrategy idempotently, (4) on success commits the seats with the atomic version check and notifies observers, and on timeout or failure releases the holds. The teachable insight: the seat-contention signal — not the class list — is what drives the whole design.

✨ Added by the guide — work these before the full problem set.

Lessons in this topic