System design interviews can feel overwhelming, but they follow a predictable pattern. Success hinges not on knowing every technology, but on structuring your thinking clearly. This post outlines a robust framework you can apply to classic problems like URL shorteners and real-time chat applications, helping you demonstrate architectural maturity and depth.
Step 1: Clarify Requirements and Scope
Never jump into architecture. Start by defining the problem boundaries. For a URL shortener, ask: Do we need analytics? What is the expected traffic? Is the link expiration time critical? For a chat app, clarify: Is it 1-on-1 or group-based? Do we need offline support or read receipts? Quantitative constraints are crucial. Assume the URL shortener needs to handle 100 million writes per day and 1 billion reads per day. For the chat app, assume 500,000 daily active users with an average of 50 messages per user.
Step 2: High-Level Architecture Design
Sketch the core components. Both systems typically include a Client, Load Balancer, Application Servers, Data Layer, and possibly a Cache.
URL Shortener Architecture
The core logic is simple: mapping a long URL to a unique short code. On write, generate a unique ID and store the mapping. On read, look up the ID and redirect. A critical decision is the ID generation strategy. Using a database auto-increment is simple but can become a bottleneck. A better approach is using a distributed ID generator or hashing. Consider base62 encoding to maximize the number of possible short URLs within a 7-character limit.
// Pseudocode for Short URL Generation
function generateShortCode(longUrl) {
// Option 1: Database Sequence
id = db.get_next_id();
// Option 2: Hash-based (with collision check)
hash = md5(longUrl).substring(0, 7);
if (db.exists(hash)) {
handle_collision(hash);
}
base62_code = to_base62(id);
return base62_code;
}
Chat App Architecture
Chat is real-time, requiring WebSocket connections or long polling. The architecture must handle ephemeral state (online status) and persistent state (messages). A Message Queue (Kafka, RabbitMQ) is essential for decoupling message ingestion from delivery. The application server should not directly hold WebSocket connections to the database; instead, it should persist messages to a NoSQL store (like Cassandra or DynamoDB) for durability and use a separate service for real-time push.
Step 3: Data Model and Storage Selection
Data models must match access patterns.
URL Shortener Data Model
A relational database (MySQL) is often sufficient for the mapping table if read/write ratios are balanced, but the lookup is the hottest path. Use Redis for caching hot short codes. The table structure is simple: short_code (PK), long_url, created_at, creator_id. Partitioning by short_code hash ensures even distribution across nodes.
Chat App Data Model
Chat messages are append-only. A wide-column store like Cassandra is ideal for high-throughput writes. Design the table to allow efficient retrieval of recent messages for a conversation.
CREATE TABLE messages (
conversation_id uuid,
message_timestamp timeuuid,
sender_id uuid,
content text,
PRIMARY KEY (conversation_id, message_timestamp)
) WITH CLUSTERING ORDER BY (message_timestamp DESC);
Step 4: Scalability and Bottlenecks
Identify where your design breaks.
Scaling URL Shorteners
Reads are typically 10-100x more frequent than writes. Caching is mandatory. Implement a multi-tier cache: L1 in-memory (Caffeine) in the app server, L2 distributed (Redis). For hot keys, use local caching to reduce network latency. If the ID generator becomes a bottleneck, move to a distributed sequence generator that pre-allocates ID ranges to application servers.
Scaling Chat Apps
The bottleneck is the WebSocket layer. Application servers holding socket connections are stateful. Use a connection layer that supports horizontal scaling, possibly with a sticky session load balancer or a stateful gateway service. For message fan-out in group chats, use a Pub/Sub system (like Kafka) where each member of a group subscribes to their own topic or partition. This ensures each user receives their messages without blocking others.
Step 5: Trade-offs and Edge Cases
Demonstrate depth by discussing trade-offs. For URL shorteners, discuss the trade-off between uniqueness guarantee and performance. A hash might collide; a sequence is unique but centralized. For chat, discuss the trade-off between consistency and availability. In a chat app, losing a message is bad, so prioritize durability. However, for presence updates (online/offline), strong consistency is not needed; eventual consistency via a TTL-based cache is acceptable and more performant.
Conclusion
System design interviews are about structured problem-solving, not memorization. By following this framework—clarifying requirements, designing high-level architecture, choosing the right data model, identifying scalability bottlenecks, and discussing trade-offs—you can confidently navigate complex design questions. Practice applying this methodology to both URL shorteners and chat apps, and you’ll develop the intuition to handle any system design challenge.