Shopping Cart
E-Commerce Cart System
Design a shopping cart that holds items, prices them with stackable promotions and coupons, and turns them into an order without overselling stock or charging a stale price.
Overview
A shopping cart holds what a shopper intends to buy. The shopper adds and removes items, sees a price with discounts and tax, applies a coupon, and checks out. Behind that simple screen sit three hard questions: how promotions combine, how money is added up without rounding errors, and what happens when the price or the stock changes between 'add to cart' and 'pay'.
The interviewer is listening for four things. First, that the cart stores only product ids and quantities, and prices are looked up when a quote is made. Second, that each promotion is its own object behind one interface, so a new promotion is a new class and the pricing code is not edited. Third, that money is an integer number of cents. Fourth, that checkout re-prices the cart and takes stock for every line or for none.
Scoping the problem
- Which promotion types? — Decides the rule interface — Percent off a category, buy X get Y free, an amount off above a minimum
- Do promotions stack, and in what order? — Changes the total — Item rules first on list prices, then cart rules on what is left
- Coupons: expiry, one use per code? — Coupon state must be shared and guarded — Yes to both
- Is stock held when an item is added, or only at checkout? — Decides where the stock lock sits — Only at checkout
- What if a price changes after the shopper saw the total? — Charging a different amount than shown is a bug — Refuse and show the new total
- Guest carts? — Needs a merge on login — Yes: quantities add up
- Tax? — One more step in the price pipeline — One rate, on the discounted amount
Core entities
Keeping prices out of the cart is the first decision to say out loud. A cart that stores the price at 'add' time will one day charge yesterday's price. A cart that stores only quantities is always priced fresh.
Money is an integer
Floating-point numbers cannot hold most decimal fractions exactly. In Python, 5260 × 0.0825 gives 433.95000000000005, not 433.95. Add a few hundred such values and the cents drift. The code stores every amount as an int of cents (1250 means 12.50), which is also how payment APIs such as Stripe take amounts.
- float dollars — No — Hidden, happens on every operation — Never for money
- int cents (the code) — Yes — Only where you divide, and you choose how — Carts, orders, ledgers
- decimal.Decimal — Yes — Set by a context you control — Many currencies, sub-cent rates
The code divides in two places and says how it rounds. A percent discount rounds down, so the shop never gives away a fraction of a cent. Tax rounds half up: (amount × basis points + 5000) // 10000, where 825 basis points is 8.25 percent.
How promotions stack
Every promotion implements discount(lines, running_total) and returns cents. The engine sorts rules by stage and applies them in order. Item rules (stage 1) look at list prices. Cart rules (stage 2) look at the running total after item rules. Each discount is capped at the running total, so the price never goes below zero, and each one is recorded with its label so the receipt can explain the price.
The trace below is the demo in the implementation: 2 mugs at 12.00, 1 knife at 30.00, 3 teas at 4.50, coupon SAVE5 (5.00 off at 50.00 or more), tax 8.25 percent.
- 1 — Subtotal — 2 × 1200 + 3000 + 3 × 450 — none — 6750
- 2 — Tea: buy 2 get 1 free (item) — 3 teas: 3 // 3 = 1 free — 450 — 6300
- 3 — 10% off kitchen (item) — mugs and knife at list price: 5400 — 540 — 5760
- 4 — SAVE5 coupon (cart) — 5760 is at least 5000 — 500 — 5260
- 5 — Tax 8.25%, half up — 5260 × 825 / 10000 = 433.95 — +434 — 5694
Checkout
Checkout is where the shared state is touched, so it is where the care goes. It re-prices the cart with today's catalog. If the total differs from the one the shopper confirmed, it refuses and returns the new quote. Then it reserves stock for every line in one step, redeems the coupon, and clears the cart. If the coupon was used up in the meantime, it puts the stock back.
In the demo the mug price moves from 12.00 to 13.00 after the shopper saw 56.94. The first attempt is refused with the new total, 58.89, and no stock is taken. The second attempt, confirmed at 58.89, succeeds.
What you will be able to do
- Keep the cart to SKUs and quantities and explain why prices are looked up at quote time.
- Model each promotion as a class behind one interface and state the stacking order as a table.
- Hold money as integer cents and name the two places the code rounds, and how.
- Walk through checkout: re-price, compare to the confirmed total, reserve all stock or none, redeem the coupon, roll back on failure.
- Show that eight shoppers racing for the last three items produce exactly three orders.
Functional requirements
- add(sku, qty), set_quantity(sku, qty) where 0 removes the line, and merge(guest_cart) on login
- preview(cart, now, coupon) returns a Quote: lines, subtotal, each discount with its label, tax and total
- Promotions: percent off a category, buy X get Y free on a SKU, and an amount off when the total reaches a minimum
- Coupons have an expiry time and a use limit; an expired or used-up code is refused
- place_order(cart, now, expected_total, coupon) re-prices, refuses if the total changed, reserves stock for all lines or none, and redeems the coupon
- An out-of-stock line fails the whole order and takes nothing
Non-functional requirements
- Money is an int of cents; rounding happens only where the code divides, and the direction is stated
- A new promotion type is a new PromotionRule subclass; PricingEngine is not edited
- Every discount appears on the quote with its label, so the price is explainable
- Stock is never oversold when shoppers check out at the same time
- Time is passed in, so coupon expiry is exact in tests
Interview questions
- A marketing team wants a promotion where only the single best discount applies, not all of them. How do you support that?
- The price changed between the time the shopper saw the total and the time they clicked Pay. What do you do?
- Two shoppers try to buy the last knife at the same time. Walk me through it.
- How do you merge a guest cart into a signed-in cart?
- How would you persist carts?
Sign up free to read the full topic
More Low-Level Design topics
Start free on Cariara · Coding problems · System design studio