INTERVIEW_QUESTIONS
Object-Oriented Design Interview Questions for Senior Engineers (2026)
Top object-oriented design interview questions with detailed answer frameworks covering SOLID principles, design patterns, domain modeling, and real-world OOD problems asked at FAANG companies.
Why Object-Oriented Design Matters in Senior Engineering Interviews
Object-oriented design interviews evaluate a senior engineer's ability to model complex real-world domains into clean, extensible, and maintainable software architectures. Unlike system design interviews that focus on distributed infrastructure, OOD interviews test your skill at decomposing problems into classes, defining clear interfaces, applying design patterns appropriately, and reasoning about coupling and cohesion at the code-architecture level.
Interviewers at companies like Google and Amazon use OOD rounds to assess whether you can lead technical design discussions, produce architectures that other engineers can understand and extend, and apply SOLID principles in practice rather than just reciting definitions. The ability to model a domain cleanly separates senior engineers from mid-level engineers who can write functional code but struggle with long-term maintainability.
A strong OOD answer demonstrates structured thinking: you identify entities and their responsibilities, define relationships and interactions, apply appropriate patterns without over-engineering, and communicate trade-offs clearly. For candidates targeting staff-level roles, the OOD round often overlaps with domain-driven design concepts where bounded contexts and aggregate roots become relevant.
This guide covers 15 commonly asked OOD questions with structured answer frameworks, follow-up questions interviewers use to probe deeper, and the common mistakes that cost candidates their offers. For a complete preparation strategy, see our system design interview guide and explore learning paths tailored to senior engineers.
1. Design a parking lot system
What the interviewer is really asking: Can you identify entities, define clear responsibilities, handle different vehicle types polymorphically, and reason about concurrency in a physical-world model?
Answer framework:
Begin by clarifying requirements: How many levels does the parking lot have? What vehicle types are supported (motorcycle, car, bus, truck)? Is there a payment system? Do we need real-time availability tracking? Are there reserved spots or handicapped spots?
Identify core entities: ParkingLot, Level, ParkingSpot, Vehicle (abstract), Car, Motorcycle, Bus, Ticket, PaymentProcessor. Apply the Single Responsibility Principle from SOLID so that each class has one reason to change. The ParkingLot should not know about payment logic, and the PaymentProcessor should not know about spot allocation.
For the vehicle hierarchy, use an abstract Vehicle class with properties like licensePlate, size, and entryTime. Concrete classes (Car, Motorcycle, Bus) define their size requirements. ParkingSpot has a SpotSize enum (COMPACT, REGULAR, LARGE) and a method canFitVehicle(Vehicle v) that compares sizes.
For spot allocation, apply the Strategy pattern. Define a ParkingStrategy interface with a method findSpot(Vehicle v, List
For concurrency, multiple vehicles arrive simultaneously. The spot allocation must be thread-safe. Use optimistic locking: attempt to mark a spot as occupied, if another thread already took it, retry with the next available spot. Alternatively, use a concurrent queue of available spots per size category.
Define clear interfaces: ParkingLot exposes parkVehicle(Vehicle) returning a Ticket, and unparkVehicle(Ticket) triggering payment calculation. The Ticket stores entry time, spot reference, and vehicle reference. PaymentProcessor calculates fees based on duration and vehicle type using a rate table.
Apply the Observer pattern for availability displays: when a spot's status changes, notify display boards on each level showing remaining capacity.
Follow-up questions:
- How would you extend the system to support electric vehicle charging spots?
- How would you handle a bus that needs multiple consecutive spots?
- What if you need to add valet parking where drivers drop off keys?
2. Design a library management system
What the interviewer is really asking: Can you model entities with complex relationships (books, copies, members, reservations), handle state machines (book checkout lifecycle), and design for extensibility?
Answer framework:
Clarify requirements: support book search by title/author/ISBN, checkout and return, reservations for unavailable books, overdue fine calculation, member account management, and librarian administrative functions.
Distinguish between a Book (the abstract concept with title, author, ISBN) and a BookCopy (a physical instance with a barcode, condition, and availability status). This separation follows domain-driven design principles where a Book is an entity identified by ISBN and BookCopy is an entity identified by its barcode.
Define the BookCopy state machine: AVAILABLE, CHECKED_OUT, RESERVED, LOST, UNDER_REPAIR. Use the State pattern where each state is a class implementing a BookCopyState interface with methods like checkout(), returnBook(), reserve(), and markLost(). Each state defines valid transitions and throws exceptions for invalid ones.
Model the Member class with a borrowing limit (5 books for regular, 10 for premium). Apply the Template Method pattern for fine calculation: define a base algorithm (days overdue times daily rate) but allow subclasses to override for different member tiers or book categories.
For the search functionality, apply the Specification pattern. Create search specifications like TitleSpecification, AuthorSpecification, and ISBNSpecification that implement a Specification
For notifications (overdue reminders, reservation available), apply the Observer pattern. The CheckoutRecord observes the system clock and triggers notifications when due dates approach. The Reservation observes BookCopy state changes and notifies the member when their reserved book becomes available.
Address the Librarian role with additional privileges: use role-based access control where operations check the actor's permissions before executing. Apply the Proxy pattern where a LibrarianProxy wraps the core library operations and adds permission checks.
Follow-up questions:
- How would you handle a reservation queue when multiple members want the same book?
- How would you extend the system to support digital books and audiobooks?
- How would you implement a recommendation feature based on borrowing history?
3. Design an elevator system
What the interviewer is really asking: Can you model concurrent state machines, implement scheduling algorithms, and handle the interaction between multiple independent actors (elevators) coordinated by a central controller?
Answer framework:
Clarify requirements: How many elevators? How many floors? Support for express elevators (skip certain floors)? Freight elevators? Emergency mode? Weight capacity enforcement?
Identify core entities: ElevatorSystem (the coordinator), Elevator, Floor, Request, Door, Display. Each Elevator is an independent state machine with states: IDLE, MOVING_UP, MOVING_DOWN, DOOR_OPEN. The ElevatorSystem is the controller that receives requests and dispatches them to elevators.
For the dispatching algorithm, apply the Strategy pattern. Define a DispatchStrategy interface with assignElevator(Request r, List
The Request class distinguishes between hallway requests (from a floor button, specifying direction) and cabin requests (from inside the elevator, specifying destination floor). The dispatcher handles these differently: hallway requests need to choose which elevator responds, cabin requests are already assigned to a specific elevator.
Apply the Command pattern for requests: each Request is a command object that can be queued, prioritized, and executed. The Elevator maintains a priority queue of pending requests sorted by the scheduling algorithm.
For the Elevator state machine, use the State pattern. In MOVING_UP state, the elevator checks at each floor whether any pending request matches the current floor. If yes, transition to DOOR_OPEN, serve the request, then check for remaining requests to determine the next state.
Address weight capacity: the Elevator has a maxWeight property and a currentWeight derived from a weight sensor. When overloaded, the door remains open and a notification sounds. The elevator does not accept new hallway requests when at capacity.
For emergency mode, apply the Chain of Responsibility pattern: emergency events propagate through a handler chain (stop elevator, open doors, activate alarm, notify building management).
Follow-up questions:
- How would you optimize for rush hour when everyone goes to the lobby?
- How would you handle an elevator going out of service for maintenance?
- How would you design for a building with sky lobbies and express zones?
4. Design a chess game
What the interviewer is really asking: Can you model complex rules with polymorphism, validate game state transitions, and handle the interplay between pieces with different movement patterns?
Answer framework:
Identify entities: Game, Board, Square, Piece (abstract), King, Queen, Rook, Bishop, Knight, Pawn, Player, Move, MoveValidator. The Board is an 8x8 grid of Squares. Each Square has a position (row, column) and an optional Piece reference.
For the Piece hierarchy, define an abstract Piece class with color, position, and an abstract method getValidMoves(Board board) that returns a list of valid destination squares. Each concrete piece implements its own movement logic. The Knight returns L-shaped moves, the Bishop returns diagonal lines until blocked, and so on.
The Pawn is the most complex piece: it moves forward one square, two squares from starting position, captures diagonally, supports en passant, and promotes upon reaching the opposite end. Apply the Special Case pattern for pawn promotion: when a pawn reaches the last rank, trigger a PromotionEvent that lets the player choose a replacement piece.
For move validation, layer multiple validators using the Chain of Responsibility pattern. BasicMoveValidator checks if the piece can physically reach the target square. PathValidator checks if the path is unobstructed (for sliding pieces). CheckValidator verifies that the move does not leave the player's own king in check. CastlingValidator handles the special king-rook move with its multiple preconditions.
The Game class manages the game state machine: IN_PROGRESS, CHECK, CHECKMATE, STALEMATE, DRAW_BY_REPETITION, DRAW_BY_FIFTY_MOVE_RULE. After each move, evaluate the board state to detect these conditions. Check detection: enumerate all opponent piece moves and see if any can capture the king.
Apply the Memento pattern for undo/move history: each Move stores enough information to reverse it (source square, destination square, captured piece if any, special flags for castling and en passant). The Game maintains a move stack enabling undo.
For notation, apply the Adapter pattern to convert internal Move objects to standard algebraic notation (e4, Nf3, O-O) for display and export.
Follow-up questions:
- How would you implement a timer system for blitz and rapid games?
- How would you add an AI opponent with different difficulty levels?
- How would you extend to support Chess960 (Fischer Random) where the starting position varies?
5. Design a hotel booking system
What the interviewer is really asking: Can you handle temporal data (date ranges), prevent double-booking, model pricing complexity, and design for concurrent reservation attempts?
Answer framework:
Clarify requirements: room types (single, double, suite), date-range bookings, pricing that varies by season and demand, cancellation policies, guest profiles, and payment integration.
Core entities: Hotel, Room, RoomType, Reservation, Guest, Payment, DateRange, PricingRule. Separate Room (the physical room with a number and floor) from RoomType (category with base price and amenities). A Reservation links a Guest to a Room for a DateRange.
The critical challenge is preventing double-booking. For a given date range, a room can only have one reservation. Model this with a ReservationRepository that checks availability before confirming. Use the Unit of Work pattern: collect all changes (room allocation, payment charge, confirmation email) and commit atomically.
For the search and availability check, define an AvailabilityService that queries reservations overlapping with the requested date range. A date range [checkIn, checkOut) overlaps with existing start, end) if checkIn < end AND checkOut > start. Index reservations by room and date for efficient queries. [blocked]
For pricing, apply the Strategy pattern combined with the Decorator pattern. Base pricing comes from the RoomType. Layer decorators for seasonal adjustment (winter rates, holiday surcharges), demand-based dynamic pricing, loyalty member discounts, and promotional codes. Each decorator wraps the previous pricing calculation. This follows the Open-Closed Principle since new pricing rules are added as new decorators without modifying existing ones.
For the cancellation policy, use the Strategy pattern: FreeCancellation (full refund before 48 hours), ModeratePolicy (50 percent refund before 24 hours), StrictPolicy (no refund within 7 days). Assign policies at the RoomType or rate level.
For concurrent booking attempts on the same room, use optimistic locking. When confirming a reservation, check that the room is still available. If another thread booked it first, offer alternative rooms. Apply the Reservation pattern from domain-driven design: create a temporary hold (5-10 minutes) while the guest completes payment.
Apply the Observer pattern for notifications: when a reservation state changes (CONFIRMED, CANCELLED, CHECKED_IN, CHECKED_OUT), notify relevant parties (guest email, housekeeping, billing).
Follow-up questions:
- How would you handle overbooking as a business strategy with compensation logic?
- How would you extend to support multi-room group bookings?
- How would you implement a waitlist for fully booked dates?
6. Design a file system
What the interviewer is really asking: Can you apply the Composite pattern for tree structures, handle permissions hierarchically, and design for extensibility with different file types and storage backends?
Answer framework:
The file system is a tree structure where directories can contain files and other directories. This is the canonical use case for the Composite pattern. Define an abstract FileSystemEntry with properties: name, size, createdAt, modifiedAt, permissions, parent. Directory extends FileSystemEntry and contains a list of FileSystemEntry children. File extends FileSystemEntry and holds content or a reference to content storage.
Apply the Composite pattern: both File and Directory implement a common interface with operations like getSize(), getPath(), delete(), and moveTo(Directory target). For Directory, getSize() recursively sums children sizes. This allows clients to treat files and directories uniformly.
For permissions, model with a Permission class containing read, write, and execute flags for owner, group, and others (Unix-style). Apply the Chain of Responsibility pattern for permission checking: first check explicit ACL entries, then group permissions, then default permissions. Permissions can be inherited from parent directories using the Prototype pattern where a new file clones its parent directory's default permissions.
For different file types, apply the Visitor pattern. Define a FileSystemVisitor interface with visitFile(File f) and visitDirectory(Directory d). Implement SearchVisitor (find files matching criteria), SizeCalculatorVisitor (compute total size), and BackupVisitor (copy to external storage). This separates algorithms from the structure, following SOLID principles.
For the storage layer, apply the Bridge pattern to separate the file system abstraction from the storage implementation. The same FileSystem interface can work with LocalDiskStorage, NetworkStorage, or CloudStorage. This allows transparent access to files regardless of where they are physically stored.
Address symbolic links and hard links: a SymbolicLink is a special FileSystemEntry that holds a path reference to another entry. Use the Proxy pattern where accessing a SymbolicLink transparently redirects to the target.
For concurrency, file locks prevent corruption when multiple processes access the same file. Implement with read-write locks: multiple readers can access simultaneously, but writers need exclusive access.
Follow-up questions:
- How would you implement a recycle bin with restore functionality?
- How would you add file versioning where previous versions are accessible?
- How would you design a file watcher that notifies subscribers of changes?
7. Design a social media feed system (OOD perspective)
What the interviewer is really asking: Can you model the complex relationships between users, posts, interactions, and feeds while keeping the design extensible for new content types and interaction models?
Answer framework:
Note: this focuses on the object model and class design, not the distributed system architecture. For the system design perspective, see our system design interview guide.
Core entities: User, Post (abstract), TextPost, ImagePost, VideoPost, Comment, Reaction, Feed, FeedItem, Notification, Relationship (follow/friend).
For the content type hierarchy, apply the principle of composition over inheritance. Rather than deep class hierarchies, use a Content interface that Post contains. Content implementations: TextContent, ImageContent, VideoContent, PollContent. A Post can have multiple Content items (a post with text and images). This is more flexible than a rigid class hierarchy and follows SOLID principles.
For the Feed, apply the Iterator pattern. A Feed provides a FeedIterator that lazily loads and returns FeedItems. The FeedItem wraps a Post with additional metadata: reason for showing (friend posted, friend liked, suggested), relevance score, and display hints. This separates feed generation logic from feed consumption.
For reactions and interactions, apply the Observer pattern and the Mediator pattern. When a user reacts to a post, the interaction creates a Notification for the post author. Use an InteractionMediator that coordinates between the actor, the target post, and all interested parties (post author, other commenters, tagged users).
For content moderation, apply the Chain of Responsibility pattern. When a Post is created, it passes through a moderation pipeline: SpamFilter, ProfanityFilter, ImageSafetyCheck, CommunityGuidelinesCheck. Each handler either passes the content forward or flags it for review. New moderation rules are added as new chain links.
For the Relationship model, distinguish between symmetric (friend) and asymmetric (follow) relationships. Use the State pattern for friend requests: PENDING, ACCEPTED, REJECTED, BLOCKED. The relationship state determines what content is visible.
For privacy, apply the Proxy pattern: a PrivacyProxy wraps Post access and checks visibility rules (public, friends-only, specific lists) before returning content.
Follow-up questions:
- How would you add support for Stories (ephemeral content that expires)?
- How would you model groups and group-specific content visibility?
- How would you implement content sharing and reposting with attribution?
8. Design an online shopping cart system
What the interviewer is really asking: Can you model a stateful system with complex pricing rules, handle concurrent modifications, and design for extensibility with promotions and discounts?
Answer framework:
Core entities: ShoppingCart, CartItem, Product, PricingEngine, Discount (abstract), Coupon, Order, ShippingCalculator, TaxCalculator.
The ShoppingCart maintains a list of CartItems (product reference, quantity, unit price at time of addition). Apply the Value Object pattern for Money: never use raw floats for currency. Money has amount and currency, with proper arithmetic operations that handle rounding.
For pricing, apply the Strategy pattern combined with the Decorator pattern for layered discounts. The PricingEngine takes a Cart and applies pricing rules in order: base product prices, quantity discounts (buy 3 get 10 percent off), coupon codes, loyalty points redemption, and shipping promotions. Each rule is a PricingRule implementation with an apply(Cart) method.
For the discount system, define a Discount abstract class with: isApplicable(Cart), calculateDiscount(Cart), and priority. Use a DiscountCombinationPolicy to define how multiple discounts interact: BEST_SINGLE (only the best discount applies), STACKABLE (all applicable discounts apply in sequence), or EXCLUSIVE (certain discounts cannot combine). This is a common real-world requirement that naive implementations miss.
Apply the Observer pattern for cart state changes: when items are added/removed/quantity changed, recalculate totals, update inventory holds, refresh shipping estimates, and re-evaluate applicable promotions. The Cart publishes CartChangedEvents to interested subscribers.
For the checkout flow, apply the Builder pattern. The CheckoutBuilder collects shipping address, payment method, applies final validations, and constructs an Order. The Order is immutable once created, representing the finalized transaction.
For persistence, the cart must survive across sessions. Apply the Memento pattern: serialize cart state to storage (database or cookie), restore on next session. Handle conflicts: if a product's price changed or went out of stock since the cart was saved, notify the user.
Address concurrency: two browser tabs modifying the same cart. Use versioning where each modification includes the expected version number. If versions conflict, merge or notify the user.
Follow-up questions:
- How would you implement a wishlist with move-to-cart functionality?
- How would you handle bundle pricing where buying items together is cheaper?
- How would you design the system to support A/B testing different checkout flows?
9. Design a deck of cards for multiple card games
What the interviewer is really asking: Can you design a flexible abstraction that supports multiple games (Poker, Blackjack, Bridge) without being tightly coupled to any single game's rules?
Answer framework:
This is an exercise in abstraction. The challenge is designing a card system flexible enough for any card game while providing enough structure to be useful.
Core entities: Card, Deck, Hand, Suit, Rank, Dealer, Game (abstract), Player. A Card is a value object defined by its Suit and Rank. Make Card immutable since a card's identity never changes. Override equals and hashCode based on suit and rank.
For the Deck, use the Factory pattern. A StandardDeckFactory creates a 52-card deck. A PinochleDeckFactory creates a 48-card deck (9 through Ace, doubled). A TarotDeckFactory includes major arcana. This separates deck creation from deck usage.
The Deck class exposes shuffle(), draw(), drawMultiple(int n), peek(), and remaining(). Apply the Iterator pattern for drawing cards. The shuffle algorithm should use Fisher-Yates for uniform randomness. Consider the Strategy pattern for different shuffling approaches (riffle, overhand) if simulating physical shuffles.
For Hand, make it a generic container that holds cards for a player. Different games evaluate hands differently. Apply the Strategy pattern with a HandEvaluator interface. PokerHandEvaluator knows the ranking (royal flush > straight flush > ...). BlackjackHandEvaluator sums card values with ace being 1 or 11. BridgeHandEvaluator counts high-card points.
The abstract Game class defines the template method pattern: dealCards(), playRound(), evaluateWinner(), resetForNextRound(). Concrete games (PokerGame, BlackjackGame) implement these with their specific rules. This allows shared infrastructure (player management, betting, card dealing) while varying game-specific logic.
For game rules validation, use the Specification pattern. Define rule specifications: MaxCardsInHandSpec, ValidPlaySpec (is it legal to play this card given game state), and TurnOrderSpec. Compose rules to define complete game legality checks.
Apply the Observer pattern for game events: CardDealt, CardPlayed, RoundEnded, GameWon. UI components observe these events to update displays without coupling to game logic.
Follow-up questions:
- How would you add support for wild cards and jokers?
- How would you extend to support games with multiple decks (like Casino Blackjack with 6 decks)?
- How would you design an AI player that can play any supported card game?
10. Design a vending machine
What the interviewer is really asking: Can you model a state machine clearly, handle money/payment correctly, and deal with edge cases like insufficient change and out-of-stock items?
Answer framework:
This is a classic state machine problem. The vending machine has states: IDLE, ACCEPTING_MONEY, DISPENSING, RETURNING_CHANGE, OUT_OF_SERVICE. Apply the State pattern where each state is a class that handles inputs differently.
Core entities: VendingMachine, State (interface), Product, Slot, MoneyHandler, Inventory, Display. The VendingMachine delegates all behavior to its current State object.
Define the State interface with methods: insertMoney(Denomination d), selectProduct(SlotId s), cancelTransaction(), and dispense(). In IDLE state, insertMoney transitions to ACCEPTING_MONEY. In ACCEPTING_MONEY state, selectProduct checks if enough money is inserted and transitions to DISPENSING. Each state only allows valid operations and throws or ignores invalid ones.
For the MoneyHandler, use the Chain of Responsibility pattern for coin/bill validation: each denomination handler checks if it recognizes the inserted money and updates the balance. The MoneyHandler also calculates change using a greedy algorithm: dispense the largest coins first. If exact change cannot be made, display a warning before purchase.
The Inventory manages stock per slot. Apply the Observer pattern: when a slot reaches zero, update the display and disable that selection. When all slots are empty, transition to OUT_OF_SERVICE.
For Product pricing, apply the Strategy pattern. Standard pricing is fixed per product. Dynamic pricing could adjust based on time of day, remaining inventory, or demand. The VendingMachine holds a PricingStrategy reference that can be swapped.
Address concurrency: what if two bills are inserted rapidly? The state machine must be thread-safe. Use synchronized state transitions or a single-threaded event loop that processes inputs sequentially.
Apply the Command pattern for the hardware interface: DispenseCommand, ReturnChangeCommand, DisplayMessageCommand. This decouples the logical state machine from physical hardware control.
Follow-up questions:
- How would you add support for card payments and mobile payments?
- How would you handle the machine running low on change coins?
- How would you design remote monitoring for a fleet of vending machines?
11. Design a notification system
What the interviewer is really asking: Can you model multiple delivery channels, handle user preferences, implement priority and throttling, and design for extensibility with new notification types?
Answer framework:
Clarify requirements: multiple channels (email, SMS, push notification, in-app), user preferences per channel and per notification type, batching/digests, priority levels, rate limiting, and delivery tracking.
Core entities: Notification (abstract), NotificationChannel (interface), UserPreferences, DeliveryRecord, NotificationTemplate, ThrottlePolicy.
Apply the Bridge pattern to separate notification content from delivery channel. A Notification has content (title, body, actions) independent of how it is delivered. A NotificationChannel (EmailChannel, SMSChannel, PushChannel) knows how to format and send for its medium. This means adding a new channel (say, Slack) does not require modifying existing notification types.
For notification types, use the Template Method pattern. Define notification templates that specify content slots. An OrderShippedNotification fills in order details, tracking number, and estimated delivery. Each template defines which channels it supports and its default priority.
For user preferences, apply the Specification pattern. UserPreferences define rules: enable email for order updates, disable SMS for marketing, enable push for urgent only. When a notification is generated, evaluate it against preferences to determine which channels to use. Apply the Null Object pattern: if a user has not set preferences, use sensible defaults rather than null checks everywhere.
For throttling, apply the Decorator pattern. Wrap each channel with a ThrottleDecorator that enforces rate limits: max 3 push notifications per hour, max 1 SMS per day for marketing. The BatchingDecorator accumulates non-urgent notifications and delivers them as a digest at configurable intervals.
Priority queue: notifications are not all equal. Define priority levels: CRITICAL (security alerts, delivered immediately), HIGH (order updates, delivered within minutes), MEDIUM (social interactions, can be batched), LOW (marketing, heavily throttled). Use a priority queue in the dispatch pipeline.
Apply the Observer pattern for delivery tracking: when a notification is sent, record the attempt. Track delivery, open, and click events. This data feeds back into optimization (if a user never opens emails but always reads push, prefer push).
Follow-up questions:
- How would you implement notification scheduling with timezone awareness?
- How would you handle notification templates with personalization and localization?
- How would you design an unsubscribe system that complies with regulations?
12. Design a movie ticket booking system
What the interviewer is really asking: Can you handle seat selection concurrency, model temporal availability (showtimes), and design a booking flow that prevents double-selling while providing a good user experience?
Answer framework:
Core entities: Movie, Theater, Screen, Showtime, Seat, Booking, User, Payment, SeatMap. A Theater has multiple Screens. Each Screen has a fixed SeatMap (rows and columns of Seats with types: STANDARD, PREMIUM, VIP). A Showtime links a Movie to a Screen at a specific datetime.
The critical design challenge is seat selection concurrency. When User A is choosing seats, User B should not be able to select the same seats. Apply the Reservation pattern: when a user selects seats, create a temporary SeatHold with a TTL (8 minutes). The seat appears occupied to other users. If payment is not completed within TTL, the hold expires and seats become available again.
Model Seat availability as a per-showtime concern. Create a ShowtimeSeatStatus entity that tracks status per seat per showtime: AVAILABLE, HELD, BOOKED. This separates the physical seat definition from its temporal availability.
For the booking flow, apply the Builder pattern. BookingBuilder collects: showtime, selected seats, user details, concession add-ons, and payment method. Validation occurs at build time: verify seats are still available, calculate total price, and create the Booking atomically.
For pricing, apply the Strategy pattern. Base price comes from the seat type. Apply decorators for: weekend surcharge, 3D surcharge, peak hour pricing, membership discounts, and bulk booking discounts (4 tickets get 10 percent off). The PricingEngine composes these rules.
Apply the State pattern for Booking lifecycle: INITIATED, SEATS_HELD, PAYMENT_PENDING, CONFIRMED, CANCELLED, COMPLETED (after the showtime passes). Each state defines valid transitions and actions.
For the theater layout display, apply the Flyweight pattern. Seat objects in the map share common properties (type, price tier) through flyweight references, reducing memory when rendering a 500-seat theater multiple times for different showtimes.
Apply the Observer pattern: when a booking is confirmed, notify the user (email confirmation), update available seat count for the showtime, and generate the digital ticket with a QR code.
Follow-up questions:
- How would you handle group bookings where friends want to sit together?
- How would you implement a waitlist for sold-out showtimes?
- How would you design seat recommendations based on user preferences?
13. Design a ride-sharing matching system (OOD perspective)
What the interviewer is really asking: Can you model the matching algorithm cleanly, handle the lifecycle of ride requests, and design for multiple matching strategies?
Answer framework:
Focus on the object model for matching riders with drivers, not the distributed system. Core entities: Rider, Driver, RideRequest, Match, Trip, Location, Vehicle, MatchingEngine, PricingEngine.
The RideRequest contains: rider, pickup location, dropoff location, requested ride type (economy, premium, shared), and creation time. Apply the Builder pattern for construction since requests have many optional parameters (accessibility needs, child seat, quiet ride preference).
The Driver entity has: current location, vehicle, status (AVAILABLE, EN_ROUTE_TO_PICKUP, ON_TRIP, OFFLINE), rating, and acceptance rate. Vehicle has type, capacity, and features (wheelchair accessible, car seat).
For the MatchingEngine, apply the Strategy pattern. Define a MatchingStrategy interface with findBestDriver(RideRequest request, List
For filtering candidates, apply the Specification pattern from domain-driven design. Compose specifications: AvailableDriverSpec AND WithinRadiusSpec AND VehicleTypeMatchSpec AND AccessibilitySpec. This cleanly separates filtering logic from matching logic.
Model the Match as a state machine: OFFERED (sent to driver), ACCEPTED, DECLINED, EXPIRED (no response within timeout). If declined or expired, the MatchingEngine tries the next best candidate. Apply the State pattern for clean transitions.
For pricing, use the Observer pattern where the PricingEngine monitors supply and demand per geographic zone. When demand exceeds supply, calculate a surge multiplier. Apply the Decorator pattern to layer pricing: base fare + distance rate + time rate + surge multiplier + booking fee - promotional discount.
The Trip entity tracks the ride lifecycle: DRIVER_EN_ROUTE, ARRIVED_AT_PICKUP, TRIP_IN_PROGRESS, COMPLETED, CANCELLED. Apply the Observer pattern to notify both rider and driver of state changes, trigger ETA recalculations, and initiate payment upon completion.
For ride sharing (pool), apply the Composite pattern: a SharedTrip contains multiple RideRequests that are compatible (similar routes). The MatchingEngine needs a PoolOptimizer that evaluates route compatibility and detour acceptability.
Follow-up questions:
- How would you handle priority matching for premium subscribers?
- How would you design the system to learn from driver acceptance and decline patterns?
- How would you model scheduled rides that are requested in advance?
14. Design an ATM system
What the interviewer is really asking: Can you model a transaction system with multiple operation types, handle the state machine of an ATM session, and ensure financial correctness with proper error handling?
Answer framework:
Core entities: ATM, CardReader, CashDispenser, Screen, Keypad, Account, Transaction (abstract), WithdrawalTransaction, DepositTransaction, BalanceInquiry, TransferTransaction, Session, Bank (interface).
The ATM communicates with a Bank interface for account operations. Apply the Facade pattern: the ATM exposes a simple interface to users while coordinating complex interactions between hardware components and the bank network. Apply SOLID principles by depending on the Bank interface, not a concrete implementation. This allows the ATM to work with any bank's backend.
For the session state machine, apply the State pattern. States: IDLE (showing welcome screen), CARD_INSERTED (reading card), AUTHENTICATING (PIN entry, max 3 attempts), AUTHENTICATED (showing menu), PROCESSING_TRANSACTION, EJECTING_CARD. Each state handles user inputs differently and defines valid transitions.
For transactions, use the Template Method pattern. Abstract Transaction defines the algorithm: validate(), execute(), generateReceipt(). WithdrawalTransaction.validate() checks balance and daily limit. WithdrawalTransaction.execute() debits the account and signals the CashDispenser. Each transaction type fills in its specific steps.
Apply the Command pattern for user actions: WithdrawCommand, DepositCommand, CheckBalanceCommand, TransferCommand. This decouples the menu UI from transaction execution and enables operation logging.
For the CashDispenser, model the denomination inventory: the ATM has specific quantities of each bill denomination (twenties, fifties, hundreds). Apply a greedy algorithm with fallback for dispensing: try to minimize the number of bills, but if a denomination runs low, prefer preserving variety. Track remaining inventory and report to the bank when low.
Error handling is critical in financial systems. Apply the Strategy pattern for error handling: NetworkTimeoutStrategy (retry with exponential backoff, then eject card), InsufficientCashStrategy (offer alternative amount), CardRetainedStrategy (three failed PINs, capture the card). Each error scenario has a defined recovery path.
For the audit trail, apply the Observer pattern. Every action (card inserted, PIN attempt, transaction executed, cash dispensed) generates an AuditEvent that is logged persistently. This provides a complete forensic trail for dispute resolution.
Follow-up questions:
- How would you handle a power failure mid-transaction?
- How would you design for cardless transactions using mobile phones?
- How would you implement daily withdrawal limits across multiple ATMs?
15. Design a task management system like Jira
What the interviewer is really asking: Can you model complex workflows with customizable states, handle relationships between entities (epics, stories, subtasks), and design for extensibility with custom fields and automations?
Answer framework:
Core entities: Project, Board, Issue (abstract), Epic, Story, Task, Bug, Sprint, User, Workflow, WorkflowState, Transition, Comment, Attachment, CustomField.
For the Issue hierarchy, use composition over inheritance. All issue types share a base: title, description, assignee, reporter, priority, labels, and custom fields. Rather than subclasses for Epic/Story/Bug, use an IssueType enum with type-specific behavior handled by the Strategy pattern. This allows creating new issue types without code changes.
For workflows, apply the State pattern combined with the Builder pattern for workflow definition. A Workflow defines states (TODO, IN_PROGRESS, IN_REVIEW, DONE) and valid Transitions between them. Each Transition can have: conditions (only assignee can move to IN_PROGRESS), validators (require a comment when rejecting), and post-functions (notify watchers, update sprint burndown). This mirrors domain-driven design aggregate behavior.
For the parent-child relationship (Epic contains Stories, Story contains Subtasks), apply the Composite pattern. An Epic's progress is calculated from its children's statuses. When all children are DONE, suggest transitioning the parent.
For custom fields, apply the Type Object pattern. Define FieldDefinition (name, type, validation rules) and FieldValue (the actual value for a specific issue). Support types: TEXT, NUMBER, DATE, SINGLE_SELECT, MULTI_SELECT, USER_REFERENCE. This allows project admins to add fields without developer involvement.
For search and filtering, apply the Specification pattern. Combine specifications: AssigneeSpec AND StatusSpec AND PrioritySpec AND LabelSpec AND DateRangeSpec. Support saving filter combinations as named views.
For automations, apply the Rule Engine pattern: define triggers (Issue moved to DONE, Sprint started, Due date approaching), conditions (Issue type is Bug, Priority is Critical), and actions (Send notification, Update field, Create subtask). This is essentially an event-driven system where automations observe issue lifecycle events.
Apply the Observer pattern broadly: watchers on issues receive notifications on changes, sprint boards update in real-time, and dashboards refresh metrics.
For the board view (Kanban/Scrum), apply the Model-View-Controller pattern. The Board model defines columns mapped to workflow states. The view renders issues as cards. Drag-and-drop triggers workflow transitions through the controller, which validates the transition and updates state.
Follow-up questions:
- How would you implement time tracking with estimated vs actual hours?
- How would you design cross-project dependencies?
- How would you handle bulk operations on hundreds of issues efficiently?
Common Mistakes in Object-Oriented Design Interviews
-
Starting with code instead of requirements. Many candidates immediately start writing class definitions without understanding what the system needs to do. Spend the first 3-5 minutes clarifying scope, identifying actors, and listing key use cases. This grounds your design in actual requirements rather than assumptions.
-
Over-applying design patterns. Using a pattern where a simple solution suffices makes the design harder to understand. Patterns solve specific problems. If you don't have that problem, don't use that pattern. Interviewers notice when candidates force patterns to show knowledge rather than solve problems.
-
Creating deep inheritance hierarchies. Prefer composition over inheritance. Deep hierarchies are rigid, hard to understand, and violate the Liskov Substitution Principle when subclasses don't truly represent IS-A relationships. Use interfaces and composition to achieve polymorphism.
-
Ignoring concurrency concerns. Senior engineers must address what happens when multiple threads or users interact with the system simultaneously. Failing to discuss thread safety for shared state, race conditions in booking systems, or concurrent modifications to shared resources is a major gap.
-
Not discussing trade-offs in your design decisions. Every design choice has pros and cons. When you choose the Strategy pattern over simple conditionals, acknowledge the added complexity and explain why it is justified by the extensibility requirement. When you choose composition over inheritance, explain the flexibility gain versus the slight indirection cost.
How to Prepare for OOD Interviews
Start by deeply internalizing SOLID principles. Do not just memorize definitions. Practice identifying violations in existing codebases and refactoring them. Understanding why these principles exist (managing change, reducing coupling) is more important than reciting acronyms.
Study the Gang of Four design patterns but focus on the 10-12 most commonly applicable: Strategy, Observer, Factory, Builder, State, Decorator, Composite, Command, Template Method, Adapter, and Proxy. For each pattern, understand the problem it solves, when NOT to use it, and real-world examples.
Practice modeling real-world systems. Pick any system you use daily (a music player, a food delivery app, a calendar) and design its class structure. Focus on identifying entities, defining responsibilities, and establishing relationships. Time yourself to 30 minutes per problem.
Study domain-driven design concepts for staff-level preparation. Bounded contexts, aggregates, value objects, and domain events provide a vocabulary for discussing complex domain models at a higher level of abstraction.
Practice communicating your design process. In the interview, thinking aloud is crucial. Explain why you chose certain abstractions, what alternatives you considered, and what trade-offs you made. Interviewers evaluate your reasoning process as much as your final design.
For comprehensive preparation across all interview types, explore the learning paths and consider structured preparation through our pricing plans that provide access to detailed walkthroughs of dozens of OOD problems.
Related Resources
GO DEEPER
Master this topic in our 12-week cohort
Our Advanced System Design cohort covers this and 11 other deep-dive topics with live sessions, assignments, and expert feedback.