LLD-010 · LOW LEVEL DESIGN
Shopping Cart and Checkout
Freeze pricing and make reservation, payment, refund and release obligations explicit.
Local Java 21 reference verified: 25 checks. Learner drafts are not executed. Author self-review; independent review pending.
Existing introductory lesson · Version 1
Understand the problem
Design a versioned cart and checkout coordinator. Produce an immutable quote, accept one checkout, correlate inventory and payment outcomes, and recover when cancellation and a late charge leave compensation work. Explain why external actions cannot be represented by a single database rollback.
Before you start
- Immutable money/price snapshots
- Optimistic versions and retry identity
- State machines
- Reservation and compensation boundaries
Scope and explicit decisions
- One in-memory cart/checkout session. It can be edited before one accepted begin; after that the cart remains sealed even if checkout fails. Opening a new cart, multiple simultaneous orders, shipment splitting, taxes, discounts and subscription billing are not implemented.
- Item unit prices are trusted catalog-adapter inputs in integer minor units. Quote priceVersion is an immutable label, not a catalog lookup or proof of price authenticity. A production adapter must resolve authorized prices rather than trust a browser.
- set replaces quantity/price for one SKU and increments cart version. Quantity must be positive; remove is explicit. Quotes capture sorted items, exact total, cart version, expiry and pricing label. Arithmetic overflow rejects quote creation.
- A trusted nondecreasing elapsed clock is used for quote expiry. begin at expiresAt is rejected. Editing the cart invalidates old quotes by version, even if their timer has not expired.
- begin uses a key fingerprint of quote ID and expected cart version. Matching repeats resolve the same Order identity/current state. A different key cannot begin another order after the session seals.
- The coordinator performs no network I/O. reserved/paid/released/refunded are trusted adapter acknowledgements. false payment means definite no-charge; unknown means leave the current state unresolved, not call paid(false).
- Reservation failure produces FAILED. Payment failure after successful reservation produces RELEASE_REQUIRED. A late success after cancellation/release can create REFUND_REQUIRED or REFUND_AND_RELEASE_REQUIRED. These states record obligations; external actions are not executed by changing the enum.
- cancel while RESERVING is rejected until reservation outcome is resolved. cancel while PAYING requests release; cancellation in other states returns current state and does not implement confirmed-order refund policy.
- Payment attempt IDs reject contradictory reuse. Additional distinct charges outside the expected states are rejected and require adapter reconciliation. Cleanup acknowledgements are state-guarded; duplicate or stale cleanup may reject rather than return a historical response. No durable storage or crash recovery is implemented.
Worked examples
Input: Cart version1 has two books at 100. Quote v1 is 200. begin succeeds, reserve succeeds, charge succeeds.
Outcome: RESERVING→PAYING→CONFIRMED; quote remains 200 and cart is sealed.
Snapshot acceptance and external outcome transitions have separate identities.
Input: Cancel while PAYING, then receive a successful charge before release completes.
Outcome: RELEASE_REQUIRED→REFUND_AND_RELEASE_REQUIRED. Both acknowledgements are needed before FAILED.
Cancellation cannot make an in-flight payment disappear.
Try before revealing
Hint 1
Which facts must remain unchanged after the customer accepts a quote?
Hint 2
What prevents a second begin under a different retry key?
Hint 3
List every external obligation that cancellation can leave behind.
Requirements and invariants
R1 — Accepted checkout uses an immutable quote for the current cart version. begin protects I1.
R2 — Only one begin can seal this cart session. begin protects I2.
R3 — Every known outstanding external repair remains represented until acknowledged. paid protects I3.
I1: Order.quote and its items/amount/pricing version never change after begin.
I2: A sealed cart cannot be edited or accepted under a second begin key; a retry resolves one original order identity.
I3: Known reserved stock/payment liability is not hidden by cancellation: release and refund obligations transition independently until both are acknowledged.
Derive the design
Cart arithmetic is not the checkout transaction
A basic cart can add items and sum totals correctly, but checkout introduces a time boundary: prices, quantities and external outcomes can change independently. Start by freezing a quote tied to cart version. The reference then uses an explicit coordinator state machine rather than hiding reserve/charge/release/refund behind one synchronous success boolean.
Alternative: A transactional database can store local cart/order state but cannot roll back an already accepted external charge. A saga or durable workflow is a suitable extension only after each step’s identity, outcome ambiguity and compensation contract are specified.
Clarify which prices are authoritative
This reference accepts trusted item prices; do not expose that method directly to an untrusted browser. Real checkout resolves prices and eligibility at an authorized boundary before creating a quote.
Freeze intent before external work
A quote has identity, expiry and cart version. begin accepts it once and seals mutable editing, so asynchronous callbacks cannot drift with cart changes.
Write the failure transitions first
Enumerate reserve failure, definite payment failure, unknown payment and cancellation/late success. Give every known obligation a visible state rather than one catch-all error.
Keep external calls outside local atomicity
Methods record transitions and trusted acknowledgements. A real worker performs I/O with stable operation identities and persists results/repair intent; the local monitor is not a distributed transaction.
Responsibilities and ownership
Checkout
Owns: Cart version, immutable quotes, order lifecycle and callback identities
Methods: set, quote, begin, reserved, paid, cancel, released, refunded
Coordinates local facts but executes no provider or inventory I/O.
Item / Quote
Owns: Validated SKU quantities, unit prices, exact totals and immutable quote terms
Methods: Record constructors/accessors
Catalog authority and pricing-policy execution are adapter responsibilities.
Order
Owns: Stable checkout identity and current lifecycle snapshot
Methods: Immutable record accessors
A state change is not a completed external action.
Begin / Paid
Owns: Retry fingerprints and trusted callback identity
Methods: Structured equality
Matching retries cannot silently change quote or payment meaning.
One local cart session is sealed after begin. No external actions are executed inside the coordinator.
- Mutable versioned cart
↓ Trusted pricing and checked totals
Immutable quote + expiry↓ Accept current terms once
Sealed RESERVING order↓ Trusted reserve/payment acknowledgements
PAYING then CONFIRMED
Refund and release are independent obligations, and either can require retries outside this model.
- Cancel while PAYING
↓ Cancellation is not a provider outcome
RELEASE_REQUIRED↓ Preserve both liabilities
Late charge: REFUND_AND_RELEASE_REQUIRED↓ Only then terminal FAILED
Acknowledge both repairs
Data model, schema and relationships
A mutable cart becomes an immutable quote, then one accepted checkout session tracks independent inventory and payment facts. Repair obligations are separate rows because refund and release can fail independently.
Implementation boundary: The reference uses Cart/Quote/session records and trusted correlated adapter facts. SQL/outbox/payment providers below describe a durable extension.
These are proposed logical schemas with field contracts, not executable database migrations. PK identifies a row, FK references an existing parent, UK is a uniqueness rule. A composite PK identifies the combination. Nullable references are marked with ?. A foreign key protects identity; cross-row limits, timing and state transitions also require the transaction rules below.
Connectors name the relationship; multiplicities state parents per child and children per parent. 0..1 means optional, 1 required, 0..* any number. Self-links show two roles of the same entity.
Connectors name the relationship; multiplicities state parents per child and children per parent. 0..1 means optional, 1 required, 0..* any number. Self-links show two roles of the same entity.
Connectors name the relationship; multiplicities state parents per child and children per parent. 0..1 means optional, 1 required, 0..* any number. Self-links show two roles of the same entity.
Entity definitions and field contracts
cart
Editable purchase intent.
| Field | Type / key | Meaning and reference |
|---|---|---|
id | uuid · PK | Cart identity |
version | bigint · VALUE | Mutation version |
owner | text · VALUE | Authenticated purchaser |
Code mapping: Cart.
- Quote must correspond to expected cart version.
quote
Immutable trusted price snapshot.
| Field | Type / key | Meaning and reference |
|---|---|---|
id | uuid · PK | Quote identity |
cart_id | uuid · FK | Source cart · cart.id |
cart_version | bigint · VALUE | Priced version |
total | bigint · VALUE | Exact single-currency total |
Code mapping: Quote.
- Snapshot line prices, quantities and policy/currency; reject stale/expired quote.
checkout
One sealed accepted session.
| Field | Type / key | Meaning and reference |
|---|---|---|
id | uuid · PK | Session identity |
quote_id | uuid · FK | Accepted quote · quote.id |
state | enum · VALUE | Explicit orchestration state |
cart_id | uuid · FK | Source cart; unique accepted session per sealed cart · cart.id |
Code mapping: Checkout state owner.
- One accepted session per sealed cart under this reference contract.
checkout_fact
Correlated provider outcome evidence.
| Field | Type / key | Meaning and reference |
|---|---|---|
id | text · PK | Provider operation identity |
checkout_id | uuid · FK | Session owner · checkout.id |
kind | enum · VALUE | Inventory or payment |
outcome | enum · VALUE | Known success failure or unknown |
Code mapping: Inventory/payment facts.
- Duplicate terminal evidence must agree; validate session and amounts.
repair_obligation
Independent compensation work.
| Field | Type / key | Meaning and reference |
|---|---|---|
id | uuid · PK | Repair identity |
checkout_id | uuid · FK | Owner · checkout.id |
kind | enum · VALUE | Refund or release |
status | enum · VALUE | Needed pending complete failed |
source_operation | text · VALUE | Remote effect being repaired; distinguishes extra side effects |
next_due | timestamp? · VALUE | Durable retry eligibility for unresolved repair work |
Code mapping: Separate refund/release obligations.
- Unique(checkout_id,kind,source_operation) when multiple provider effects are supported.
Relationship cardinalities and their reasons
| Parent → child | Multiplicity | FK / why |
|---|---|---|
| cart → quote is priced | 1 parent per child; 0..* children per parent | quote.cart_id → cart.idA cart can have many historical quotes. |
| quote → checkout is accepted | 1 parent per child; 0..* children per parent | checkout.quote_id → quote.idSingle-session constraints restrict which quotes may begin checkout. |
| checkout → checkout_fact correlates evidence | 1 parent per child; 0..* children per parent | checkout_fact.checkout_id → checkout.idLocal orchestration state derives from trusted remote facts. |
| checkout → repair_obligation requires repair | 1 parent per child; 0..* children per parent | repair_obligation.checkout_id → checkout.idRefund and release progress independently; one success cannot erase the other obligation. |
| cart → checkout belongs to | 1 parent per child; 0..* children per parent | checkout.cart_id → cart.idSource cart; unique accepted session per sealed cart |
Transaction boundaries
- begin: compare cart/quote versions, seal cart and insert accepted session with stable command identities.
- Outcome: record provider fact and derive session plus required repair rows atomically; dispatch repair outside transaction through an outbox.
Indexes and uniqueness
- Unique accepted checkout per cart through explicit cart/session ownership.
- Index repair_obligation(status,next_due), checkout_fact(checkout_id,kind); provider operation IDs unique.
Schema traps and failure cases
- A successful refund cannot mark inventory release complete.
- Timeout is unknown; reopening the same sealed cart can duplicate side effects.
Trace the records through one operation
- Quote Q pins cart v4 prices; session C accepts it once.
- Payment succeeds while inventory fails; refund obligation is created.
- Repair result closes only its own obligation; repeated provider fact cannot create a second checkout.
Method contracts
set(Item,expectedVersion); remove(sku,expectedVersion)
Edit an unsealed cart only at its current version. set replaces one SKU entry; remove is a no-op for absent SKU without increment.
quote(priceVersion,ttl,now) → Quote
Reject empty cart/invalid label/TTL. Compute every quantity×unitPrice and sum with overflow checks; capture current cart version/items and immutable expiry.
begin(quoteId,expectedVersion,key,now) → Order
Resolve key repeats, validate quote/current version/expiry, then atomically seal cart and create RESERVING order.
reserved(orderId,success); paid(orderId,attemptId,success)
Correlate trusted outcomes. Do not interpret timeout as false. Contradictory repeats reject; known late charge adds refund liability rather than confirming a cancelled flow.
cancel; released; refunded
Cancellation creates repair work only in declared states. Acknowledgements clear the corresponding obligation, preserving the other if both are pending.
What, when, why and how
Each concept is attached to the requirement it protects, its implementation, an alternative and a test.
Immutable quote snapshot
- what
- Freeze the exact order facts the customer accepted.
- when
- Catalog prices or cart quantities change between browsing and payment.
- why
- Recomputing from mutable cart/catalog data makes history and payment comparison inconsistent.
- how
- Quote copies Item records and stores cartVersion, checked amount, expiresAt and priceVersion. Order retains that Quote unchanged.
Code: Quote, quote, begin · Invariants: I1
Alternatives, cost and failure example
Alternative: Reprice at begin instead, but explicitly ask the customer to accept new terms; do not silently switch amounts.
Cost: Quote storage retains item snapshots and histories; this example has no retention cleanup.
Counterexample: Customer accepts two books at 100 each, then a later catalog update rewrites the order total.
Test: Edit cart after quoting and reject old begin; verify old quote stays 200 under its original version.
Optimistic version plus idempotent begin
- what
- Reject stale intent and resolve repeated accepted checkout identity.
- when
- Two tabs edit or begin the same cart.
- why
- Version guards prevent unintended contents while keys prevent duplicate effects after a response loss.
- how
- begin compares current cart/quote versions and seals the session under one monitor. Begin key stores quote+expectedVersion and original Order ID.
Code: set, begin, Begin · Invariants: I1, I2
Alternatives, cost and failure example
Alternative: A versioned database row and unique command key are the durable equivalent; separate uncoordinated writes lose atomicity.
Cost: One cart session stays sealed permanently here; a production product needs explicit new-cart/reset ownership.
Counterexample: Two different begin keys both charge the same cart because only a per-key cache was checked.
Test: Race two begin keys at the same version; one wins, and original key retry returns that Order.
Stateful compensation obligations
- what
- Represent external repair as business work that remains pending.
- when
- A reservation succeeds but payment fails, or payment arrives after cancellation.
- why
- An enum named FAILED can conceal stock still held or money still charged.
- how
- Use RELEASE_REQUIRED, REFUND_REQUIRED and the combined state. released/refunded remove only their own obligation; terminal FAILED follows completed local acknowledgements.
Code: paid, released, refunded · Invariants: I3
Alternatives, cost and failure example
Alternative: A durable workflow/outbox can drive these obligations, but compensation itself can fail and needs its own retry/review policy.
Cost: More states increase transition-table/test requirements but expose otherwise hidden liabilities.
Counterexample: Cancel sets FAILED immediately while a late successful charge is discarded.
Test: Cancel PAYING, accept late success, then acknowledge refund/release in either supported order and inspect remaining obligation.
Definite failure versus unknown external outcome
- what
- An absence of response is not evidence that no charge occurred.
- when
- Provider timeouts or worker crashes interrupt the request path.
- why
- Treating unknown as failure may trigger a second charge or clear obligations incorrectly.
- how
- Adapters call paid(false) only for definite no-charge. Unknown leaves PAYING or cleanup state unresolved; a later trusted success is correlated by attempt ID.
Code: paid, Paid, cancel · Invariants: I3
Alternatives, cost and failure example
Alternative: Use provider idempotency/query APIs or manual reconciliation for unknown results. No local boolean can infer an unobserved remote fact.
Cost: The teaching coordinator lacks a durable attempt journal and automatic reconciler; process loss loses evidence.
Counterexample: A timeout invokes paid(false), releases stock and retries with a fresh payment identity while the first charge succeeded.
Test: Exercise late success after release and reject contradictory success/failure under one attempt ID.
Read and run the Java reference
Reveal complete Solution.java
Java 21. Save as Solution.java; compile with javac --release 21. The repository also contains Test.java and the lld:java verification command. This reference implements the local contract above.
quote calculates before publishing
Checked multiplication and addition run before storing the new Quote. Overflow does not create a malformed accepted price.
begin resolves prior identity before checking current lock
A successful retry must still return its original Order after the cart is sealed. A fresh key is rejected by the seal.
paid handles the losing late-success path
A charge that arrives after release/cancellation does not confirm the old business flow; it creates a refund obligation and preserves any release work.
cleanup acknowledgements preserve the other obligation
From the combined state, refunded leaves RELEASE_REQUIRED and released leaves REFUND_REQUIRED. Only completion of both reaches terminal repair state.
Complexity and limits
- Cart updates use an ordered SKU map: O(log I). quote iterates/copies I items: O(I), with checked long arithmetic.
- begin and callback lookup are expected O(1) over retained maps; constructing order/quote snapshots retains references to immutable item records.
- Space grows with I cart items, Q quotes, O orders and retained begin/payment identities. This session seals after one begin but quote/history retention is not bounded by a cleanup policy.
- The coordinator has no network latency inside its monitor. That simplifies local reasoning but does not imply durability or exactly-once external side effects.
Walk through the actual reference
These walkthrough fixtures are emitted only after the Java harness verifies their operations and expected state. Concurrency tests additionally exercise competing callers.
Quote snapshot and successful checkout
Step 1 of 3: Quote 2 books at 100 each
Amount 200; cartVersion 1; price-v1
Invariant: Order terms are immutable
Read the complete trace table
| Action | State after action | Invariant |
|---|---|---|
| Quote 2 books at 100 each | Amount 200; cartVersion 1; price-v1 | Order terms are immutable |
| Begin with current version | RESERVING; cart sealed | Only one accepted checkout |
| Reserve and accept payment | CONFIRMED | Payment completion follows reservation |
Cancellation plus late successful charge
Step 1 of 3: Cancel while PAYING
RELEASE_REQUIRED
Invariant: Cancellation is not proof that a provider stopped
Read the complete trace table
| Action | State after action | Invariant |
|---|---|---|
| Cancel while PAYING | RELEASE_REQUIRED | Cancellation is not proof that a provider stopped |
| Late payment succeeds | REFUND_AND_RELEASE_REQUIRED | Both external obligations stay visible |
| Acknowledge refund, then release | FAILED with no outstanding cleanup in this model | Terminal repair requires both acknowledgements |
Mistakes and counterexamples
Calculate historic order price from current catalog
Failing case: The item price changes after quote acceptance.
Repair: Retain immutable accepted terms and version.
Use only a retry key without cart version
Failing case: A stale tab submits an old quote for edited contents.
Repair: Check both accepted identity and current version/expiry.
Set FAILED on cancellation and forget obligations
Failing case: Stock remains reserved and a late payment succeeds.
Repair: Track release/refund as explicit pending business work.
Equate timeout with no charge
Failing case: Provider processed payment but response was lost.
Repair: Preserve unknown state and reconcile a stable attempt identity.
Engineer → Senior → Staff
Implement exact cart/quote behavior.
Executed: quantity validation, checked totals, immutable pricing snapshot, version and expiry guards.
Change exercise
Add a discount calculation with explicit ordering and rounding before snapshot creation.
Worked answer guidance
Separate policy from mechanism, define whether discounts apply per item or subtotal, test noncommutative ordering and exact minor-unit rounding. Store the resulting breakdown/version in Quote rather than re-evaluating later.
Interview follow-ups
Why is quantity zero not remove?
This API chooses explicit remove, avoiding ambiguous zero-price/zero-quantity item semantics. Other choices are valid if documented and tested.
Is priceVersion proof of a correct price?
No. It is a label on a trusted snapshot. A real catalog adapter must enforce actual policy and source authority.
Your design attempt
Each language has a separate draft across all three levels. Saving is device-only and does not sync to your account. Anyone using this browser profile can load it. Loading is always explicit.
Java 21 reference code is provided for local study. Use Run program in the editor to execute your code and test driver. Program runs show output; they do not grade your design.
Run program · Java 21
No unsaved changes · 208/20000
Self-review
Checks apply to this visit. Self-review is not a passed submission or verified mastery.
Provenance and scope of evidence
Original teaching material. The local contract is implemented by Solution.java and tested by Test.java on Java 21. Distributed persistence, real provider/device adapters and deployment are not claimed.
Reference verification is not independent editorial review, production deployment verification or a claim that distributed adapters are implemented.