Difficulty: Advanced
Design an e-commerce platform like Amazon. Focus on product catalog, cart, checkout, and inventory management.
An e-commerce system combines product discovery, cart management, checkout flow, and order fulfillment.
Core services: 1. Product Catalog: Browse, search, filter products with variants and pricing 2. Cart Service: Add/remove items, calculate totals with discounts 3. Inventory Service: Track stock levels, reserve inventory during checkout 4. Order Service: Process checkout, manage order lifecycle 5. Payment Service: Handle payment processing 6. Search Service: Elasticsearch for product search with filters and facets
Key challenges: - Inventory consistency: Prevent overselling when two users buy the last item simultaneously - Cart abandonment: Handle reserved inventory that is never purchased - Flash sales: Sudden traffic spikes for limited inventory items
// Problem: Two users add the last item to cart simultaneously
// Solution: Reserve inventory during checkout, release on timeout
async function addToCart(userId, productId, quantity) {
// 1. Check available stock (not reserved stock)
const product = await db.products.findById(productId);
const reserved = await redis.get(`reserved:${productId}`) || 0;
const available = product.stock - parseInt(reserved);
if (available < quantity) {
throw new Error('Not enough stock');
}
// 2. Add to cart (no reservation yet)
await db.cartItems.upsert({
userId, productId, quantity
});
}
async function checkout(userId) {
const cartItems = await db.cartItems.findAll({ userId });
// 1. Reserve inventory (atomic operation)
for (const item of cartItems) {
const reserved = await redis.incrby(
`reserved:${item.productId}`, item.quantity
);
// Check if reservation exceeds stock
const product = await db.products.findById(item.productId);
if (reserved > product.stock) {
// Roll back this reservation
await redis.decrby(`reserved:${item.productId}`, item.quantity);
throw new Error(`${product.name} is out of stock`);
}
}
// 2. Set reservation expiry (15 minutes to complete payment)
for (const item of cartItems) {
await redis.expire(`reserved:${item.productId}`, 900);
}
// 3. Create order and process payment
const order = await createOrder(userId, cartItems);
return order;
}
// On successful payment: deduct actual stock
// On payment failure or timeout: release reservation
Inventory reservation prevents overselling. Redis atomic operations ensure concurrent checkouts cannot exceed available stock. Reservations expire after 15 minutes to prevent inventory lockup.
// Order lifecycle state machine
const ORDER_TRANSITIONS = {
PENDING: ['CONFIRMED', 'CANCELLED'],
CONFIRMED: ['PROCESSING', 'CANCELLED'],
PROCESSING: ['SHIPPED', 'CANCELLED'],
SHIPPED: ['DELIVERED', 'RETURNED'],
DELIVERED: ['RETURNED', 'COMPLETED'],
COMPLETED: [],
CANCELLED: [],
RETURNED: ['REFUNDED'],
REFUNDED: []
};
class OrderService {
async transitionOrder(orderId, newStatus) {
const order = await db.orders.findById(orderId);
const allowedTransitions = ORDER_TRANSITIONS[order.status];
if (!allowedTransitions.includes(newStatus)) {
throw new Error(
`Cannot transition from ${order.status} to ${newStatus}`
);
}
// Perform side effects based on transition
switch (newStatus) {
case 'CONFIRMED':
await this.deductInventory(order);
await this.chargePayment(order);
break;
case 'SHIPPED':
await this.createShipment(order);
await this.notifyCustomer(order, 'Your order has shipped!');
break;
case 'CANCELLED':
await this.releaseInventory(order);
await this.refundPayment(order);
break;
case 'DELIVERED':
await this.notifyCustomer(order, 'Your order was delivered!');
break;
}
// Update order status
await db.orders.update(orderId, {
status: newStatus,
statusHistory: {
push: { status: newStatus, timestamp: new Date() }
}
});
}
}
A state machine enforces valid order transitions and triggers appropriate side effects (payment, shipping, notifications) at each step. The status history provides a full audit trail.
// Elasticsearch index for products
// PUT /products
{
"mappings": {
"properties": {
"name": { "type": "text", "analyzer": "standard" },
"description": { "type": "text" },
"category": { "type": "keyword" },
"brand": { "type": "keyword" },
"price": { "type": "float" },
"rating": { "type": "float" },
"inStock": { "type": "boolean" },
"tags": { "type": "keyword" }
}
}
}
// Search with filters and facets
async function searchProducts(query, filters, page = 0) {
const result = await elasticsearch.search({
index: 'products',
body: {
query: {
bool: {
must: [
{ multi_match: { query, fields: ['name^3', 'description', 'tags'] } }
],
filter: [
filters.category && { term: { category: filters.category } },
filters.minPrice && { range: { price: { gte: filters.minPrice } } },
filters.maxPrice && { range: { price: { lte: filters.maxPrice } } },
{ term: { inStock: true } }
].filter(Boolean)
}
},
aggs: {
categories: { terms: { field: 'category', size: 20 } },
brands: { terms: { field: 'brand', size: 20 } },
price_ranges: {
range: {
field: 'price',
ranges: [
{ to: 50 }, { from: 50, to: 200 },
{ from: 200, to: 500 }, { from: 500 }
]
}
}
},
from: page * 20,
size: 20,
sort: [{ _score: 'desc' }, { rating: 'desc' }]
}
});
return {
products: result.hits.hits.map(h => h._source),
facets: result.aggregations,
total: result.hits.total.value
};
}
Elasticsearch powers fast full-text product search with filtering and faceted navigation (category counts, price ranges). The name field is boosted 3x to prioritize title matches.
Inventory Management, Cart, Checkout, Order Processing, Product Catalog