Table of Contents
- Why System Design Mock Interviews Matter
- How to Structure a System Design Mock Interview
- Practice Questions and Detailed Answers
- How to Practice Effectively
- Common Mistakes to Avoid
- Conclusion
- References
Why System Design Mock Interviews Matter
System design interviews are notoriously open-ended. Unlike coding interviews, there’s no single “correct” answer—success depends on how you approach the problem: clarifying requirements, prioritizing constraints, and justifying trade-offs. Mock interviews help you:
- Simulate pressure: Real interviews have time limits (typically 45–60 minutes). Mock sessions train you to stay calm and focused under time constraints.
- Refine communication: Interviewers evaluate not just your technical skills, but your ability to explain complex ideas to non-technical stakeholders. Mock interviews help you practice articulating decisions.
- Identify blind spots: A peer or mentor can point out gaps (e.g., ignoring scalability, overlooking data consistency) that you might miss alone.
- Build confidence: Regular practice turns vague concepts (e.g., “use a cache”) into concrete, actionable strategies (e.g., “use Redis with a TTL of 1 hour for frequent queries”).
How to Structure a System Design Mock Interview
A typical system design interview follows this flow. Use this structure in your mock sessions to mirror real-world scenarios:
1. Requirements Clarification (5–10 mins)
Start by asking questions to define the problem scope:
- Functional requirements: What features must the system support? (e.g., “Does the URL shortener need custom short links?”)
- Non-functional requirements: What are the scalability, reliability, and performance constraints? (e.g., “How many users? What’s the expected QPS?”)
- Edge cases: What about rare scenarios? (e.g., “What happens if a user uploads a 10GB file?”)
2. High-Level Design (10–15 mins)
Outline the core components of your system using a diagram. Focus on:
- Major services (e.g., “user service,” “feed service”).
- Data flow (e.g., “user posts → API → database → cache → client”).
- Technology choices (e.g., “use PostgreSQL for relational data, Redis for caching”).
3. Detailed Design (15–20 mins)
Dive into specific components:
- Database schema (e.g., “users table: user_id (PK), name, email”).
- API endpoints (e.g.,
POST /api/shortenfor URL shortening). - Algorithms or protocols (e.g., “use WebSockets for real-time chat”).
4. Scalability & Trade-Offs (5–10 mins)
Discuss how the system handles growth:
- Scaling strategies: Horizontal vs. vertical scaling, sharding, replication.
- Trade-offs: Consistency vs. availability (CAP theorem), latency vs. cost (e.g., using a CDN adds cost but reduces latency).
Practice Questions and Detailed Answers
Below are 5 common system design questions, with step-by-step answers to guide your mock sessions.
Question 1: Design a URL Shortener
Scenario: Build a service that converts long URLs (e.g., https://example.com/blog/system-design) into short URLs (e.g., https://short.xyz/abc123).
Step 1: Requirements Clarification
- Functional: Shorten URLs, redirect short URLs to original, (optional) custom short links, analytics (clicks per URL).
- Non-functional: Low latency (redirects must be fast), high availability (99.99% uptime), scalability (100M new URLs/month, 10B redirects/month).
- Constraints: Short URLs must be unique and as short as possible (6–8 characters).
Step 2: High-Level Design
Key components:
- API Layer: Handles
POST /shorten(create short URL) andGET /{short_code}(redirect). - URL Generator: Creates unique short codes (e.g.,
abc123). - Database: Stores mappings between short codes and original URLs.
- Cache: Speeds up redirects by caching frequent short codes.
Step 3: Detailed Design
URL Generation
To create short codes, use base62 encoding:
- Map each short code to a unique integer ID (e.g.,
123456→dnhin base62). - Use an auto-incrementing counter (e.g., a database sequence) to generate unique IDs.
- Why base62? It uses 62 characters (a-z, A-Z, 0-9), so 6 characters = 62⁶ ≈ 56 billion unique URLs—enough for most use cases.
Edge case: Hash collisions (two long URLs mapping to the same short code). Mitigate by checking the database for existing codes and regenerating if needed.
Database Schema
Use a NoSQL database (e.g., MongoDB) for scalability, or a relational database (e.g., PostgreSQL) with sharding. Schema:
Table: url_mappings
- short_code (PK): string (6 chars)
- original_url: string (long)
- created_at: timestamp
- user_id: (optional, for custom links)
Caching
Most redirects are read-heavy (10B redirects/month). Use Redis to cache frequent short codes with a TTL (e.g., 24 hours). This reduces database load and speeds up redirects.
API Endpoints
POST /api/shorten: Acceptsoriginal_urland optionalcustom_code. Returnsshort_url.GET /{short_code}: Looks upshort_codein cache → if miss, check database → redirect with301 Moved Permanently(for SEO) or302 Found(temporary).
Step 4: Scalability & Trade-Offs
- Scaling the database: Shard by
short_code(e.g., split into 62 shards, one per first character) to distribute load. - Handling traffic spikes: Use a load balancer (e.g., NGINX) to distribute requests across multiple API servers.
- Trade-offs:
301vs.302:301(permanent) is cached by browsers, reducing server load, but analytics will undercount (since browsers skip the redirect). Use302if accurate click tracking is critical.- Custom short codes: Increase user satisfaction but require checking for duplicates (adds latency).
Question 2: Design a Social Media Feed (e.g., Twitter Timeline)
Scenario: Build a system that displays a user’s feed of posts from accounts they follow (e.g., “Your Home Timeline”).
Step 1: Requirements Clarification
- Functional: Post tweets, view home timeline, like/retweet posts.
- Non-functional: Low latency (timeline loads in <200ms), high availability, scalability (100M daily active users, 500M tweets/day).
- Constraints: Users follow up to 5000 accounts; timeline should show recent posts first (sorted by timestamp).
Step 2: High-Level Design
Key components:
- Post Service: Handles creating/retrieving tweets.
- Timeline Service: Generates and caches user timelines.
- Database: Stores tweets, user data, and follow relationships.
Step 3: Detailed Design
Timeline Generation: Push vs. Pull Model
Two approaches to build timelines:
-
Pull model (on-demand): When a user loads their timeline, fetch recent tweets from all followed accounts and merge them.
- Pros: Simple, no precomputation.
- Cons: Slow for users with many followers (e.g., a user following 5000 accounts must query 5000 data sources).
-
Push model (precomputed): When a user posts a tweet, push it to the timelines of all their followers.
- Pros: Timelines are precomputed, so loading is fast.
- Cons: Resource-heavy for celebrities (e.g., a user with 100M followers would trigger 100M writes).
Hybrid solution: Use push for most users, pull for celebrities. For example:
- If a user has <10k followers: push tweets to their followers’ timelines.
- If >10k followers: pull tweets on-demand when followers load their timeline.
Data Storage
- Tweets: Use a column-family database (e.g., Cassandra) for high write throughput. Schema:
Table: tweets - tweet_id (PK): UUID - user_id: string - content: text - timestamp: datetime - likes/retweets: counters - Followers/Following: Use a graph database (e.g., Neo4j) or Redis sorted sets to track relationships.
Caching Timelines
Store timelines in Redis sorted sets (sorted by timestamp) for fast retrieval. Example:
- Key:
timeline:{user_id} - Value: Sorted set of
tweet_ids, scored by timestamp.
Step 4: Scalability & Trade-Offs
- Sharding: Shard tweet data by
user_idto distribute read/write load. - CDN for media: Store images/videos in a CDN (e.g., Cloudflare) to reduce latency.
- Trade-offs:
- Push model latency: For users with many followers, pushing tweets may take seconds. Use a message queue (e.g., Kafka) to process pushes asynchronously.
- Timeline freshness: Precomputed timelines may not include the latest tweets. Add a “pull latest” button to fetch new posts on demand.
Question 3: Design a Real-Time Chat Application
Scenario: Build a chat app supporting one-on-one, group chats, and message history (e.g., WhatsApp, Slack).
Step 1: Requirements Clarification
- Functional: Send/receive messages (text/media), group chats (up to 100 users), message history, read receipts.
- Non-functional: Low latency (<1s delivery), high availability, durability (messages not lost).
- Constraints: Support 100M daily active users, 10B messages/day.
Step 2: High-Level Design
Key components:
- WebSocket Server: For real-time message delivery (bidirectional communication).
- REST API: For fetching message history, user profiles.
- Message Queue: For reliable delivery (e.g., Kafka, RabbitMQ).
- Database: Stores messages, user data, chat metadata.
Step 3: Detailed Design
Real-Time Communication
Use WebSockets (or Socket.io for fallback) instead of HTTP polling:
- Clients connect to a WebSocket server, which maintains persistent connections.
- For group chats, use a pub/sub system (e.g., Redis Pub/Sub): When a user sends a message, the server publishes it to a channel, and all group members subscribe to the channel.
Message Delivery
- Flow: User sends message → WebSocket server → message queue (to ensure durability) → database → delivered to recipients via WebSocket.
- Guarantees: Use at-least-once delivery (retry failed sends) to prevent message loss.
Database Schema
- Messages: Store in a time-series database (e.g., InfluxDB) or Cassandra (for scalability). Schema:
Table: messages - chat_id (PK): UUID (group or one-on-one chat) - message_id (PK): UUID - sender_id: string - content: text/media URL - timestamp: datetime - status: read/unread - Chats: Track participants in group chats. Use a relational database (e.g., PostgreSQL) for joins:
Table: chat_participants - chat_id (FK): UUID - user_id (FK): string
Step 4: Scalability & Trade-Offs
- Horizontal scaling: Add more WebSocket servers behind a load balancer (use sticky sessions to keep clients connected to the same server).
- Handling offline users: Store undelivered messages in the database and send them when the user reconnects.
- Trade-offs:
- WebSocket vs. HTTP long polling: WebSockets are more efficient for real-time, but some networks block WebSockets. Use Socket.io for automatic fallback to long polling.
- Media messages: Compress images/videos before sending to reduce bandwidth. Use a CDN for media delivery.
Question 4: Design a File Storage System (e.g., Google Drive)
Scenario: Build a system for uploading, storing, and sharing files (documents, images, videos).
Step 1: Requirements Clarification
- Functional: Upload/download files, share with users/groups, version history, search files.
- Non-functional: Durability (99.999% uptime), scalability (1B users, 10PB total storage), low latency for downloads.
- Constraints: Support files up to 100GB; minimize storage costs.
Step 2: High-Level Design
Key components:
- Object Storage: Stores raw file data (e.g., S3, Google Cloud Storage).
- Metadata Service: Manages file metadata (name, size, owner, permissions).
- CDN: Caches frequently accessed files for fast downloads.
- Search Service: Indexes metadata for fast file search (e.g., Elasticsearch).
Step 3: Detailed Design
File Upload/Download
- Upload flow:
- Client splits large files into chunks (e.g., 5MB) to avoid retransmitting on failure.
- Chunks are uploaded to object storage via a presigned URL (temporary, secure URL).
- Metadata service records the file’s chunk IDs, size, and checksum.
- Download flow:
- Client requests file → metadata service returns chunk IDs and presigned URLs.
- Client downloads chunks in parallel and reassembles the file.
Metadata Storage
Use a relational database (e.g., PostgreSQL) for structured metadata:
Table: files
- file_id (PK): UUID
- name: string
- owner_id: string
- size: bigint
- created_at: timestamp
- chunk_ids: array<string> (list of object storage keys)
- permissions: json (e.g., {"user:123": "read", "group:456": "write"})
Sharing Files
- Generate a shareable link with permissions (view/edit). Embed the link in a token (e.g., JWT) to avoid database lookups.
- For group sharing, use a group service to map group IDs to user IDs (check permissions at download time).
Step 4: Scalability & Trade-Offs
- Durability: Replicate files across 3+ regions (object storage typically does this automatically).
- Cost optimization: Use tiered storage (e.g., “hot” for recent files, “cold” for archives with higher latency but lower cost).
- Trade-offs:
- Consistency vs. availability: Use eventual consistency for metadata (e.g., search indexes may lag by 5 mins) to improve availability.
- Chunk size: Smaller chunks (1MB) reduce retransmission on failure but increase metadata overhead. Larger chunks (50MB) reduce overhead but are slower to upload.
Question 5: Design a Rate Limiter
Scenario: Build a rate limiter to prevent abuse (e.g., too many API requests from a single user).
Step 1: Requirements Clarification
- Functional: Limit requests per user (e.g., 100 requests/minute), block excess requests.
- Non-functional: Low latency (add <1ms to requests), distributed (works across multiple API servers).
- Constraints: Support 100k QPS; track limits for 10M users.
Step 2: High-Level Design
Key components:
- Rate Limiter Service: Enforces limits using an algorithm (e.g., token bucket, fixed window).
- Redis: Stores request counts (in-memory, fast for distributed systems).
Step 3: Detailed Design
Rate Limiting Algorithms
Choose an algorithm based on strictness:
- Fixed window: Track requests in 1-minute windows (e.g., “100 requests between 2:00–2:01”). Simple but allows “bursting” (99 requests at 2:00:59, 100 at 2:01:00 = 199 in 2 seconds).
- Sliding window: Split the window into intervals (e.g., 60 1-second intervals per minute). More precise but complex.
- Token bucket: Users get tokens at a fixed rate (e.g., 100 tokens/minute). Each request consumes a token; refills over time. Allows short bursts (uses stored tokens).
Implementation with Redis
Use Redis to track request counts/distributed tokens:
- Fixed window example:
def is_allowed(user_id, limit=100, window=60): key = f"rate_limit:{user_id}:{int(time.time()/window)}" # e.g., "rate_limit:123:16200000" count = redis.incr(key) if count == 1: redis.expire(key, window) # Auto-delete after window return count <= limit - Token bucket example: Use
redis.decrto consume tokens; refill with a background job.
Step 4: Scalability & Trade-Offs
- Distributed rate limiting: Redis cluster ensures all API servers see the same request counts.
- Race conditions: Use Redis
INCR(atomic) to avoid double-counting. - Trade-offs:
- Strictness vs. performance: Sliding window is precise but requires more Redis operations. Use fixed window for simplicity.
- Exceeding limits: Return
429 Too Many Requestswith aRetry-Afterheader to guide clients.
How to Practice Effectively
- Find a partner: Use platforms like Pramp, interviewing.io, or Meetup to connect with peers.
- Use tools: Draw diagrams with Miro/FigJam; simulate Redis/database interactions with code snippets.
- Time yourself: Set a 45-minute timer for each mock interview to practice pacing.
- Review and iterate: After each session, debrief with your partner. Ask: “Did I clarify requirements? Miss any scalability issues?”
Common Mistakes to Avoid
- Jumping into design too early: Spend 5–10 minutes clarifying requirements before drawing diagrams.
- Ignoring non-functional requirements: Interviewers care about scalability, reliability, and latency as much as features.
- Over-engineering: You don’t need to design for 1B users if the problem specifies 10k. Tailor complexity to constraints.
- Poor diagramming: Use clear labels (e.g., “API Server” vs. “Server A”). Avoid messy, unlabeled boxes.
Conclusion
System design mock interviews are the bridge between theoretical knowledge and real-world problem-solving. By practicing with realistic questions, refining your communication, and learning from feedback, you’ll turn “I think I need a cache” into “I’ll use Redis with a TTL of 1 hour for frequent queries, sharded by user ID to scale.”
Remember: The goal isn’t perfection—it’s to show you can break down complex problems, prioritize constraints, and justify decisions. With consistent practice, you’ll approach your next interview with confidence.
References
- Books: System Design Interview (Alex Xu), Designing Data-Intensive Applications (Martin Kleppmann).
- Courses: Grokking the System Design Interview (educative.io), System Design Fundamentals (ByteByteGo).
- Tools: Miro (diagramming), Redis (caching/rate limiting), Kafka (message queues).
- Platforms: Pramp (free mock interviews), interviewing.io (tech-focused mock interviews).