Core Entities
Cart: cart_id, user_id (NULL for guest), session_id (for guest carts), status (ACTIVE, ABANDONED, CHECKED_OUT), currency, created_at, updated_at, expires_at. CartItem: item_id, cart_id, product_id, variant_id (size/color), quantity, unit_price (captured at add-to-cart time), custom_options (JSONB: gift wrap, personalization text). Product: product_id, name, description, base_price, currency, stock_quantity, is_active. ProductVariant: variant_id, product_id, sku, attributes (JSONB: size, color), price_override (NULL = use base_price), stock_quantity. Discount: discount_id, code, type (PERCENTAGE, FIXED_AMOUNT, FREE_SHIPPING, BUY_X_GET_Y), value, min_order_amount, max_uses, uses_count, valid_from, valid_until, applicable_products (NULL = all). CartDiscount: cart_id, discount_id, applied_amount. SavedForLater: user_id, product_id, variant_id, saved_at.
Cart Persistence Strategy
class CartService:
# Guest carts: stored in Redis with TTL (30 days)
# Authenticated carts: stored in both Redis (cache) and DB (persistent)
def get_cart(self, cart_id: str, user_id: Optional[int]) -> Cart:
# Try Redis first (fast path)
cached = self.redis.get(f"cart:{cart_id}")
if cached:
return Cart.from_json(cached)
# Cache miss: load from DB (authenticated users only)
if user_id:
cart = self.db.get_cart_by_user(user_id)
if cart:
self.redis.setex(f"cart:{cart_id}", 86400, cart.to_json())
return cart
return None # guest cart expired
def add_item(self, cart_id: str, product_id: int,
variant_id: int, quantity: int) -> Cart:
with db.transaction():
# Validate product availability
variant = self.db.get_variant(variant_id)
if not variant.is_available or variant.stock_quantity < quantity:
raise InsufficientStock(variant_id)
# Capture current price (price can change after add-to-cart)
unit_price = variant.price_override or variant.product.base_price
# Upsert cart item
existing = self.db.get_cart_item(cart_id, variant_id)
if existing:
new_qty = existing.quantity + quantity
if variant.stock_quantity < new_qty:
raise InsufficientStock(variant_id)
self.db.update_cart_item(existing.item_id, {"quantity": new_qty})
else:
self.db.insert_cart_item({
"cart_id": cart_id, "product_id": product_id,
"variant_id": variant_id, "quantity": quantity,
"unit_price": unit_price
})
cart = self.db.get_cart(cart_id)
self.redis.setex(f"cart:{cart_id}", 86400, cart.to_json())
return cart
Price Calculation and Discount Engine
class PricingEngine:
def calculate(self, cart: Cart,
discount_code: Optional[str] = None) -> CartSummary:
subtotal = sum(item.unit_price * item.quantity for item in cart.items)
# Validate and apply discount
discount_amount = 0
if discount_code:
discount = self.db.get_discount(discount_code)
error = self._validate_discount(discount, cart, subtotal)
if error:
raise DiscountError(error)
discount_amount = self._compute_discount(discount, cart, subtotal)
# Shipping calculation
shipping = self._calculate_shipping(cart, subtotal - discount_amount)
# Tax calculation (by destination address)
taxable = subtotal - discount_amount + shipping
tax = self._calculate_tax(taxable, cart.shipping_address)
return CartSummary(
subtotal=subtotal,
discount=discount_amount,
shipping=shipping,
tax=tax,
total=subtotal - discount_amount + shipping + tax
)
def _validate_discount(self, d, cart, subtotal) -> Optional[str]:
if not d or not d.is_active: return "Invalid discount code"
if d.valid_until = d.max_uses: return "Discount limit reached"
if subtotal < d.min_order_amount: return f"Minimum order ${d.min_order_amount}"
return None
Checkout and Inventory Reservation
Checkout flow must atomically reserve inventory and create the order to prevent overselling. Step 1: Price recalculation. Recalculate prices at checkout time (not at add-to-cart time) to catch price changes. Notify user if any price changed. Step 2: Inventory reservation. Use SELECT FOR UPDATE on product_variants rows for all items. Verify stock >= quantity for each item. Decrement stock atomically in the same transaction. If any item is out of stock: rollback and return a specific error per item. Step 3: Order creation. Create Order and OrderItem records with the final prices. Set cart.status = CHECKED_OUT. Step 4: Payment. Create a Stripe PaymentIntent with the final total. On payment failure: reverse inventory reservation (increment stock back). On payment success: confirm the order and trigger fulfillment. Idempotency: the checkout endpoint accepts an idempotency_key (UUID) from the client. If the same key is submitted twice (network retry), return the existing result without double-processing.
{“@context”:”https://schema.org”,”@type”:”FAQPage”,”mainEntity”:[{“@type”:”Question”,”name”:”Why is price captured at add-to-cart time rather than checkout time?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”Capturing the price at add-to-cart time (storing unit_price in the CartItem) means the user sees the price they expected when they added the item, even if the product price changes before they check out. This is a better user experience — users would be surprised to find prices changed at checkout. Recalculation at checkout: the system should still recalculate and compare. If a price decreased: apply the lower price (favorable to the user). If a price increased: notify the user before final payment (do not silently charge more). The captured price is also important for promotions: a "10% off sale" price is captured at add time, so removing the promotion later does not affect items already in carts (respects the user's expected price).”}},{“@type”:”Question”,”name”:”How does atomic inventory reservation prevent overselling at checkout?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”The race condition: two users simultaneously check out with the last unit of a product. Both read stock=1, both decide to proceed, both decrement — stock goes to -1. Prevention: use SELECT … FOR UPDATE on the inventory row within the checkout transaction. Only one transaction holds the lock at a time. The critical sequence: BEGIN; SELECT stock_quantity FROM product_variants WHERE id=? FOR UPDATE; (check stock >= quantity); UPDATE product_variants SET stock_quantity = stock_quantity – quantity WHERE id=?; INSERT INTO orders …; COMMIT. The FOR UPDATE lock ensures only one transaction can decrement stock at a time. If the second transaction tries to lock the same row, it waits until the first commits or rolls back. After the first commits (stock=0), the second reads stock=0 and returns InsufficientStock.”}},{“@type”:”Question”,”name”:”How do you handle abandoned carts and recovery emails?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”Abandoned cart: a cart with items that has not been checked out after a configurable time (e.g., 1 hour). Detection: a scheduled job queries carts with status=ACTIVE and updated_at < NOW() – 1 hour that have at least one item. For each abandoned cart with a known email (authenticated user or guest who provided email): (1) Send a recovery email with a link that restores the cart. (2) Update cart status to ABANDONED. Recovery link: includes the cart_id as a token. Clicking the link restores the cart and prompts the user to complete checkout. Email sequence: email 1 at 1 hour, email 2 at 24 hours (with a discount code if cart value is high), email 3 at 72 hours (final reminder). Stop sequence if user checks out or explicitly dismisses. Cart recovery emails typically recover 5-15% of abandoned carts.”}},{“@type”:”Question”,”name”:”How does the discount validation engine prevent abuse of discount codes?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”Discount code abuse vectors: (1) Code sharing: a private code used by many users. Prevent with max_uses limit, enforced atomically: UPDATE discounts SET uses_count = uses_count + 1 WHERE discount_id = ? AND uses_count < max_uses. Check rows_affected == 1. (2) Per-user limits: some codes allow one use per user. Check discount_uses table (user_id, discount_id) before applying. Insert a record on use. (3) Stacking: applying multiple discount codes simultaneously. Policy varies — most platforms allow one code at a time. Enforce by checking cart_discounts count before applying. (4) Race condition on max_uses: two concurrent applications might both pass the check before either increments. The atomic UPDATE with WHERE condition prevents this — only one will get rows_affected == 1. (5) Replay: re-applying a code to the same order. Idempotency check: if a code is already in cart_discounts for this cart, return the already-applied amount.”}},{“@type”:”Question”,”name”:”How do you merge a guest cart with a user cart on login?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”When a guest user logs in, they may have items in their guest cart (identified by session_id) AND possibly an existing authenticated cart from a previous session. Merge strategy: (1) Fetch both carts. (2) For each item in the guest cart: if the item (product + variant) already exists in the authenticated cart, add quantities together (capped at available stock). If not: add the item to the authenticated cart at the guest cart's unit_price. (3) Mark the guest cart as MERGED (not deleted — keep for audit). (4) Return the merged authenticated cart. Price handling: re-validate prices against current product prices during merge. If prices changed since the guest session, notify the user. Implementation: run the merge in a database transaction to prevent partial merges. The merge is idempotent (can be retried if interrupted) since we check for existing items before adding.”}}]}
See also: Shopify Interview Prep
See also: Stripe Interview Prep
See also: Airbnb Interview Prep