1. Apa Itu System Design Interview?
System Design Interview adalah jenis interview teknis yang menguji kemampuan kandidat dalam merancang sistem software yang scalable, reliable, dan efisien. Berbeda dengan coding interview yang fokus pada algoritma, system design menguji pemahaman arsitektur, trade-off, dan kemampuan berpikir pada skala besar.
Di perusahaan top seperti Google, Meta, Amazon, dan Netflix, system design interview adalah tahap krusial yang menentukan level penawaran. Bahkan untuk posisi mid-level, pertanyaan system design sudah mulai muncul.
ke beberapa server
di memory
persistent
antar service
request
untuk client
1.1 Mengapa System Design Penting?
System design bukan hanya tentang interview โ ini adalah skill fundamental yang membedakan engineer junior dari senior. Kemampuan merancang sistem yang baik akan membantu Anda:
- Membuat keputusan arsitektur yang tepat dalam proyek nyata
- Berkomunikasi dengan efektif tentang trade-off teknis dengan tim
- Memahami skala โ bagaimana sistem berperilaku saat traffic naik 10x atau 100x
- Mengantisipasi masalah sebelum terjadi di production
Tidak ada jawaban "benar" dalam system design. Yang dinilai adalah proses berpikir Anda: bagaimana Anda mengidentifikasi requirements, mempertimbangkan trade-off, dan mengkomunikasikan solusi secara terstruktur.
2. Framework Menjawab System Design Interview
Framework yang terstruktur adalah kunci untuk menjawab pertanyaan system design dengan percaya diri. Berikut adalah langkah-langkah yang sudah terbukti efektif:
2.1 Step-by-Step Framework
# === STEP 1: CLARIFY REQUIREMENTS (3-5 menit) === # Tanyakan hal-hal yang ambigu. Jangan langsung menggambar. # Contoh pertanyaan: # - Berapa estimasi user per hari? (scale) # - Apa fitur utama yang harus ada? (functional) # - Berapa latency yang diharapkan? (non-functional) # - Read-heavy atau write-heavy? # - Konsistensi atau availability yang diprioritaskan? # === STEP 2: ESTIMATION & BACK-OF-ENVELOPE (3-5 menit) === # Hitung estimasi kasar untuk memahami skala: # - Jumlah request per detik (QPS) # - Penyimpanan yang dibutuhkan per tahun # - Bandwidth yang dibutuhkan # - Jumlah server yang diperlukan # # Contoh: 10 juta user/hari, rata-rata 5 request/user # QPS = 10M ร 5 / 86400 โ 580 requests/detik # Peak QPS โ 580 ร 3 = 1,740 requests/detik # === STEP 3: HIGH-LEVEL DESIGN (10-15 menit) === # Gambar arsitektur utama: # - Client โ API Gateway โ Services โ Database # - Tentukan komponen mana yang dibutuhkan # - Definisikan API endpoint utama # - Pilih database yang sesuai # - Identifikasi di mana cache diperlukan # === STEP 4: DEEP DIVE & TRADE-OFF (10-15 menit) === # Fokus pada komponen yang paling kritis: # - Bagaimana data di-shard di database? # - Strategi caching (write-through vs write-behind)? # - Bagaimana menangani failure scenario? # - Monitoring dan alerting?
2.2 Back-of-the-Envelope Estimation
Kemampuan menghitung estimasi kasar sangat penting. Berikut reference cepat yang perlu dihafal:
| Metric | Angka | Catatan |
|---|---|---|
| 1 hari | 86,400 detik | ~100K detik (pembulatan) |
| 1 bulan | ~2.6 juta detik | Untuk estimasi penyimpanan |
| 1 tahun | ~31.5 juta detik | ~10 juta detik per bulan ร 12 |
| Laju transfer 1 Gbps | ~125 MB/detik | Untuk bandwidth calculation |
| SSD random read | ~16 ยตs | 60x lebih cepat dari HDD |
| SSD sequential read | ~200 MB/s - 3 GB/s | SSD NVMe jauh lebih cepat |
| Round trip network | ~0.5 ms (same region) | Inter-continental: ~150 ms |
| L1/L2 cache reference | ~1 ns / ~10 ns | Perbandingan kecepatan |
3. Load Balancer
Load Balancer adalah komponen yang mendistribusikan incoming traffic ke beberapa server backend. Tujuannya adalah memastikan tidak ada satu server yang kelebihan beban sementara server lain menganggur.
3.1 Jenis Load Balancer
- Layer 4 (Transport): Mendistribusikan traffic berdasarkan IP dan port. Cepat tapi kurang fleksibel. Cocok untuk TCP/UDP raw.
- Layer 7 (Application): Mendistribusikan berdasarkan konten HTTP (URL, header, cookie). Lebih cerdas tapi lebih berat. Cocok untuk HTTP routing.
3.2 Load Balancing Algorithms
# 1. ROUND ROBIN # Setiap request didistribusikan secara berurutan # Server: [A, B, C] # Request 1 โ A, Request 2 โ B, Request 3 โ C, Request 4 โ A # Cocok untuk: server dengan spesifikasi sama # Kelemahan: Tidak memperhitungkan beban aktual server # 2. WEIGHTED ROUND ROBIN # Seperti round robin tapi server punya "bobot" # Server: [A(weight=5), B(weight=3), C(weight=2)] # A menerima 50% request, B 30%, C 20% # Cocok untuk: server dengan spesifikasi berbeda # 3. LEAST CONNECTIONS # Request dikirim ke server yang paling sedikit koneksi aktif # Server A: 10 connections, B: 5 connections, C: 8 connections # Request baru โ B (paling sedikit) # Cocok untuk: request dengan durasi bervariasi # 4. IP HASH # Hash dari IP client menentukan server tujuan # hash(client_ip) % num_servers # Keuntungan: Session persistence (user selalu ke server sama) # Cocok untuk: stateful application tanpa shared session # 5. CONSISTENT HASHING # Hash ring โ meminimalkan redistribusi saat server ditambah/dihapus # Cocok untuk: distributed cache, CDN # Hanya ~1/N key yang perlu dipindahkan saat server berubah
3.3 Health Check & Failover
Load balancer secara periodik melakukan health check ke setiap server. Jika server tidak merespons dalam waktu yang ditentukan, traffic akan dialihkan ke server lain yang masih sehat. Ini disebut failover otomatis.
Load balancer sendiri bisa menjadi single point of failure. Untuk mengatasinya, gunakan redundant load balancer dengan teknik seperti active-passive atau active-active setup. Contoh: AWS Elastic Load Balancer (ELB) sudah built-in redundancy.
4. Caching Strategy
Cache adalah lapisan penyimpanan sementara yang menyimpan data yang sering diakses di memory untuk mengurangi latency dan beban database. Cache bisa menjadi "game changer" untuk performa sistem.
4.1 Level Cache
- Client-side cache: Browser cache, mobile app cache
- CDN cache: Cache konten statis di edge server terdekat
- Application cache: In-memory cache di dalam aplikasi (HashMap, LRU cache)
- Distributed cache: Redis, Memcached โ terpisah dari aplikasi
- Database cache: Query cache, buffer pool di database
4.2 Cache Invalidation Strategies
# === WRITE-THROUGH === # Data ditulis ke cache DAN database secara bersamaan # Keuntungan: Data selalu konsisten # Kerugian: Write latency lebih tinggi (dua operasi) # # App โ Cache + DB (simultan) # Read: selalu dari cache # === WRITE-BEHIND (WRITE-BACK) === # Data ditulis ke cache dulu, lalu async ke database # Keuntungan: Write latency sangat rendah # Kerugian: Risiko data loss jika cache crash sebelum flush # # App โ Cache โ DB (async, batched) # Cocok untuk: write-heavy workload yang bisa toleransi sedikit data loss # === WRITE-AROUND === # Data ditulis langsung ke database, tidak ke cache # Cache hanya terisi saat cache miss (read-through) # Keuntungan: Cache tidak dipenuhi data yang jarang dibaca # Kerugian: Pertama kali read selalu lambat (cache miss) # # App โ DB (langsung) # App โ Cache (miss) โ DB โ Cache (isi) โ App # === CACHE-ASIDE (LAZY LOADING) === # Strategi paling umum. App yang mengatur sendiri kapan # menulis/membaca dari cache. # Read: # 1. Cek cache โ hit? return # 2. Miss? query DB โ tulis ke cache โ return # Write: # 1. Tulis ke DB # 2. Invalidate cache (hapus entry) # 3. Biarkan cache terisi ulang saat read berikutnya
4.3 Masalah Cache yang Perlu Diwaspadai
| Masalah | Penjelasan | Solusi |
|---|---|---|
| Cache Stampede | Banyak request bersamaan saat cache expired | Lock/mutex, early refresh |
| Cache Avalanche | Banyak cache key expired bersamaan | Random TTL (jitter) |
| Cache Penetration | Request untuk data yang tidak ada terus menerus | Bloom filter, cache null |
| Hot Key | Satu key diakses sangat sering | Replicate, local cache |
| Cold Start | Cache kosong setelah restart | Pre-warming cache |
5. Database Design & Pemilihan
Memilih database yang tepat adalah salah satu keputusan terpenting dalam system design. Tidak ada database yang sempurna untuk semua kasus โ setiap jenis punya trade-off.
5.1 Jenis Database
| Jenis | Contoh | Cocok Untuk | Trade-off |
|---|---|---|---|
| Relational (SQL) | PostgreSQL, MySQL | Data terstruktur, ACID, JOIN | Horizontal scaling sulit |
| Document | MongoDB, CouchDB | Data semi-struktur, flexible schema | JOIN lemah |
| Key-Value | Redis, DynamoDB | Cache, session, simple lookup | Query terbatas |
| Wide Column | Cassandra, HBase | Time-series, high write throughput | Eventual consistency |
| Graph | Neo4j, Amazon Neptune | Relationship kompleks, social graph | Kurang cocok untuk bulk read |
| Search Engine | Elasticsearch | Full-text search, log analytics | Bukan primary database |
5.2 Replication & Sharding
# === REPLICATION === # Menyalin data ke beberapa server untuk redundancy & read scaling # 1. LEADER-FOLLOWER (Master-Slave) # - Semua write ke leader # - Read bisa dari follower (read replica) # - Follower async/sync replikasi dari leader # - Jika leader mati, follower bisa di-promote # 2. MULTI-LEADER # - Beberapa server bisa menerima write # - Cocok untuk multi-region # - Konflik write harus diselesaikan (conflict resolution) # 3. LEADERLESS # - Semua node bisa menerima read & write # - Consensus: quorum read & write # - Contoh: Amazon DynamoDB, Cassandra # === SHARDING (PARTITIONING) === # Membagi data ke beberapa database server # 1. RANGE-BASED SHARDING # Data dibagi berdasarkan range nilai # Shard 1: user_id 1-1,000,000 # Shard 2: user_id 1,000,001-2,000,000 # Risiko: Hotspot jika distribusi tidak merata # 2. HASH-BASED SHARDING # hash(key) % num_shards # Distribusi lebih merata # Tapi range query sulit # 3. DIRECTORY-BASED SHARDING # Lookup table: key โ shard mapping # Fleksibel tapi tambah satu layer lookup
6. Message Queue & Async Processing
Message Queue memungkinkan komunikasi asynchronous antar service. Alih-alih service A langsung memanggil service B, service A mengirim pesan ke queue dan service B mengambil pesan tersebut kapan saja siap.
6.1 Mengapa Message Queue Penting?
- Decoupling: Producer dan consumer tidak perlu tahu tentang satu sama lain
- Buffering: Menampung burst traffic tanpa meng-crash consumer
- Async processing: Operasi berat di-background tanpa blocking user
- Retry mechanism: Pesan gagal bisa di-retry otomatis
6.2 Perbandingan Message Queue Populer
| Queue | Model | Kekuatan | Cocok Untuk |
|---|---|---|---|
| RabbitMQ | AMQP, traditional queue | Routing fleksibel, ack mechanism | Task queue, RPC |
| Apache Kafka | Distributed log | Throughput sangat tinggi, replay | Event streaming, log aggregation |
| Amazon SQS | Managed queue | Fully managed, auto-scaling | Microservices, serverless |
| Redis Streams | Log-based | Cepat, built-in di Redis | Lightweight event stream |
| NATS | Pub/Sub | Sangat cepat, lightweight | IoT, real-time messaging |
// Producer โ Mengirim task ke queue
// producer.js
const amqp = require('amqplib');
async function sendTask(task) {
const connection = await amqp.connect('amqp://localhost');
const channel = await connection.createChannel();
const queue = 'task_queue';
await channel.assertQueue(queue, { durable: true });
channel.sendToQueue(queue, Buffer.from(JSON.stringify(task)), {
persistent: true // Pesan disimpan ke disk
});
console.log(`Task dikirim: ${task.id}`);
await connection.close();
}
// Contoh penggunaan
sendTask({ id: 'task-001', action: 'resize_image', url: '/uploads/img1.jpg' });
// Consumer โ Memproses task dari queue
// consumer.js
async function consumeTasks() {
const connection = await amqp.connect('amqp://localhost');
const channel = await connection.createChannel();
const queue = 'task_queue';
await channel.assertQueue(queue, { durable: true });
channel.prefetch(1); // Proses 1 task sekaligus
console.log('Menunggu task...');
channel.consume(queue, async (msg) => {
const task = JSON.parse(msg.content.toString());
console.log(`Memproses task: ${task.id}`);
try {
await processTask(task);
channel.ack(msg); // Beritahu queue: task selesai
} catch (err) {
channel.nack(msg, false, true); // Re-queue jika gagal
}
});
}
async function processTask(task) {
// Simulasi proses berat
await new Promise(resolve => setTimeout(resolve, 2000));
console.log(`Task ${task.id} selesai`);
}
7. Rate Limiter
Rate Limiter membatasi jumlah request yang bisa dilakukan oleh user/IP dalam periode waktu tertentu. Ini penting untuk melindungi sistem dari abuse, DDoS, dan memastikan fair usage.
7.1 Rate Limiting Algorithms
# 1. TOKEN BUCKET # - Bucket berisi "token" dengan kapasitas tetap # - Token diisi ulang dengan rate konstan (misal: 10 token/detik) # - Setiap request mengambil 1 token # - Jika bucket kosong, request ditolak (HTTP 429) # # Kapasitas: 10, Refill: 2/detik # Request burst: 10 langsung diizinkan, lalu 2/detik # Cocok untuk: burst traffic yang wajar # 2. LEAKY BUCKET # - Request masuk ke "bucket" (queue) dengan kapasitas tetap # - Request diproses dengan rate konstan # - Jika bucket penuh, request ditolak # # Rate konstan: 5 request/detik # Cocok untuk: output rate yang harus stabil # 3. FIXED WINDOW COUNTER # - Membagi waktu ke window (misal: 1 menit) # - Counter per window, reset saat window baru # - Limit: 100 request per menit # # Masalah: Burst di boundary (2 window berturut-turut bisa = 2x limit) # 4. SLIDING WINDOW LOG # - Menyimpan timestamp setiap request # - Saat request baru, hapus timestamp yang sudah di luar window # - Hitung jumlah request yang tersisa # - Lebih akurat tapi butuh memory lebih besar # 5. SLIDING WINDOW COUNTER # - Kombinasi fixed window + sliding window # - Weighted count dari window sebelumnya # - 70% window lama + 100% window baru = smoothing # - Cocok untuk: implementasi production yang efisien
7.2 Implementasi Rate Limiter
# rate_limiter.py โ Implementasi Token Bucket di Redis
import redis
import time
class TokenBucketRateLimiter:
def __init__(self, redis_client, capacity, refill_rate):
"""
capacity: maksimum token dalam bucket
refill_rate: jumlah token yang ditambah per detik
"""
self.redis = redis_client
self.capacity = capacity
self.refill_rate = refill_rate
def is_allowed(self, key):
"""Cek apakah request diizinkan untuk key tertentu"""
now = time.time()
bucket_key = f"rate_limit:{key}"
# Gunakan Lua script untuk atomicity
lua_script = """
local key = KEYS[1]
local capacity = tonumber(ARGV[1])
local refill_rate = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
local requested = tonumber(ARGV[4])
local data = redis.call('HMGET', key, 'tokens', 'last_refill')
local tokens = tonumber(data[1]) or capacity
local last_refill = tonumber(data[2]) or now
-- Hitung token baru berdasarkan waktu yang berlalu
local elapsed = now - last_refill
local new_tokens = elapsed * refill_rate
tokens = math.min(capacity, tokens + new_tokens)
-- Cek apakah cukup token
if tokens >= requested then
tokens = tokens - requested
redis.call('HMSET', key, 'tokens', tokens, 'last_refill', now)
redis.call('EXPIRE', key, 3600) -- TTL 1 jam
return 1 -- Allowed
else
redis.call('HMSET', key, 'tokens', tokens, 'last_refill', now)
redis.call('EXPIRE', key, 3600)
return 0 -- Denied
end
"""
result = self.redis.eval(lua_script, 1, bucket_key,
self.capacity, self.refill_rate, now, 1)
return result == 1
# Contoh penggunaan
r = redis.Redis(host='localhost', port=6379)
limiter = TokenBucketRateLimiter(r, capacity=100, refill_rate=10)
# Di middleware web framework
for request in incoming_requests:
user_id = request.headers.get('X-User-ID')
if limiter.is_allowed(user_id):
process_request(request)
else:
return Response(status=429, body="Rate limit exceeded")
8. API Gateway
API Gateway adalah entry point tunggal untuk semua client request. Di microservices architecture, API Gateway menjadi facade yang menyederhanakan komunikasi client dengan puluhan atau ratusan service di belakangnya.
8.1 Fungsi API Gateway
- Request routing: Mengarahkan request ke service yang tepat
- Authentication & Authorization: Validasi token JWT, OAuth
- Rate limiting: Membatasi laju request per client
- Request/Response transformation: Mengubah format data
- Load balancing: Mendistribusikan ke beberapa instance service
- Caching: Cache response untuk mengurangi beban backend
- Logging & Monitoring: Centralized logging untuk semua request
- Circuit breaker: Melindungi dari cascade failure
8.2 Implementasi Sederhana API Gateway
// api-gateway.js โ Simple API Gateway dengan Express
const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');
const rateLimit = require('express-rate-limit');
const jwt = require('jsonwebtoken');
const app = express();
// === MIDDLEWARE: Rate Limiting ===
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 menit
max: 100, // 100 request per window
message: { error: 'Too many requests, coba lagi nanti' }
});
app.use(limiter);
// === MIDDLEWARE: Authentication ===
const authMiddleware = (req, res, next) => {
// Skip auth untuk public endpoints
if (req.path.startsWith('/api/auth') || req.path === '/health') {
return next();
}
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) {
return res.status(401).json({ error: 'Token tidak ditemukan' });
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded; // Attach user info ke request
next();
} catch (err) {
return res.status(401).json({ error: 'Token tidak valid' });
}
};
app.use(authMiddleware);
// === ROUTE PROXY ===
// User Service
app.use('/api/users', createProxyMiddleware({
target: 'http://user-service:3001',
changeOrigin: true,
pathRewrite: { '^/api/users': '/users' }
}));
// Product Service
app.use('/api/products', createProxyMiddleware({
target: 'http://product-service:3002',
changeOrigin: true,
pathRewrite: { '^/api/products': '/products' }
}));
// Order Service
app.use('/api/orders', createProxyMiddleware({
target: 'http://order-service:3003',
changeOrigin: true,
pathRewrite: { '^/api/orders': '/orders' }
}));
// === Health Check ===
app.get('/health', (req, res) => {
res.json({ status: 'ok', timestamp: new Date().toISOString() });
});
app.listen(8080, () => {
console.log('API Gateway berjalan di port 8080');
});
9. Contoh Kasus: Mendesain URL Shortener
Mari kita terapkan semua konsep di atas ke contoh kasus yang sering muncul di interview: URL Shortener (seperti bit.ly).
9.1 Step 1: Clarify Requirements
- Functional: Buat short URL dari long URL, redirect short โ long, custom alias (opsional), analytics (klik count)
- Non-functional: High availability, low latency (<100ms redirect), scalable hingga 100 juta URL/bulan
- Read-heavy: Rasio read:write = 100:1
9.2 Step 2: Estimation
# Write: 100 juta URL/bulan = ~40 URL/detik # Read: 10 miliar redirect/bulan = ~4000 redirect/detik # Peak: 3x average = ~12,000 redirect/detik # Storage (5 tahun): # 100M ร 12 bulan ร 5 tahun = 6 miliar URL # Per URL: ~500 bytes (long_url + short_code + metadata) # Total: 6B ร 500 = 3 TB storage # Bandwidth: # Read: 4000 ร 500 bytes = 2 MB/detik = 16 Mbps # Ini sangat manageable untuk 1-2 server # Short code: # 6 karakter alphanumeric (a-z, A-Z, 0-9) = 62^6 = ~56 miliar kombinasi # Cukup untuk 6 miliar URL dengan margin besar # QPS breakdown: # Write QPS: 40 โ 1 database server cukup # Read QPS: 4000 โ Butuh cache untuk mengurangi beban DB
9.3 Step 3: High-Level Design
# === API ENDPOINTS ===
# POST /api/shorten
# Body: { "long_url": "https://example.com/very/long/path", "custom_alias": "mylink" }
# Response: { "short_url": "https://sho.rt/aB3xY2", "short_code": "aB3xY2" }
# GET /:short_code
# Response: 301 Redirect ke long_url
# GET /api/stats/:short_code
# Response: { "total_clicks": 15432, "unique_visitors": 8234, "created_at": "..." }
# === DATABASE SCHEMA ===
# Table: urls
# id BIGINT PRIMARY KEY AUTO_INCREMENT
# short_code VARCHAR(10) UNIQUE INDEX
# long_url TEXT NOT NULL
# user_id BIGINT FK โ users.id
# created_at TIMESTAMP DEFAULT NOW()
# expires_at TIMESTAMP NULL
# click_count BIGINT DEFAULT 0
# Table: click_analytics
# id BIGINT PRIMARY KEY AUTO_INCREMENT
# short_code VARCHAR(10) INDEX
# ip_address VARCHAR(45)
# user_agent TEXT
# referer TEXT
# country VARCHAR(2)
# clicked_at TIMESTAMP DEFAULT NOW()
9.4 Step 4: Deep Dive โ Short Code Generation
# shortener.py โ Strategi generate short code
import hashlib
import string
import random
CHARSET = string.ascii_letters + string.digits # 62 karakter
# === STRATEGI 1: HASH-BASED ===
# MD5/SHA256 dari long_url, ambil 6 karakter pertama
# Masalah: Collision jika URL sama โ perlu cek DB dulu
def hash_based(long_url):
hash_val = hashlib.md5(long_url.encode()).hexdigest()
# Konversi ke base62, ambil 6 karakter
num = int(hash_val[:12], 16)
code = ''
while num > 0 and len(code) < 6:
code = CHARSET[num % 62] + code
num //= 62
return code
# === STRATEGI 2: COUNTER-BASED ===
# Auto-increment counter, konversi ke base62
# Keuntungan: Unik, tidak collision
# Masalah: Predictable, bisa ditebak URL berikutnya
def counter_based(counter):
code = ''
while counter > 0:
code = CHARSET[counter % 62] + code
counter //= 62
return code.zfill(6) # Pad ke 6 karakter
# === STRATEGI 3: RANDOM + CHECK ===
# Generate random, cek uniqueness di DB
# Paling sederhana tapi perlu collision check
def random_based():
return ''.join(random.choices(CHARSET, k=6))
# === STRATEGI 4: PRE-GENERATED KEY SERVICE (REKOMENDASI) ===
# Pre-generate jutaan unique key, simpan di DB/key service
# Saat ada request, ambil 1 key dari pool
# Tidak ada collision, tidak perlu check
# Kekurangan: Butuh infrastruktur key service tambahan
Sampaikan beberapa strategi dan diskusikan trade-off-nya. Ini menunjukkan bahwa Anda mempertimbangkan berbagai opsi sebelum memilih. Strategi pre-generated key service sering disukai interviewer karena menunjukkan pemikiran yang matang.
10. Tips & Strategi Lanjutan
10.1 Common System Design Questions
- URL Shortener: Seperti yang dibahas di atas
- Chat System (WhatsApp/Messenger): Fokus pada 1:1 vs group chat, message ordering, delivery receipt
- News Feed (Twitter/Facebook): Fan-out on write vs fan-out on read, ranking algorithm
- Video Streaming (YouTube/Netflix): Upload pipeline, transcoding, CDN, adaptive bitrate
- File Storage (Google Drive/Dropbox): Chunking, deduplication, sync conflict resolution
- Notification System: Multi-channel (push, email, SMS), priority queue, rate limiting
- Rate Limiter: Distributed rate limiting, per-user vs per-IP
- Web Crawler: URL frontier, politeness, deduplication, distributed crawling
10.2 DO & DON'T Saat Interview
| โ DO | โ DON'T |
|---|---|
| Bertanya untuk clarify requirements | Langsung menggambar tanpa bertanya |
| Mulai dari high-level design, baru deep dive | Langsung fokus ke satu detail kecil |
| Diskusikan trade-off setiap keputusan | Mengklaim solusi Anda "the best" |
| Berpikir loud (think out loud) | Diam lama tanpa menjelaskan |
| Pertimbangkan failure scenarios | Hanya berpikir untuk happy path |
| Hitung estimasi (QPS, storage, bandwidth) | Mengabaikan skala sistem |
| Tunjukkan iterasi โ "kita bisa mulai simpel lalu scale" | Over-engineering dari awal |
10.3 Resources untuk Belajar
- Buku: "Designing Data-Intensive Applications" oleh Martin Kleppmann
- Buku: "System Design Interview" oleh Alex Xu (Vol 1 & 2)
- Website: system-design-primer (GitHub) โ repo populer untuk belajar system design
- YouTube: Gaurav Sen, Hussein Nasser, ByteByteGo
- Practice: Mock interview dengan teman, gunakan timer 45 menit
Interviewer bisa langsung tahu jika Anda menghafal solusi tanpa memahami trade-off-nya. Lebih baik memahami konsep dasar dan mampu beradaptasi dengan pertanyaan yang dimodifikasi, daripada menghafal arsitektur spesifik yang mungkin tidak cocok dengan constraints yang diberikan.