Course Roadmap: What We'll Build & Learn
A comprehensive, beginner-friendly masterclass on designing scalable distributed systems. Learn High-Level Design (HLD), horizontal scaling, stateless load balancers, CDN edge caching, B-Tree database indexing, Redis caching tiers, message queues, read replicas, database sharding, and consistent hashing by scaling a photo-sharing application from 1 to 50M+ users.
What You Need Before Starting:
- Basic understanding of web architecture (clients, HTTP requests, APIs, and databases)
- Curiosity to learn how web applications scale from a single server to millions of active users
- No prior distributed systems experience required — all concepts are introduced step-by-step with intuitive diagrams
What is System Design? From Single-Server to Distributed Scale
The engineering blueprint for designing resilient, high-throughput web architectures.
When building modern software, developers have access to a vast array of technologies: web servers, relational and NoSQL databases, in-memory caches, message queues, and global content delivery networks. However, technology alone does not solve scalability challenges.
System design is the skill of choosing the right architectural components, placing them in the correct locations, and defining clean interfaces between them to satisfy product requirements reliably under heavy load.
Rather than studying abstract concepts in isolation, this masterclass follows the evolution of a real-world photo-sharing platform. We start with a $10 single-server baseline, observe where bottlenecks emerge as traffic surges, and systematically scale each tier to support over 50 million active users.
Traditional Methods vs. System Design
Scaling Philosophy
State & Session Handling
Data Layer Architecture
The 6 Core Superpowers We Will Master
Stateless Horizontal Compute
Distribute HTTP traffic across elastic server fleets using Layer 4/7 load balancers and health checks.
Decoupled Blob Storage & CDNs
Offload gigabytes of image and video media to S3 object stores and cache static assets at global edge POPs.
In-Memory Redis Caching
Implement Cache-Aside and Write-Through caching patterns to serve hot reads in sub-millisecond latencies.
B-Tree Database Indexing
Transform slow O(N) table scans into logarithmic O(log N) lookups with composite indexing strategies.
Primary-Replica & DB Sharding
Scale database read throughput with replicas and partition massive datasets across shards using consistent hashing.
Asynchronous Message Queues
Decouple heavy tasks (image compression, push notifications) using message queues and background worker fleets.
Quick Cheat Sheet: Essential Commands
Keep these core syntax triggers handy as you follow the walkthrough modules below:
| Command / Trigger | What It Does |
|---|---|
Horizontal Scaling | Scale out by adding stateless server instances behind a Load Balancer (Round-Robin / Least Connections). |
Blob Storage (S3) | Store media files (images, videos) in object storage; keep only metadata and URLs in relational DB. |
CDN Edge Caching | Cache static assets and popular media at global Points of Presence (POPs) near users. |
Database Index (B-Tree) | Accelerates SELECT queries from O(N) full-table scan to O(log N) binary tree search. |
Cache-Aside (Redis) | App checks Redis first. On cache miss, fetch from DB, write to Redis with TTL, and return. |
Read Replicas | Primary DB handles writes; multiple replica DBs handle read queries via asynchronous replication. |
Database Sharding | Partition large database tables across multiple independent database nodes using a shard key. |
Message Queues | Decouple synchronous HTTP request handling from background worker processing (RabbitMQ, Kafka, SQS). |
System Design Fundamentals & The Single-Server Baseline
Understand the core responsibilities of High-Level Design (HLD), why single-server architectures fail, and how resource bottlenecks develop.
System Design is the discipline of selecting software components (web servers, databases, caches, queues) and arranging them to solve business requirements reliably and at scale. In early-stage development, putting the entire stack—web server, database, and uploaded media files—on a single $10/month virtual server is simple and cost-effective. However, as traffic grows, all components compete for the same physical CPU, RAM, disk space, and network bandwidth. When traffic spikes, the server's CPU hits 100%, disk I/O saturates, and the entire platform crashes. System design is the methodical process of resolving these bottlenecks one component at a time.
1Step 1: Inspect Single-Server Resource Contention
$ Top Process Monitor: 1 CPU Core, 2 GB RAM2Step 2: Transition from Junior to Senior Architectural Thinking
$ Evaluate System Scope: Junior vs Mid-Level vs Senior EngineerArchitectural evolution roadmap transitioning a single-server monolith into an elastic distributed system.
// The Evolution of a Scalable System
// Phase 1: Single Server (Monolith + Local DB + Local Files) -> Bottleneck: Shared RAM/CPU
// Phase 2: Decoupled Storage (Stateless App Server + Managed DB + Object Storage S3)
// Phase 3: Horizontal App Scaling (Load Balancer + N App Servers)
// Phase 4: Edge Acceleration (CDN for Global Media Delivery)
// Phase 5: Caching & Indexing (Redis Cache-Aside + Database B-Tree Indexes)
// Phase 6: Async Decoupling (Message Queue + Worker Fleet for Media Resizing)
// Phase 7: Database Scaling (Primary-Replica Read Clusters + Horizontal Sharding)
export interface SystemArchitectureTier {
tierName: string;
primaryRole: string;
scalingStrategy: "vertical" | "horizontal";
bottlenecks: string[];
}Do:Start simple with a modular monolith before adopting distributed microservices
Begin with a clean modular architecture and separate components as traffic demands, rather than introducing distributed complexity prematurely.
Avoid:Storing user uploads and database files on the web server's ephemeral root drive
Local disk storage blocks horizontal scaling and risks permanent data loss if the server instance is replaced or restarted.
Pro Tip for Beginners
Common Beginner Pitfall & How to Fix It
What happens: Prematurely introducing distributed microservices for a prototype
How to solve it: Design clean boundaries in a modular codebase first; decouple the database and storage before splitting application services.
Hands-On Practice Checklist
Try each of these steps on your computer and check them off as you complete them:
Requirement Engineering & Back-of-the-Envelope Estimation
Master Functional vs Non-Functional Requirements, capacity estimation math, and sizing QPS, storage throughput, and bandwidth constraints.
Every successful system design starts by establishing clear boundaries. Functional Requirements define what the system must do (e.g., users can upload photos, view a home feed, follow creators, and search tags). Non-Functional Requirements define how the system performs (e.g., P95 latency < 200ms, 99.99% availability, eventual consistency for social feeds, strong consistency for billing). Back-of-the-envelope calculations translate user metrics (Daily Active Users, read/write ratios) into technical infrastructure requirements like Queries Per Second (QPS), network bandwidth (MB/s), and multi-year storage capacity.
1Step 1: Compute Read vs Write Queries Per Second (QPS)
$ Inputs: 10 Million Daily Active Users (DAU). Read:Write Ratio = 100:1. Daily Photos = 1M uploads.2Step 2: Calculate Multi-Year Storage & Bandwidth Requirements
$ Inputs: 1M photos/day @ 2 MB average photo sizeStandard back-of-the-envelope estimation utility modeling QPS, peak multipliers, and storage trajectories.
// Back-of-the-Envelope Estimation Formula Standards
export function estimateSystemScale(dau: number, avgUploadsPerUser: number, avgPhotoSizeMB: number) {
const SECONDS_PER_DAY = 86400;
const dailyUploads = dau * avgUploadsPerUser;
const writeQPS = Math.ceil(dailyUploads / SECONDS_PER_DAY);
const peakWriteQPS = writeQPS * 2; // Standard 2x peak traffic multiplier
const readQPS = writeQPS * 50; // Assuming 50:1 read-to-write ratio
const peakReadQPS = readQPS * 2;
const dailyStorageGB = (dailyUploads * avgPhotoSizeMB) / 1024;
const yearlyStorageTB = (dailyStorageGB * 365) / 1024;
return {
writeQPS,
peakWriteQPS,
readQPS,
peakReadQPS,
dailyStorageGB: Math.round(dailyStorageGB),
yearlyStorageTB: Math.round(yearlyStorageTB)
};
}Do:Round numbers aggressively during interview calculations
Use 1 day ≈ 100,000 seconds (or 86,400) and powers of 10 to perform quick mental math without getting bogged down in fractions.
Avoid:Jumping into database schemas before establishing scale constraints
A system designed for 100 requests/day requires fundamentally different architectural tradeoffs than one handling 100,000 QPS.
Pro Tip for Beginners
Common Beginner Pitfall & How to Fix It
What happens: Forgetting peak traffic multipliers during capacity planning
How to solve it: Always design infrastructure for peak load (typically 2x to 3x average daily traffic) to prevent outages during viral spikes.
Hands-On Practice Checklist
Try each of these steps on your computer and check them off as you complete them:
Decoupling Compute, Blob Storage & Media Offloading
Learn why relational databases should never store binary image data, how Object/Blob Storage (S3) works, and how pre-signed URLs optimize uploads.
When building a photo-sharing application, a critical early mistake is storing raw image files inside relational database BLOB columns or directly on the web server's local hard drive. Relational databases are optimized for structured rows, indexing, and transactional ACID queries—storing megabyte-sized binary files bloats database page buffers, degrades cache hit rates, and exhausts backup bandwidth. The first major architectural decoupling is moving media to a specialized Object/Blob Store (such as Amazon S3, Google Cloud Storage, or MinIO). The database stores only lightweight metadata (Photo ID, User ID, Timestamp, S3 Object Key URL), while binary files are offloaded to high-durability object storage.
1Step 1: Compare Database Rows vs Object Store Blobs
$ Inspect Schema Structure for Photo Upload2Step 2: Prevent Server Saturation with Pre-Signed Upload URLs
$ Client Upload Flow: Client -> Web Server -> S3Direct-to-S3 pre-signed URL generator allowing clients to upload binary media directly to object storage.
// Decoupled Storage Architecture: Direct-to-S3 Upload Handler
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
const s3 = new S3Client({ region: "us-east-1" });
export async function generateUploadUrl(userId: string, filename: string) {
const objectKey = `uploads/${userId}/${Date.now()}_${filename}`;
const command = new PutObjectCommand({
Bucket: "production-photo-storage",
Key: objectKey,
ContentType: "image/jpeg"
});
// Generate 15-minute temporary pre-signed upload URL
const uploadUrl = await getSignedUrl(s3, command, { expiresIn: 900 });
return {
uploadUrl,
objectKey,
publicUrl: `https://cdn.photoapp.com/${objectKey}`
};
}Do:Store only metadata and file URLs in SQL/NoSQL databases
Keep database records compact (hundreds of bytes) to maximize the number of index pages that fit inside RAM.
Avoid:Streaming multi-megabyte file uploads through your application server memory
Buffering large file uploads inside Node.js/Python server memory exhausts thread pools and causes high request queuing.
Pro Tip for Beginners
Common Beginner Pitfall & How to Fix It
What happens: Allowing public write access to storage buckets
How to solve it: Always keep storage buckets private and issue short-lived pre-signed URLs with strict content-type validations.
Hands-On Practice Checklist
Try each of these steps on your computer and check them off as you complete them:
Horizontal Scaling, Stateless Servers & Load Balancers
Master the shift from vertical to horizontal scaling, stateless server architectures, Layer 4 vs Layer 7 load balancers, and health checks.
Vertical Scaling (Scale-Up) means replacing a server with a more powerful machine (e.g. 64 CPUs, 256 GB RAM). While simple, vertical scaling has severe physical hardware limits, exponential costs, and still represents a catastrophic single point of failure. Horizontal Scaling (Scale-Out) solves this by running multiple identical, lightweight application servers behind a Load Balancer. For horizontal scaling to work, servers must be completely Stateless: no user sessions or temporary files can live on local disk. A Load Balancer sits between incoming user traffic and the server pool, health-checking instances and distributing requests using algorithms like Round-Robin, Least Connections, or IP Hash.
1Step 1: Contrast Layer 4 vs Layer 7 Load Balancing
$ Evaluate Load Balancer Types (L4 Transport vs L7 Application)2Step 2: Understand Server Health Checking & Failover
$ Simulate Server Failure in 3-Node Cluster [App 1, App 2, App 3]Round-Robin and Least Connections load balancing algorithms distributing requests across healthy instances.
// Load Balancer Routing Algorithm Implementations
export class LoadBalancer {
private servers: { id: string; isHealthy: boolean; activeConnections: number }[] = [];
private currentIndex: number = 0;
addServer(id: string) {
this.servers.push({ id, isHealthy: true, activeConnections: 0 });
}
// 1. Round-Robin Routing
getRoundRobinServer(): string | null {
const healthyServers = this.servers.filter(s => s.isHealthy);
if (healthyServers.length === 0) return null;
const server = healthyServers[this.currentIndex % healthyServers.length];
this.currentIndex++;
return server.id;
}
// 2. Least Connections Routing
getLeastConnectionsServer(): string | null {
const healthyServers = this.servers.filter(s => s.isHealthy);
if (healthyServers.length === 0) return null;
return healthyServers.reduce((prev, curr) =>
curr.activeConnections < prev.activeConnections ? curr : prev
).id;
}
}Do:Store user sessions in an external shared cache like Redis
Keeping sessions in Redis allows any app server instance to authenticate any incoming user request seamlessly.
Avoid:Relying on sticky sessions (session affinity) if avoidable
Sticky sessions bind users to specific servers, leading to uneven traffic distribution and lost sessions when instances scale down or crash.
Pro Tip for Beginners
Common Beginner Pitfall & How to Fix It
What happens: Hardcoding server IP addresses inside client applications
How to solve it: Always route client traffic through DNS domain names pointing to resilient Load Balancer VIPs.
Hands-On Practice Checklist
Try each of these steps on your computer and check them off as you complete them:
Edge Acceleration & Content Delivery Networks (CDNs)
Understand Content Delivery Networks (CDNs), Points of Presence (POPs), Origin Pull mechanics, and edge caching strategies for static and media assets.
A Content Delivery Network (CDN) is a globally distributed network of edge proxy servers (Points of Presence or POPs) strategically placed close to users worldwide. In a photo-sharing app without a CDN, a user in Tokyo requesting an image hosted in a Virginia data center suffers a 200ms+ round-trip network latency across oceans. With a CDN (like Cloudflare, CloudFront, or Fastly), the photo is cached at the Tokyo edge server after the first request. Subsequent users in Tokyo receive the photo in under 10ms directly from their local edge server. This edge caching dramatically accelerates perceived page load speeds and offloads up to 90% of bandwidth traffic from your origin servers.
1Step 1: Trace CDN Origin Pull & Cache Hit Workflow
$ User in Tokyo requests 'https://cdn.photoapp.com/photos/p100.jpg'2Step 2: Understand Cache Invalidation & Fingerprinted URLs
$ Update Profile Avatar: User uploads new avatar photoConfiguring production Cache-Control headers for immutable media and dynamic API endpoints.
// Express / Next.js Response Headers for CDN Optimization
export function setCdnHeaders(res: any, assetType: "immutable_media" | "dynamic_api") {
if (assetType === "immutable_media") {
// Cache at edge CDN and browser for 1 full year; immutable prevents revalidation
res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
} else {
// Dynamic user API: Cache for 60s at edge, must revalidate with origin
res.setHeader("Cache-Control", "public, s-maxage=60, stale-while-revalidate=30");
}
}Do:Use content-hash fingerprinting (cache busting) for uploaded media URLs
Appending file hashes (e.g., `photo_a8f9c1.jpg`) allows aggressive 1-year caching without worrying about stale cache invalidation.
Avoid:Serving all static images and video clips directly from application servers
Application servers should never spend CPU cycles and bandwidth serving static files; delegate all static media to CDNs and S3.
Pro Tip for Beginners
Common Beginner Pitfall & How to Fix It
What happens: Setting indefinite TTLs on mutable URLs without a cache-invalidation strategy
How to solve it: Always pair long TTLs with immutable fingerprinted URLs or configure automated CDN purge webhooks on asset updates.
Hands-On Practice Checklist
Try each of these steps on your computer and check them off as you complete them:
Database Performance & B-Tree Indexing Mechanics
Master database indexing fundamentals, how B-Trees accelerate lookups from O(N) to O(log N), composite indexes, and write amplification trade-offs.
As your photo-sharing platform grows to millions of rows, database queries without indexes grind to a halt. Without an index on `user_id`, finding all photos uploaded by Alan requires a Full Table Scan: the database reads every single disk block in the table one by one ($O(N)$ Disk I/O). A Database Index is a separate auxiliary data structure—most commonly a self-balancing B-Tree—that keeps sorted copies of the indexed column pointing to the underlying table rows. Searching a B-Tree takes logarithmic $O(log N)$ time, turning a 5-second query across 10 million rows into a 2-millisecond indexed lookup. However, indexes are not free: every INSERT, UPDATE, and DELETE must update the B-Tree, adding write overhead.
1Step 1: Contrast Full Table Scan vs B-Tree Index Lookup
$ Query: SELECT * FROM photos WHERE user_id = 'user_123' (Table Size: 10,000,000 rows)2Step 2: Understand Composite Index Column Ordering
$ Query: SELECT * FROM photos WHERE user_id = 'u1' AND created_at > '2026-01-01'PostgreSQL schema featuring foreign keys and composite indexes for fast user timeline queries.
-- Relational Schema & Indexing Strategy for Photo App CREATE TABLE users ( id UUID PRIMARY KEY, username VARCHAR(50) UNIQUE NOT NULL, email VARCHAR(255) UNIQUE NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE photos ( id UUID PRIMARY KEY, user_id UUID NOT NULL REFERENCES users(id), s3_url VARCHAR(500) NOT NULL, caption TEXT, likes_count INT DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- Crucial Secondary Indexes for Fast Feed Queries CREATE INDEX idx_photos_user_created ON photos (user_id, created_at DESC); CREATE INDEX idx_photos_created_at ON photos (created_at DESC);
Do:Index columns frequently used in WHERE, JOIN, and ORDER BY clauses
Analyze production query logs (`EXPLAIN ANALYZE`) to identify slow sequential scans and add targeted indexes.
Avoid:Adding indexes blindly to every single column in a table
Every index consumes disk space and slows down INSERT/UPDATE operations because all B-Trees must be synchronously updated.
Pro Tip for Beginners
Common Beginner Pitfall & How to Fix It
What happens: Violating the Leftmost Prefix Rule in composite indexes
How to solve it: An index on `(user_id, created_at)` can accelerate queries on `user_id` alone, but cannot accelerate queries filtering only on `created_at`.
Hands-On Practice Checklist
Try each of these steps on your computer and check them off as you complete them:
In-Memory Caching Layers (Redis / Memcached)
Master in-memory caching patterns (Cache-Aside, Write-Through, Write-Back), eviction policies (LRU/LFU), TTLs, and cache stampede prevention.
Even with B-Tree indexes, reading from disk or traversing database tables under 100,000 QPS will overwhelm database connection pools. In-Memory Caching (using Redis or Memcached) places an ultra-fast key-value store in RAM between your application servers and database. Because RAM access takes microseconds compared to milliseconds for disk I/O, caching hot data—such as a celebrity's user profile or trending photo feeds—drops database load by 80-95%. The most popular architectural pattern is Cache-Aside (Lazy Loading): the application queries Redis first; on a cache miss, it reads from the database, writes the result to Redis with a Time-To-Live (TTL), and returns the response.
1Step 1: Execute the Cache-Aside (Lazy Loading) Read Flow
$ User requests Profile for 'creator_88'2Step 2: Understand Cache Invalidation on Data Mutation
$ Creator updates Bio descriptionProduction Cache-Aside helper with automatic JSON serialization and 1-hour TTL expiration.
// Redis Cache-Aside Pattern Implementation
import Redis from "ioredis";
const redis = new Redis();
export async function getUserProfile(userId: string, dbQueryFn: (id: string) => Promise<any>) {
const cacheKey = `user:profile:${userId}`;
// 1. Try fetching from in-memory Redis cache
const cachedData = await redis.get(cacheKey);
if (cachedData) {
return JSON.parse(cachedData); // Cache Hit: Sub-millisecond return
}
// 2. Cache Miss: Fetch from primary database
const dbData = await dbQueryFn(userId);
if (!dbData) return null;
// 3. Populate Redis with 1-hour expiration (TTL = 3600 seconds)
await redis.setex(cacheKey, 3600, JSON.stringify(dbData));
return dbData;
}Do:Always assign a Time-To-Live (TTL) expiration to every cached key
TTLs prevent dead data from lingering in RAM indefinitely and act as a safety net against missed cache invalidation events.
Avoid:Treating Redis as your source of truth for permanent storage
Caches are volatile and subject to LRU eviction during memory pressure; the primary relational database remains the permanent source of truth.
Pro Tip for Beginners
Common Beginner Pitfall & How to Fix It
What happens: Cache Penetration (repeated queries for non-existent keys hitting the database)
How to solve it: Cache null results with short TTLs (e.g. 30 seconds) or use Bloom Filters to verify key existence before querying the database.
Hands-On Practice Checklist
Try each of these steps on your computer and check them off as you complete them:
Asynchronous Processing, Message Queues & Event-Driven Workers
Master synchronous vs asynchronous processing, Message Queues (RabbitMQ, Kafka, SQS), worker fleets, and decoupled event architectures.
When a user uploads a high-resolution 10 MB photo, the application needs to perform multiple heavy operations: generate thumbnail previews (small, medium, large), strip EXIF metadata, run AI content moderation, and push notifications to followers. If handled Synchronously within the HTTP upload request, the user's browser hangs for 6 to 10 seconds waiting for a response. Asynchronous Processing decouples long-running work: the web server accepts the photo, saves the metadata, publishes a lightweight job message to a Message Queue (such as RabbitMQ, Apache Kafka, or AWS SQS), and immediately returns `202 Accepted` to the user in under 50ms. Dedicated background worker fleets consume messages from the queue and process thumbnails asynchronously at their own pace.
1Step 1: Trace Synchronous vs Asynchronous Photo Upload Lifecycle
$ Compare HTTP Request Lifecycles2Step 2: Handle Worker Failures with Dead-Letter Queues (DLQ)
$ Simulate Worker Crash during Image CompressionEvent-driven asynchronous upload architecture decoupling HTTP response latency from background worker processing.
// Asynchronous Message Queue Event Architecture
export interface PhotoProcessingJob {
photoId: string;
s3RawUrl: string;
userId: string;
timestamp: number;
}
// 1. Web Server Handler: Fast enqueue & immediate HTTP 202 response
export async function handlePhotoUpload(req: any, res: any, queueClient: any) {
const { photoId, s3RawUrl, userId } = req.body;
// Publish event to background message queue
await queueClient.publish("photo.processing", {
photoId,
s3RawUrl,
userId,
timestamp: Date.now()
});
return res.status(202).json({
status: "processing",
message: "Photo accepted. Thumbnails are being generated."
});
}
// 2. Background Worker Fleet Consumer
export async function startWorkerConsumer(queueClient: any, imageProcessor: any) {
queueClient.subscribe("photo.processing", async (job: PhotoProcessingJob) => {
console.log(`[Worker] Processing thumbnails for photo: ${job.photoId}`);
await imageProcessor.generateThumbnails(job.s3RawUrl);
await imageProcessor.notifyFollowers(job.userId, job.photoId);
});
}Do:Make all background worker job handlers idempotent
Because distributed message queues guarantee at-least-once delivery, workers must handle duplicate message deliveries without corrupting data.
Avoid:Executing blocking external API calls directly in the main HTTP request thread
Sending emails, encoding video, or notifying third-party webhooks must always be offloaded to background message queues.
Pro Tip for Beginners
Common Beginner Pitfall & How to Fix It
What happens: Infinite retry loops on corrupted or unparseable job payloads
How to solve it: Implement strict retry count limits (e.g. max 3 retries) before routing failed jobs to a Dead-Letter Queue (DLQ).
Hands-On Practice Checklist
Try each of these steps on your computer and check them off as you complete them:
Database Scaling: Read Replicas & Horizontal Sharding
Master Primary-Replica (Master-Slave) replication, replication lag, horizontal database sharding, shard key selection, and consistent hashing.
When a platform reaches 50+ million users, even indexed and cached databases hit hard architectural ceilings. Scaling databases happens in two distinct phases: Phase 1: Read Replicas. Because social apps are 90%+ read-heavy, we split database traffic. One Primary database handles all write operations (INSERT, UPDATE, DELETE) and asynchronously replicates transactions to multiple Read Replicas that handle SELECT queries. Phase 2: Horizontal Sharding. When total data exceeds terabytes and writes exceed a single primary's capacity, we partition tables horizontally across multiple independent database clusters using a Shard Key (e.g., `user_id % 4`). Consistent Hashing ensures data is evenly distributed without hot shard bottlenecks.
1Step 1: Scale Reads with Primary-Replica Replication
$ Architecture: 1 Primary DB (Writes) + 3 Read Replicas (Reads)2Step 2: Scale Writes with Horizontal Database Sharding
$ Partition 100 Million Users across 4 DB Shards using ShardKey = user_idDatabase shard router mapping user IDs deterministically to target database clusters.
// Database Sharding Router using Shard Key Hashing
import crypto from "crypto";
export class DatabaseShardRouter {
private shardEndpoints: string[];
constructor(shardEndpoints: string[]) {
this.shardEndpoints = shardEndpoints;
}
// Deterministically routes user operations to the correct DB shard
getShardForUser(userId: string): string {
const hash = crypto.createHash("md5").update(userId).digest("hex");
// Convert first 8 hex characters to integer
const hashNum = parseInt(hash.substring(0, 8), 16);
const shardIndex = hashNum % this.shardEndpoints.length;
return this.shardEndpoints[shardIndex];
}
}Do:Choose high-cardinality shard keys with uniform distribution (e.g. user_id)
A good shard key distributes data and query traffic evenly across all database partitions without creating hot shards.
Avoid:Cross-shard JOIN operations across multiple database nodes
Cross-shard joins require distributed network coordination that degrades performance exponentially. Denormalize data or query in application code.
Pro Tip for Beginners
Common Beginner Pitfall & How to Fix It
What happens: Replication Lag causing read-after-write inconsistency
How to solve it: If a user updates their profile and immediately refreshes the page, route their specific read directly to the Primary DB for the first 5 seconds.
Hands-On Practice Checklist
Try each of these steps on your computer and check them off as you complete them:
The Master System Design Interview Framework & End-to-End Architecture
Master the complete production-grade system architecture and the 5-step structured interview walkthrough framework for senior engineering rounds.
In a 45-minute technical system design interview, success is not just about knowing tools—it is about demonstrating structured problem-solving, identifying trade-offs, and driving the conversation methodically. This module consolidates our entire journey into the complete production blueprint: Global Anycast DNS → Cloudflare CDN Edge → Layer 7 Application Load Balancer → Auto-scaling Stateless Web Fleets → In-Memory Redis Cache Clusters → Primary/Replica Sharded Relational Databases → S3 Blob Storage → Asynchronous Message Queues & Thumbnail Worker Fleets.
1Step 1: Review the Complete 10-Tier Scaled Production Architecture
$ Trace Full End-to-End System Data Flow2Step 2: Master the 5-Step System Design Interview Framework
$ The 45-Minute Interview RoadmapConsolidated reference summary of all 10 architectural tiers in modern scalable system design.
// SYSTEM DESIGN MASTER ARCHITECTURE COMPONENT MATRIX // ------------------------------------------------------------------------------------------------ // Tier | Technology Examples | Primary Scaling Role // ------------------------------------------------------------------------------------------------ // DNS & Edge CDN | Cloudflare, CloudFront | Global Anycast routing & static media caching // Load Balancing | NGINX, AWS ALB, Envoy | Distribute HTTP traffic & active health checks // Application Tier | Node.js, Go, Python (Docker)| Stateless compute & business logic execution // In-Memory Cache | Redis, Memcached | Sub-millisecond Cache-Aside read acceleration // Object Storage | Amazon S3, MinIO, GCS | High-durability (11 9s) binary photo storage // Relational DB | PostgreSQL, MySQL | ACID transactions & structured metadata // DB Read Replicas | Read-only DB Instances | Scale read throughput linearly across regions // Database Sharding | Citus, Vitess, Manual Hash | Scale write throughput and partition petabytes // Message Queues | RabbitMQ, Apache Kafka, SQS | Asynchronous task decoupling & burst smoothing // Worker Fleet | Background Consumers | Heavy thumbnail generation & push notifications // ------------------------------------------------------------------------------------------------
Do:Lead the conversation and proactively discuss architectural trade-offs
System design has no single 'perfect' answer. Always explain WHY you chose a specific component (e.g. SQL vs NoSQL, Redis vs Memcached) given the constraints.
Avoid:Staying silent or drawing complex architecture diagrams without explaining your thought process
Continuously verbalize your reasoning, check in with the interviewer, and validate assumptions.
Pro Tip for Beginners
Common Beginner Pitfall & How to Fix It
What happens: Getting defensive when the interviewer introduces a failure scenario
How to solve it: Embrace failure scenarios as an opportunity to demonstrate resilience mechanisms (circuit breakers, fallbacks, and dead-letter queues).
Hands-On Practice Checklist
Try each of these steps on your computer and check them off as you complete them:
Masterclass Starter Files & Templates
System Design Scalable Architecture Blueprint Pack
Includes high-resolution architecture diagrams, capacity estimation calculators, Redis cache-aside boilerplates, and standard system design interview checklists.