๐The Interview Setting
Company: Amazon Round: Frontend System Design Duration: 45 minutes
๐๐ค Interviewer: "Design a shopping cart for an e-commerce site."
Candidate's Clarifications:
Interviewer: "Real-time inventory, both guest and logged-in, yes to save for later."
๐๐ State Management
// Cart state structureinterface CartState { items: CartItem[]; savedItems: CartItem[]; appliedCoupons: Coupon[]; shippingAddress: Address | null; selectedPayment: PaymentMethod | null; giftOptions: GiftOption[];}interface CartItem { id: string; productId: string; sellerId: string; name: string; image: string; price: number; originalPrice: number; quantity: number; maxQuantity: number; // Inventory limit isAvailable: boolean; deliveryEstimate: string; giftWrap: boolean;}// Cart contextconst CartContext = createContext<CartContextType>(null);const CartProvider = ({ children }) => { const [cart, dispatch] = useReducer(cartReducer, initialState); // Sync with server useEffect(() => { const syncCart = async () => { if (isLoggedIn) { await api.syncCart(cart); } else { localStorage.setItem('cart', JSON.stringify(cart)); } }; const debounced = debounce(syncCart, 1000); debounced(); }, [cart]); return ( <CartContext.Provider value={{ cart, dispatch }}> {children} </CartContext.Provider> );};
๐๐ค Interviewer: "How do you handle quantity updates with inventory checks?"
Candidate:
const useQuantityUpdate = (itemId) => { const { dispatch } = useCart(); const [isUpdating, setIsUpdating] = useState(false); const [error, setError] = useState(null); const updateQuantity = async (newQuantity) => { setIsUpdating(true); setError(null); // Optimistic update const previousQuantity = getItemQuantity(itemId); dispatch({ type: 'UPDATE_QUANTITY', itemId, quantity: newQuantity }); try { // Verify inventory const { available, maxAllowed, currentPrice } = await api.checkInventory( itemId, newQuantity ); if (!available) { // Rollback and show error dispatch({ type: 'UPDATE_QUANTITY', itemId, quantity: previousQuantity }); setError('Item is no longer available'); return; } if (newQuantity > maxAllowed) { // Adjust to max dispatch({ type: 'UPDATE_QUANTITY', itemId, quantity: maxAllowed }); setError(`Only ${maxAllowed} available`); return; } // Update price if changed if (currentPrice !== getItemPrice(itemId)) { dispatch({ type: 'UPDATE_PRICE', itemId, price: currentPrice }); toast.info('Price updated'); } } catch (err) { // Rollback on network error dispatch({ type: 'UPDATE_QUANTITY', itemId, quantity: previousQuantity }); setError('Failed to update. Please try again.'); } finally { setIsUpdating(false); } }; return { updateQuantity, isUpdating, error };};
๐๐ค Interviewer: "What edge cases do you handle?"
Candidate:
1. Price Changes During Session
// Poll for price updatesuseEffect(() => { const interval = setInterval(async () => { const productIds = cart.items.map(i => i.productId); const updates = await api.getPriceUpdates(productIds); for (const update of updates) { if (update.newPrice !== getItemPrice(update.productId)) { dispatch({ type: 'PRICE_CHANGED', productId: update.productId, oldPrice: getItemPrice(update.productId), newPrice: update.newPrice }); } } }, 60000); // Every minute return () => clearInterval(interval);}, [cart.items]);2. Item Goes Out of Stock
// Real-time inventory via WebSocketuseEffect(() => { const ws = new WebSocket('wss://inventory.amazon.com'); ws.onmessage = (event) => { const { productId, inStock, availableQty } = JSON.parse(event.data); const item = cart.items.find(i => i.productId === productId); if (!item) return; if (!inStock) { dispatch({ type: 'MARK_UNAVAILABLE', productId }); toast.error(`${item.name} is now out of stock`); } else if (item.quantity > availableQty) { dispatch({ type: 'REDUCE_QUANTITY', productId, maxQty: availableQty }); toast.warning(`Only ${availableQty} available for ${item.name}`); } }; return () => ws.close();}, []);3. Coupon Validation
const applyCoupon = async (code) => { try { const result = await api.validateCoupon(code, cart); if (!result.valid) { setError(result.reason); return; } if (result.minimumOrder && cart.subtotal < result.minimumOrder) { setError(`Minimum order: โน${result.minimumOrder}`); return; } if (result.excludedProducts.length > 0) { toast.warning('Coupon not applicable to some items'); } dispatch({ type: 'APPLY_COUPON', coupon: result.coupon }); } catch (err) { setError('Failed to apply coupon'); }};4. Cart Merge on Login
const handleLogin = async () => { const guestCart = getGuestCart(); const userCart = await api.getUserCart(); // Merge strategy const mergedItems = []; for (const guestItem of guestCart.items) { const existingItem = userCart.items.find( i => i.productId === guestItem.productId ); if (existingItem) { // Combine quantities (up to max) mergedItems.push({ ...existingItem, quantity: Math.min( existingItem.quantity + guestItem.quantity, existingItem.maxQuantity ) }); } else { mergedItems.push(guestItem); } } // Add user items not in guest cart for (const userItem of userCart.items) { if (!guestCart.items.find(i => i.productId === userItem.productId)) { mergedItems.push(userItem); } } dispatch({ type: 'SET_CART', items: mergedItems }); clearGuestCart();};5. Concurrent Tab Updates
// Sync across tabsuseEffect(() => { const handleStorageChange = (e) => { if (e.key === 'cart' && e.newValue) { const newCart = JSON.parse(e.newValue); dispatch({ type: 'SYNC_FROM_STORAGE', cart: newCart }); } }; window.addEventListener('storage', handleStorageChange); return () => window.removeEventListener('storage', handleStorageChange);}, []);
๐๐ Cart Summary Component
const CartSummary = () => { const { cart } = useCart(); const subtotal = cart.items.reduce( (sum, item) => sum + item.price * item.quantity, 0 ); const discount = cart.appliedCoupons.reduce( (sum, coupon) => sum + calculateDiscount(coupon, cart), 0 ); const shipping = subtotal >= 499 ? 0 : 40; const total = subtotal - discount + shipping; return ( <div className="cart-summary"> <div className="line"> <span>Subtotal ({cart.items.length} items)</span> <span>โน{subtotal.toLocaleString()}</span> </div> {discount > 0 && ( <div className="line discount"> <span>Discount</span> <span>-โน{discount.toLocaleString()}</span> </div> )} <div className="line"> <span>Shipping</span> <span>{shipping === 0 ? 'FREE' : `โน${shipping}`}</span> </div> <div className="line total"> <span>Total</span> <span>โน{total.toLocaleString()}</span> </div> <button onClick={proceedToCheckout} disabled={cart.items.some(i => !i.isAvailable)} > Proceed to Checkout </button> </div> );};
๐๐ Interview Scoring
Result: Strong Hire ๐