Interview conceptSoftware Engineering Fundamentals

Object-Oriented Design And Concurrency-Safe LLD

Asked of: Software Engineer

Last updated

What's being tested

Interviewers are probing whether you can turn an ambiguous product workflow into a clean object-oriented design, then make it safe under concurrent access. For a Software Engineer, this means more than naming classes: you must define responsibilities, state transitions, invariants, APIs, persistence boundaries, and failure behavior. Amazon cares because many production systems look “simple” at the feature level—tasks, carts, orders, reservations—but fail when multiple users, services, retries, or devices act on the same entity at once. A strong answer shows practical judgment: simple in-memory design when appropriate, database-backed consistency when needed, and extensibility without over-engineering.

Core knowledge

  • Domain modeling starts with nouns, verbs, and invariants. For a task system: Task, User, Project, Comment, Assignee; for a restaurant: Table, Reservation, Order, MenuItem, Bill. The important part is not the nouns themselves, but who owns state and which transitions are legal.

  • Single Responsibility Principle means each class should have one reason to change. Order can own order-line state, but pricing rules often belong in PricingService or PriceCalculator; payment authorization belongs behind a PaymentProcessor interface, not inside Order.

  • State machines are essential for workflows. Model states explicitly: CREATED -> CONFIRMED -> PREPARING -> READY -> COMPLETED, or OPEN -> ASSIGNED -> IN_PROGRESS -> DONE. Define invalid transitions and enforce them centrally, preferably through methods like order.markReady() rather than arbitrary field mutation.

  • Invariants are the rules that must always hold, even under concurrency. Examples: inventory cannot go below zero, one table cannot have two active reservations for the same time slot, a task cannot be assigned to a deleted user, and an order cannot be paid twice. State these aloud before choosing locks or transactions.

  • API design should expose use cases, not database tables. Prefer operations like POST /orders/{id}/items, POST /tasks/{id}/assign, or POST /reservations/{id}/cancel over generic PATCH for every field when transitions have business rules. Include idempotency for retryable operations such as checkout.

  • Composition over inheritance is usually better for extensibility. A pizza order should compose Crust, Size, Topping, and PricingRule; avoid a class explosion like LargeThinCrustPepperoniPizza. Use Strategy pattern for pricing, discounts, tax, or payment providers.

  • Optimistic concurrency control works well when conflicts are rare. Add a version column and update with WHERE id = ? AND version = ?; if zero rows are updated, reload and retry or return a conflict. This is common for task edits, cart updates, and profile-like entities.

  • Pessimistic locking is appropriate when conflicts are likely or consequences are severe. In Postgres, SELECT ... FOR UPDATE can lock an inventory row during purchase. Keep transactions short; never hold a database lock while calling an external payment provider.

  • Idempotency keys protect against duplicate client retries. For checkout, accept an Idempotency-Key header and store the request key with the resulting Order or payment attempt. If the same key is retried, return the original result instead of charging twice.

  • Consistency boundaries should be explicit. A single aggregate, such as Order plus OrderItems, can often be updated transactionally. Cross-aggregate workflows—inventory reservation, payment, fulfillment—may need sagas, status fields, compensating actions, and reconciliation jobs rather than one giant transaction.

  • Thread safety matters for in-memory components. Shared mutable structures like Map<TaskId, Task> require synchronization, ConcurrentHashMap, immutable value objects, or actor-style ownership. Remember that ConcurrentHashMap makes individual map operations safe, not multi-step invariants like “check then insert if capacity remains.”

  • Event ordering matters in input systems and workflow systems. Keyboard/mouse events need monotonic sequence numbers, timestamps, and deterministic replay; order systems need append-only history for debugging. A log like Event(seq, deviceId, type, payload, timestamp) helps reproduce bugs and reason about causality.

Worked example

For Design an OOD restaurant management system, a strong candidate would first clarify scope: “Are we designing for dine-in only, or also delivery? Do we need reservations, table assignment, ordering, kitchen status, billing, and payment? Is this single restaurant or multi-location?” Then they would declare assumptions: single location, multiple staff using terminals concurrently, and core flows covering reservation, seating, order placement, kitchen fulfillment, bill splitting, and payment.

The answer skeleton should have four pillars: domain model, state transitions, service/API layer, and concurrency controls. The domain model might include Restaurant, Table, Reservation, Party, Order, OrderItem, MenuItem, KitchenTicket, Bill, and Payment. State transitions matter: a Reservation moves from REQUESTED to CONFIRMED to SEATED or CANCELLED; an OrderItem moves from PLACED to IN_PREP to SERVED.

The candidate should explicitly separate responsibilities: TableManager assigns tables, OrderService mutates orders, KitchenService manages tickets, BillingService computes totals, and PaymentService integrates with a payment processor. A concrete tradeoff to flag is reservation concurrency: two hosts might try to assign the same table for overlapping times, so table assignment should happen inside a transaction with a uniqueness constraint or lock on the table/time slot. Another useful tradeoff is bill splitting: you can support equal split first, then item-level split later by modeling BillLineItem ownership.

A strong close would be: “If I had more time, I’d add audit logs for staff actions, offline-terminal sync, menu availability windows, and failure handling around partial payment authorization.”

A second angle

For Design a keyboard and mouse input system, the same design skill applies, but the domain is lower-level and more event-driven. Instead of modeling business aggregates like Order and Reservation, you model InputEvent, KeyboardEvent, PointerEvent, FocusTarget, TextComposition, and EventDispatcher. The key invariant is not “do not oversell inventory,” but “deliver events in deterministic order to the correct focused component while preserving composition and pointer semantics.”

Concurrency shows up differently: hardware events, UI thread dispatch, accessibility hooks, and replay logs may operate on different threads. A strong design would use an event queue with sequence numbers, immutable event objects, and a single-threaded dispatch loop or carefully synchronized handoff. The transferable concept is the same: define ownership of mutable state, legal transitions, ordering guarantees, and the boundary where external inputs become internal state changes.

Common pitfalls

Pitfall: Starting with a class diagram before clarifying workflows.

A tempting weak answer is to list classes like Book, User, Cart, Order, or Task immediately. That misses what interviewers care about: behavior under real operations. Start with 3–5 core flows, then derive classes and methods from those flows.

Pitfall: Treating concurrency as “add a lock” without naming the invariant.

Saying “I’ll synchronize the method” is too vague. A better answer is: “The invariant is that inventory cannot go negative; I’ll enforce it with a transaction and conditional update, UPDATE inventory SET quantity = quantity - 1 WHERE sku = ? AND quantity > 0, then check affected rows.”

Pitfall: Overusing inheritance and patterns.

Designs like VegPizza extends Pizza, CheesePizza extends VegPizza, and LargeCheesePizza extends CheesePizza become brittle fast. Prefer small interfaces, composition, and strategies: Pizza has Size, Crust, List<Topping>, and pricing rules that can evolve independently.

Connections

Interviewers often pivot from this topic into system design, especially when an object model becomes a service with persistence, caching, and APIs. They may also pivot into database transactions, distributed consistency, idempotent API design, or design patterns such as Factory, Strategy, Observer, Repository, and State.

Further reading

Featured in interview prep guides

Practice questions

Related concepts

Object-Oriented Design And Concurrency-Safe LLD — Tech Interview Concept | PracHub