Design cart and pricing engine classes
Company: Uber
Role: Software Engineer
Category: System Design
Difficulty: medium
Interview Round: Onsite
Design the core classes and interfaces for a **cart & pricing engine** for a food delivery marketplace (e.g., Uber Eats). This is an **object-oriented / low-level design (LLD)** problem — the focus is on clean class boundaries, the right design patterns, and a correct, traceable pricing pipeline, **not** on distributed systems, sharding, or QPS.
In this marketplace, the final checkout price is rarely just the sum of the menu items. It is dynamically computed from several independent factors:
- **Item customizations** — add/remove ingredients, size upgrades, dietary substitutions. Each customization may add `$0` or a positive cost (e.g., "Burger" + "Extra Cheese" + "No Onions").
- **Marketplace dynamics** — e.g., **surge pricing** modeled as a multiplier applied to a subtotal (e.g., `1.2×`) during peak hours or bad weather.
- **Promotions** — coupon codes that are flat amounts (**$5 off**) or percentages (**10% off**), **Buy 1 Get 1 Free (BOGO)**, and possibly tiered delivery fees.
- **Membership benefits** — e.g., subscribers get **$0 delivery fee** and **5% off eligible items**.
**Your task.** Provide an object-oriented design — the key classes, interfaces, and how they collaborate — that satisfies these requirements:
1. **Cart operations** — add/remove items, change quantities, and apply item-level customizations.
2. **Flexible pricing strategies** — support multiple independent pricing rules (surge, membership discount, coupons, BOGO, delivery-fee rules). Rules must be **composable** and **easy to extend** (adding a new rule shouldn't require touching the cart model or existing rules).
3. **Receipt breakdown** — return an itemized checkout receipt showing at least: base price, add-ons, fees, discounts, and the final total.
Be explicit about **how pricing rules are ordered and combined** (e.g., does a percentage coupon apply before or after surge?) and **how the itemized receipt is produced**.
```hint Where to start
Separate the **cart model** (what the user selected) from the **pricing engine** (what it costs). The cart should store only selections, never computed totals, so it can never hold a stale price — the engine derives the receipt on demand.
```
```hint Key patterns to consider
Each pricing feature behind one interface is a textbook **Strategy**; running them as an ordered **pipeline** (a Chain of Responsibility where every link may *contribute* a line rather than short-circuit) gives you Open/Closed extensibility. Consider how the **Decorator** pattern (each customization wraps the item) compares to a normalized **selection list** for itemizing the receipt.
```
```hint Ordering & combination
The biggest correctness trap is rule *order*: a 10%-off coupon yields a different total depending on whether surge has already been applied. Think about expressing order as a small set of named **phases** (itemize → surge → fees → discounts → post-processing) instead of magic priority integers, and define exactly **one** base that every percentage discount targets.
```
```hint Money & explainability
Don't represent money as a `double` — `0.1 + 0.2 != 0.3` in binary floating point and a pricing engine that drifts by a cent is a correctness bug. Use an integer-minor-unit money type with explicit currency and an explicit rounding policy. For explainability, have rules **append receipt lines** to an accumulator rather than return opaque numbers — the receipt then *is* the audit log.
```
### Constraints & Assumptions
- **Scope**: this is a single-process LLD exercise. In scope: cart model, pricing rules, rule ordering/combination, receipt generation, money arithmetic, testability. Out of scope (mention the seams, don't design them): tax-jurisdiction logic, payment capture, inventory, multi-restaurant carts, persistence/storage.
- **Determinism**: pricing must be a pure function of `(cart, user context, marketplace context, ruleset)` — the same inputs always produce a byte-identical receipt (the preview price must equal the price charged at capture).
- **Correctness invariants**: exact money arithmetic (no binary floating point); the final total is never negative; no discount exceeds its eligible base; displayed receipt lines must sum exactly to the grand total.
- **Single currency** per cart, validated at construction.
- Assume promotions/coupons are a small set per cart; the engine processes one cart at a time (no need to optimize for batch pricing).
### Clarifying Questions to Ask
- **Stacking** — Do coupons stack with membership discounts and with each other? (Common answer: at most one coupon, but a coupon may stack with membership; BOGO is a separate promo class.)
- **Surge base** — Does surge apply to the item subtotal only, or to subtotal + fees? (Surging a flat service fee is rarely intended.)
- **Percentage base** — Do percentage discounts (membership %, coupon %) apply to the goods only (items + surge) or also to fees? (You normally discount the food, not the delivery fee.)
- **BOGO semantics** — Same-line ("buy 1 get 1 of the identical item") or cross-item ("buy A get B free")? How are the free unit's add-ons (e.g., Extra Cheese) priced — free, or charged?
- **Membership effects** — Is the membership delivery-fee benefit a true $0 fee or a waiver that should still appear on the receipt? Are some items (alcohol, regulated goods) excluded from the 5% discount?
- **Rounding** — Round per line or once at the end? Round-half-up or banker's rounding?
### What a Strong Answer Covers
- **Cart/pricing separation** — a cart that stores only selections (line items + customizations + applied coupon + context), with totals derived by a separate engine so no stale price can be stored.
- **A safe money type** — integer minor units (cents) with explicit currency, exact integer scaling for quantities, and a *deliberate* rounding decision (with mode) only for rational factors (surge, percentages); rejects currency mismatches.
- **Strategy + pipeline** — each pricing feature as an independent rule behind one interface, run by an engine that knows only how to *sequence* rules and assemble the receipt (Open/Closed: a new rule is a new class and nothing else changes).
- **Explicit, justified ordering** — phases rather than magic priority numbers, with stated policy decisions (what surge applies to, what base percentages target, when fee waivers run) that the candidate offers up for the interviewer to veto.
- **Explainable receipt** — rules emit typed receipt lines (item base, add-on, surge, fee, discount, tax) into an accumulator; the receipt is the single source of truth for the total and every line is traceable.
- **The hard sub-rules done, not hand-waved** — surge as a multiplier surcharge kept distinct from real fees; BOGO pairing/add-on/stacking semantics resolved; coupon flat-vs-percent unified with a discount cap so a discount can never exceed its base.
- **Cross-cutting policy extracted** — stacking, eligibility, and rounding pulled into small collaborators instead of being hardcoded per rule.
- **Design-pattern awareness with tradeoffs** — naming the patterns used (Strategy, Pipeline/Chain of Responsibility, Value Object, Accumulator/Builder, Policy objects, optional Factory) and discussing alternatives considered (e.g., Decorator for customizations) and why.
- **Correctness, failure handling, testability** — enforced invariants (non-negative total, single currency, discount caps), determinism/idempotency, and a test strategy (per-rule unit tests, a golden-receipt fixture, property tests).
- **A worked end-to-end example** — a concrete cart priced line by line to a final total, demonstrating the ordering and that the lines reconcile to the total.
### Follow-up Questions
- A new market needs a **city tax** applied after all discounts, and a separate **small-order fee** below a $10 subtotal. Show exactly what you add and why nothing else changes.
- A promotion says **"$5 off, but never more than 50% of the order."** How does your discount-cap design express this, and where does it live?
- Marketing wants surge as a **loss-leader**: percentage discounts should apply to the *pre-surge* subtotal so the customer "doesn't pay the discount on surge." How big is that change in your design?
- How do you guarantee the **price shown at preview equals the price charged at capture** even if surge, coupons, or membership change between the two moments?
- How would you unit-test the **interaction** between BOGO and a percentage coupon on the same line items so a future refactor can't silently double-count?
Quick Answer: This question evaluates object-oriented system design and domain modeling skills related to cart operations, item customizations, composable pricing rules (surge, promotions, membership) and producing an itemized receipt.