Part 03 · High Level Design
How to Solve an HLD Interview
যেকোনো HLD round এর জন্য ধাপে ধাপে plan: প্রশ্ন পরিষ্কার করা, estimation, বড় ছবিটা আঁকা, যেখানে দরকার সেখানে গভীরে যাওয়া, আর একজন senior এর মতো trade-off নিয়ে কথা বলা।
⏱ 1-2 ঘণ্টা · Station 33 / 80
দুইজন candidate, একটা প্রশ্ন
দুইজন engineer একই backend role এর interview দিচ্ছে। দুজনেই একই প্রশ্ন পেল: "একটা URL shortener design করো।"
প্রথমজন সাথে সাথে আঁকা শুরু করল: Kubernetes, Kafka, Cassandra, একটা Redis cluster, তিনটা region। বিশ মিনিট পর board ভর্তি, দেখতে চমৎকার। তারপর interviewer জিজ্ঞেস করলেন, "দিনে কয়টা link এর কথা বলছি আমরা?" চুপ। "এখানে Kafka কেন?" একটু থেমে, "scale এর জন্য"। "একটা short code collide করলে কী হবে?" এটা ভাবাই হয়নি। Design বড়, কিন্তু তার কোনো অংশই কোনো কারণের সাথে বাঁধা না।
দ্বিতীয়জন পাঁচ মিনিট প্রশ্ন করে কাটাল: কয়টা নতুন link, কয়টা click, link কি expire হয়, custom alias, analytics? সংখ্যাগুলো লিখে ফেলল: সেকেন্ডে মোটামুটি 40টা write আর 4,000টা read, পাঁচ বছরে 3 TB। "Read write এর একশো গুণ, তাই কঠিন অংশটা হলো দ্রুত lookup দেওয়া, রাখা না।" তারপর একটা সহজ diagram, তারপর unique code বানানো আর গরম link cache করা নিয়ে গভীরে যাওয়া, তারপর: "Click যদি 50 গুণ বাড়ে, প্রথমে যেটা বদলাতে হবে সেটা…"
দ্বিতীয়জন কম box এঁকে offer পেল। HLD interview তুমি কয়টা technology জানো তার quiz না। এটা দেখানোর সুযোগ তুমি কীভাবে ভাবো: প্রশ্ন পরিষ্কার করা, আন্দাজ করা, সংখ্যা থেকে design করা, যেখানে দরকার সেখানে গভীরে যাওয়া, আর trade-off নিয়ে সৎভাবে কথা বলা। এই chapter তোমাকে যেকোনো HLD প্রশ্নের জন্য বারবার ব্যবহার করার মতো একটা plan দেয়, এই part এর সবকিছু দিয়ে বানানো।
সমস্যা: HLD interview কোথায় ভুল দিকে যায়
| কী হয় | কেন |
|---|---|
| Design যে সমস্যার সমাধান করে সেটা প্রশ্নের সমস্যা না | Requirement আগে পরিষ্কার করা হয়নি |
| প্রতিটা box "scale এর জন্য", কোনোটার যুক্তি নেই | কোনো আন্দাজ নেই, তাই কোনো কারণও নেই |
| আকর্ষণীয় কিছু আলোচনার আগেই সময় শেষ | সহজ অংশে বেশি সময়, কোনো সময়ের plan নেই |
| Interviewer এর ইঙ্গিত চোখ এড়িয়ে যায় | Candidate check না করেই বলে যায় |
| "এতে Kafka আর Cassandra আছে", কিন্তু failure এর কোনো গল্প নেই | Technology এর নাম বলা হয়েছে, আচরণ বোঝানো হয়নি |
| Senior প্রশ্নের junior উত্তর | Trade-off নেই, "10x এ কী বদলাবে" নেই, চালানোর ভাবনা নেই |
ডাক্তারের চেম্বারের কথা ভাবো
ভালো ডাক্তার ঢুকেই প্রেসক্রিপশন লেখা শুরু করেন না। আগে প্রশ্ন করেন: কোথায় ব্যথা, কবে থেকে, জ্বর আছে? (requirement পরিষ্কার করা)। তারপর মাপেন: তাপমাত্রা, প্রেশার, হয়তো একটা test (আন্দাজ)। তারপর একটা সামগ্রিক ছবি দাঁড় করান: "মনে হচ্ছে বুকে একটা infection" (high-level design)। তারপর যেখানে দরকার সেখানে খুঁটিয়ে দেখেন: ফুসফুসের শব্দ শোনেন, নির্দিষ্ট test টা দেখেন (deep dive)। শেষে বিকল্প আর ঝুঁকি বুঝিয়ে বলেন: "antibiotic কয়েকদিনে কাজ করবে; ভালো না হলে X-ray করব" (trade-off আর পরের ধাপ)।
তিনি কাজ করতে করতে কথাও বলেন, তাই তুমি জানো তিনি কী ভাবছেন আর ঠিক করে দিতে পারো ("আসলে ব্যথাটা পড়ে যাওয়ার পর থেকে")। ভালো একটা HLD interview এর আকৃতি হুবহু এটাই, আর একই অভ্যাস: জোরে জোরে ভাবা।
তবে একটা জায়গায় মেলে না। রোগী একটা সঠিক উত্তর চায়। HLD interview তে অনেকগুলো ঠিকঠাক design আছে। বিচার হয় তোমার বাছাইগুলো requirement আর সংখ্যা থেকে আসছে কিনা, আর প্রতিটার দাম তুমি বলতে পারো কিনা।
প্রশ্ন হলো 'একটা chat app design করো'। প্রথমে কী বলা সবচেয়ে ভালো?
ভালো HLD উত্তরের পেছনের নিয়মগুলো
- আগে পরিষ্কার করো, আর লিখে রাখো। Functional requirement (user কী করে: link বানানো, link খোলা, stats দেখা), non-functional requirement (scale, latency, availability, consistency, durability), আর কী scope এর বাইরে। কত user আর কতটা বাড়বে জিজ্ঞেস করো। এগোনোর আগে নিশ্চিত হয়ে নাও।
- যে সংখ্যাগুলো design ঠিক করে সেগুলো আন্দাজ করো। User → সেকেন্ডে request (গড় আর peak), read:write অনুপাত, কপি সহ কয়েক বছরের storage, bandwidth। জোরে গোল করো; ধরে নেওয়াগুলো মুখে বলো। তারপর ভয়ের সংখ্যাটার নাম বলো: আকর্ষণীয় design টা সেখানেই।
- API আর data model ঠিক করো। ইনপুট আর আউটপুট সহ কয়েকটা endpoint, মূল entity গুলো আর তাদের কীভাবে খোঁজা হয় (এটাই key, index আর database বাছাই ঠিক করে)।
- Requirement পূরণ করে এমন সবচেয়ে সহজ design আঁকো। Client → load balancer → stateless service → data store, আর cache, queue আর object storage শুধু যেখানে সংখ্যা বা requirement চায়। একটা request শুরু থেকে শেষ পর্যন্ত এর মধ্য দিয়ে হাঁটিয়ে দেখাও।
- প্রতিটা box এর যুক্তি দাও একটা সংখ্যা বা requirement দিয়ে। "একটা cache, কারণ সেকেন্ডে 11,000 read আর write এর 100 গুণ।" "একটা queue, কারণ notification পাঠানো checkout কে slow করা উচিত না।" কোনো box এর যুক্তি দিতে না পারলে সরিয়ে দাও।
- যেখানে গুরুত্বপূর্ণ সেখানে গভীরে যাও। এই সমস্যার সবচেয়ে কঠিন দুই তিনটা অংশ বাছো (unique ID বানানো, hot key, celebrity দের fan-out, payment এর consistency, real-time delivery) আর খুঁটিনাটিতে যাও: data structure, algorithm, failure এর ধরন। কোনগুলো সেটা interviewer কে ঠিক করতে দাও।
- Failure আর চালানো নিয়ে কথা বলো। একটা node, একটা zone বা একটা dependency fail করলে কী হয়? কীভাবে deploy, monitor আর alert করবে? খরচ কত? Mid-level আর senior উত্তরের পার্থক্য প্রায়ই এখানেই।
- Trade-off স্পষ্ট করে বলো। "Like এর সংখ্যার জন্য eventual consistency নিলাম: সস্তা আর দ্রুত, আর কিছুক্ষণ ভুল সংখ্যায় কারো ক্ষতি নেই। Balance এর জন্য একজন leader নিতাম।" তারপর: "10x এ প্রথমে যেটা ভাঙবে সেটা হলো… আর আমি…"।
- পুরো সময় যোগাযোগ রাখো। জোরে জোরে ভাবো, প্রতিটা ধাপে check করো ("আপনি যা ভাবছিলেন তার সাথে কি মিলছে?"), ইঙ্গিত মেনে চলো, আর সময় সামলাও। যে জটিল design ব্যাখ্যা করতে পারো না তার চেয়ে পরিষ্কার, ভালো যুক্তির সহজ design ভালো।
একটা URL shortener interview, ধাপে ধাপে
দ্বিতীয় candidate এর 45 মিনিট, সংক্ষেপে
ধাপ 1/5 Clarify (5 মিনিট)
Short link বানানো, redirect, optional custom alias, link কখনো expire হয় না, সাধারণ click গণনা। মাসে 10 কোটি নতুন link, read বেশি, redirect দ্রুত হতে হবে (p99 < 50 ms) আর খুব বেশি available। Scope এর বাইরে: user account, link edit।
- scale
- মাসে 10 কোটি link
- latency
- p99 < 50 ms
- scope এর বাইরে
- account, edit
এক checklist এ পুরো plan
- Clarify (মোটামুটি 5 মিনিট): feature, scale, latency, availability, consistency, scope এর বাইরে কী। লিখে রাখো।
- Estimate (মোটামুটি 5 মিনিট): গড় আর peak QPS, read:write, storage, bandwidth। ভয়ের সংখ্যাটার নাম বলো।
- API আর data model (মোটামুটি 5 মিনিট): endpoint, entity, access pattern, key।
- High-level design (মোটামুটি 10 মিনিট): কাজ করে এমন সবচেয়ে সহজ diagram; একটা request হাঁটিয়ে দেখাও; প্রতিটা box এর যুক্তি দাও।
- Deep dive (মোটামুটি 15 মিনিট): এই সমস্যার সবচেয়ে কঠিন দুই তিনটা অংশ, failure এর ধরন সহ।
- শেষ কথা (মোটামুটি 5 মিনিট): যে trade-off নিলে, bottleneck, 10x এ কী বদলাবে, monitoring আর খরচ।
- পুরো সময়: জোরে ভাবো, check করো, ইঙ্গিত মেনে চলো, ঘড়ি দেখো।
নিজে বলো: ভালো চাল নাকি দুর্বল চাল?
Interviewer এটা কীভাবে পড়বেন?
পরের design সেকেন্ডে 12,000 read আর 40 write এর মাপে বানানো
চালিয়ে দেখার মতো একটা number-to-design helper
ছোট একটা program, যেটা সেরা candidate রা মাথায় যা করে সেটাই করে। 45 মিনিটের plan print করে, তারপর দুইটা সমস্যা নেয় (একটা URL shortener আর একটা chat app), peak এ সেকেন্ডে write আর read আর তিন কপি সহ পাঁচ বছরের storage হিসাব করে, আর কয়েকটা সহজ rule চালায় যেগুলো প্রতিটা সংখ্যাকে একটা design সিদ্ধান্তে বদলায়, কোন সংখ্যা সেটা চালু করল সেটাও বলে। ছয়টা language একই সিদ্ধান্ত print করে।
package main
import (
"fmt"
"strings"
)
// The 45-minute plan, in minutes.
var plan = []struct {
step string
minutes int
}{
{"clarify requirements", 5},
{"estimate", 5},
{"API and data model", 5},
{"high-level design", 10},
{"deep dives", 15},
{"trade-offs and wrap-up", 5},
}
type problem struct {
name string
writesPerDay, readsPerX int64 // reads per write
bytesPerItem int64
realtime bool
}
// commas writes 1234567 as 1,234,567.
func commas(n int64) string {
s := fmt.Sprint(n)
var b strings.Builder
for i, c := range s {
if i > 0 && (len(s)-i)%3 == 0 {
b.WriteByte(',')
}
b.WriteRune(c)
}
return b.String()
}
// size prints a byte count as GB, TB or PB with one decimal.
func size(b int64) string {
units := []string{"GB", "TB", "PB"}
unit, i := int64(1_000_000_000), 0
for i < len(units)-1 && b >= unit*1000 {
unit *= 1000
i++
}
tenths := (b*10 + unit/2) / unit
return fmt.Sprintf("%d.%d %s", tenths/10, tenths%10, units[i])
}
// design turns the numbers into decisions. Each rule says which number triggered it.
func design(p problem) {
const day, peak, years, copies = 86_400, 3, 5, 3
writeQPS := p.writesPerDay / day * peak
readQPS := p.writesPerDay * p.readsPerX / day * peak
stored := p.writesPerDay * 365 * years * p.bytesPerItem * copies
fmt.Printf("%s\n", p.name)
fmt.Printf(" peak writes %s/s, peak reads %s/s, storage in %d years with %d copies: %s\n", commas(writeQPS), commas(readQPS), years, copies, size(stored))
decisions := []string{"load balancer + at least 2 stateless app servers"}
if readQPS > 1_000 {
decisions = append(decisions, fmt.Sprintf("cache hot reads (reads %s/s > 1,000)", commas(readQPS)))
}
if p.readsPerX >= 10 {
decisions = append(decisions, fmt.Sprintf("read replicas (%d reads per write)", p.readsPerX))
}
if writeQPS > 1_000 || stored > 10_000_000_000_000 {
decisions = append(decisions, fmt.Sprintf("shard the data (writes %s/s, storage %s)", commas(writeQPS), size(stored)))
} else {
decisions = append(decisions, "one primary database is enough for now")
}
if p.realtime {
decisions = append(decisions, "WebSocket gateways + pub/sub between them (real-time delivery)")
}
for _, d := range decisions {
fmt.Printf(" - %s\n", d)
}
}
func main() {
var steps []string
total := 0
for _, s := range plan {
steps = append(steps, fmt.Sprintf("%s %d", s.step, s.minutes))
total += s.minutes
}
fmt.Printf("45-minute plan (%d min): %s\n", total, strings.Join(steps, ", "))
design(problem{"URL shortener: 100M new links a month, 100 reads each, 500 B per link", 100_000_000 / 30, 100, 500, false})
design(problem{"chat: 50M daily users, 40 messages each a day, 2 reads per message, 200 B", 50_000_000 * 40, 2, 200, true})
}# The 45-minute plan, in minutes.
PLAN = [
("clarify requirements", 5),
("estimate", 5),
("API and data model", 5),
("high-level design", 10),
("deep dives", 15),
("trade-offs and wrap-up", 5),
]
def commas(n: int) -> str:
return f"{n:,}"
def size(b: int) -> str:
"""A byte count as GB, TB or PB with one decimal."""
units = ["GB", "TB", "PB"]
unit, i = 1_000_000_000, 0
while i < len(units) - 1 and b >= unit * 1000:
unit *= 1000
i += 1
tenths = (b * 10 + unit // 2) // unit
return f"{tenths // 10}.{tenths % 10} {units[i]}"
def design(name: str, writes_per_day: int, reads_per_write: int, bytes_per_item: int, realtime: bool) -> None:
"""Turn the numbers into decisions. Each rule says which number triggered it."""
day, peak, years, copies = 86_400, 3, 5, 3
write_qps = writes_per_day // day * peak
read_qps = writes_per_day * reads_per_write // day * peak
stored = writes_per_day * 365 * years * bytes_per_item * copies
print(name)
print(f" peak writes {commas(write_qps)}/s, peak reads {commas(read_qps)}/s, storage in {years} years with {copies} copies: {size(stored)}")
decisions = ["load balancer + at least 2 stateless app servers"]
if read_qps > 1_000:
decisions.append(f"cache hot reads (reads {commas(read_qps)}/s > 1,000)")
if reads_per_write >= 10:
decisions.append(f"read replicas ({reads_per_write} reads per write)")
if write_qps > 1_000 or stored > 10_000_000_000_000:
decisions.append(f"shard the data (writes {commas(write_qps)}/s, storage {size(stored)})")
else:
decisions.append("one primary database is enough for now")
if realtime:
decisions.append("WebSocket gateways + pub/sub between them (real-time delivery)")
for d in decisions:
print(f" - {d}")
total = sum(m for _, m in PLAN)
print(f"45-minute plan ({total} min): {', '.join(f'{s} {m}' for s, m in PLAN)}")
design("URL shortener: 100M new links a month, 100 reads each, 500 B per link", 100_000_000 // 30, 100, 500, False)
design("chat: 50M daily users, 40 messages each a day, 2 reads per message, 200 B", 50_000_000 * 40, 2, 200, True)// The 45-minute plan, in minutes.
const PLAN: [string, number][] = [
["clarify requirements", 5],
["estimate", 5],
["API and data model", 5],
["high-level design", 10],
["deep dives", 15],
["trade-offs and wrap-up", 5],
];
// Storage reaches about 2.2e15 bytes, below 2^53 (about 9e15), so plain numbers stay exact.
const div = (a: number, b: number) => Math.floor(a / b);
/** 1234567 as 1,234,567, without depending on the machine's locale. */
const commas = (n: number) => String(n).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
/** A byte count as GB, TB or PB with one decimal. */
function size(b: number): string {
const units = ["GB", "TB", "PB"];
let unit = 1_000_000_000;
let i = 0;
while (i < units.length - 1 && b >= unit * 1000) {
unit *= 1000;
i++;
}
const tenths = div(b * 10 + unit / 2, unit);
return `${div(tenths, 10)}.${tenths % 10} ${units[i]}`;
}
/** Turn the numbers into decisions. Each rule says which number triggered it. */
function design(name: string, writesPerDay: number, readsPerWrite: number, bytesPerItem: number, realtime: boolean) {
const [day, peak, years, copies] = [86_400, 3, 5, 3];
const writeQPS = div(writesPerDay, day) * peak;
const readQPS = div(writesPerDay * readsPerWrite, day) * peak;
const stored = writesPerDay * 365 * years * bytesPerItem * copies;
console.log(name);
console.log(` peak writes ${commas(writeQPS)}/s, peak reads ${commas(readQPS)}/s, storage in ${years} years with ${copies} copies: ${size(stored)}`);
const decisions = ["load balancer + at least 2 stateless app servers"];
if (readQPS > 1_000) decisions.push(`cache hot reads (reads ${commas(readQPS)}/s > 1,000)`);
if (readsPerWrite >= 10) decisions.push(`read replicas (${readsPerWrite} reads per write)`);
if (writeQPS > 1_000 || stored > 10_000_000_000_000) decisions.push(`shard the data (writes ${commas(writeQPS)}/s, storage ${size(stored)})`);
else decisions.push("one primary database is enough for now");
if (realtime) decisions.push("WebSocket gateways + pub/sub between them (real-time delivery)");
for (const d of decisions) console.log(` - ${d}`);
}
const total = PLAN.reduce((sum, [, m]) => sum + m, 0);
console.log(`45-minute plan (${total} min): ${PLAN.map(([s, m]) => `${s} ${m}`).join(", ")}`);
design("URL shortener: 100M new links a month, 100 reads each, 500 B per link", div(100_000_000, 30), 100, 500, false);
design("chat: 50M daily users, 40 messages each a day, 2 reads per message, 200 B", 50_000_000 * 40, 2, 200, true);// The 45-minute plan, in minutes.
const PLAN: [(&str, i64); 6] = [
("clarify requirements", 5),
("estimate", 5),
("API and data model", 5),
("high-level design", 10),
("deep dives", 15),
("trade-offs and wrap-up", 5),
];
/// 1234567 as 1,234,567.
fn commas(n: i64) -> String {
let s = n.to_string();
let mut out = String::new();
for (i, c) in s.chars().enumerate() {
if i > 0 && (s.len() - i) % 3 == 0 {
out.push(',');
}
out.push(c);
}
out
}
/// A byte count as GB, TB or PB with one decimal.
fn size(b: i64) -> String {
let units = ["GB", "TB", "PB"];
let (mut unit, mut i) = (1_000_000_000i64, 0usize);
while i < units.len() - 1 && b >= unit * 1000 {
unit *= 1000;
i += 1;
}
let tenths = (b * 10 + unit / 2) / unit;
format!("{}.{} {}", tenths / 10, tenths % 10, units[i])
}
/// Turn the numbers into decisions. Each rule says which number triggered it.
fn design(name: &str, writes_per_day: i64, reads_per_write: i64, bytes_per_item: i64, realtime: bool) {
let (day, peak, years, copies) = (86_400, 3, 5, 3);
let write_qps = writes_per_day / day * peak;
let read_qps = writes_per_day * reads_per_write / day * peak;
let stored = writes_per_day * 365 * years * bytes_per_item * copies;
println!("{name}");
println!(
" peak writes {}/s, peak reads {}/s, storage in {years} years with {copies} copies: {}",
commas(write_qps),
commas(read_qps),
size(stored)
);
let mut decisions = vec!["load balancer + at least 2 stateless app servers".to_string()];
if read_qps > 1_000 {
decisions.push(format!("cache hot reads (reads {}/s > 1,000)", commas(read_qps)));
}
if reads_per_write >= 10 {
decisions.push(format!("read replicas ({reads_per_write} reads per write)"));
}
if write_qps > 1_000 || stored > 10_000_000_000_000 {
decisions.push(format!("shard the data (writes {}/s, storage {})", commas(write_qps), size(stored)));
} else {
decisions.push("one primary database is enough for now".to_string());
}
if realtime {
decisions.push("WebSocket gateways + pub/sub between them (real-time delivery)".to_string());
}
for d in decisions {
println!(" - {d}");
}
}
fn main() {
let total: i64 = PLAN.iter().map(|(_, m)| m).sum();
let steps: Vec<String> = PLAN.iter().map(|(s, m)| format!("{s} {m}")).collect();
println!("45-minute plan ({total} min): {}", steps.join(", "));
design("URL shortener: 100M new links a month, 100 reads each, 500 B per link", 100_000_000 / 30, 100, 500, false);
design("chat: 50M daily users, 40 messages each a day, 2 reads per message, 200 B", 50_000_000 * 40, 2, 200, true);
}import java.util.ArrayList;
import java.util.List;
public class Main {
// The 45-minute plan, in minutes.
static final String[] STEPS = {"clarify requirements", "estimate", "API and data model", "high-level design", "deep dives", "trade-offs and wrap-up"};
static final int[] MINUTES = {5, 5, 5, 10, 15, 5};
/** 1234567 as 1,234,567, without depending on the machine's locale. */
static String commas(long n) {
String s = Long.toString(n);
StringBuilder b = new StringBuilder();
for (int i = 0; i < s.length(); i++) {
if (i > 0 && (s.length() - i) % 3 == 0) b.append(',');
b.append(s.charAt(i));
}
return b.toString();
}
/** A byte count as GB, TB or PB with one decimal. */
static String size(long b) {
String[] units = {"GB", "TB", "PB"};
long unit = 1_000_000_000L;
int i = 0;
while (i < units.length - 1 && b >= unit * 1000) { unit *= 1000; i++; }
long tenths = (b * 10 + unit / 2) / unit;
return tenths / 10 + "." + tenths % 10 + " " + units[i];
}
/** Turn the numbers into decisions. Each rule says which number triggered it. */
static void design(String name, long writesPerDay, long readsPerWrite, long bytesPerItem, boolean realtime) {
final long day = 86_400, peak = 3, years = 5, copies = 3;
long writeQPS = writesPerDay / day * peak;
long readQPS = writesPerDay * readsPerWrite / day * peak;
long stored = writesPerDay * 365 * years * bytesPerItem * copies;
System.out.println(name);
System.out.printf(" peak writes %s/s, peak reads %s/s, storage in %d years with %d copies: %s%n", commas(writeQPS), commas(readQPS), years, copies, size(stored));
List<String> decisions = new ArrayList<>(List.of("load balancer + at least 2 stateless app servers"));
if (readQPS > 1_000) decisions.add("cache hot reads (reads " + commas(readQPS) + "/s > 1,000)");
if (readsPerWrite >= 10) decisions.add("read replicas (" + readsPerWrite + " reads per write)");
if (writeQPS > 1_000 || stored > 10_000_000_000_000L) decisions.add("shard the data (writes " + commas(writeQPS) + "/s, storage " + size(stored) + ")");
else decisions.add("one primary database is enough for now");
if (realtime) decisions.add("WebSocket gateways + pub/sub between them (real-time delivery)");
for (String d : decisions) System.out.println(" - " + d);
}
public static void main(String[] args) {
int total = 0;
List<String> steps = new ArrayList<>();
for (int i = 0; i < STEPS.length; i++) {
steps.add(STEPS[i] + " " + MINUTES[i]);
total += MINUTES[i];
}
System.out.printf("45-minute plan (%d min): %s%n", total, String.join(", ", steps));
design("URL shortener: 100M new links a month, 100 reads each, 500 B per link", 100_000_000L / 30, 100, 500, false);
design("chat: 50M daily users, 40 messages each a day, 2 reads per message, 200 B", 50_000_000L * 40, 2, 200, true);
}
}#include <cstdint>
#include <cstdio>
#include <string>
#include <utility>
#include <vector>
using i64 = std::int64_t;
// The 45-minute plan, in minutes.
const std::vector<std::pair<std::string, int>> kPlan = {
{"clarify requirements", 5}, {"estimate", 5}, {"API and data model", 5},
{"high-level design", 10}, {"deep dives", 15}, {"trade-offs and wrap-up", 5},
};
// 1234567 as 1,234,567.
std::string commas(i64 n) {
std::string s = std::to_string(n), out;
for (size_t i = 0; i < s.size(); i++) {
if (i > 0 && (s.size() - i) % 3 == 0) out += ',';
out += s[i];
}
return out;
}
// A byte count as GB, TB or PB with one decimal.
std::string size(i64 b) {
const char* units[] = {"GB", "TB", "PB"};
i64 unit = 1'000'000'000;
int i = 0;
while (i < 2 && b >= unit * 1000) {
unit *= 1000;
i++;
}
i64 tenths = (b * 10 + unit / 2) / unit;
return std::to_string(tenths / 10) + "." + std::to_string(tenths % 10) + " " + units[i];
}
// Turn the numbers into decisions. Each rule says which number triggered it.
void design(const char* name, i64 writesPerDay, i64 readsPerWrite, i64 bytesPerItem, bool realtime) {
const i64 day = 86'400, peak = 3, years = 5, copies = 3;
i64 writeQPS = writesPerDay / day * peak;
i64 readQPS = writesPerDay * readsPerWrite / day * peak;
i64 stored = writesPerDay * 365 * years * bytesPerItem * copies;
std::printf("%s\n", name);
std::printf(" peak writes %s/s, peak reads %s/s, storage in %lld years with %lld copies: %s\n", commas(writeQPS).c_str(),
commas(readQPS).c_str(), static_cast<long long>(years), static_cast<long long>(copies), size(stored).c_str());
std::vector<std::string> decisions = {"load balancer + at least 2 stateless app servers"};
if (readQPS > 1'000) decisions.push_back("cache hot reads (reads " + commas(readQPS) + "/s > 1,000)");
if (readsPerWrite >= 10) decisions.push_back("read replicas (" + std::to_string(readsPerWrite) + " reads per write)");
if (writeQPS > 1'000 || stored > 10'000'000'000'000LL)
decisions.push_back("shard the data (writes " + commas(writeQPS) + "/s, storage " + size(stored) + ")");
else
decisions.push_back("one primary database is enough for now");
if (realtime) decisions.push_back("WebSocket gateways + pub/sub between them (real-time delivery)");
for (const auto& d : decisions) std::printf(" - %s\n", d.c_str());
}
int main() {
int total = 0;
std::string steps;
for (size_t i = 0; i < kPlan.size(); i++) {
steps += (i ? ", " : "") + kPlan[i].first + " " + std::to_string(kPlan[i].second);
total += kPlan[i].second;
}
std::printf("45-minute plan (%d min): %s\n", total, steps.c_str());
design("URL shortener: 100M new links a month, 100 reads each, 500 B per link", 100'000'000LL / 30, 100, 500, false);
design("chat: 50M daily users, 40 messages each a day, 2 reads per message, 200 B", 50'000'000LL * 40, 2, 200, true);
}ছয়টাই হুবহু এটা print করে:
45-minute plan (45 min): clarify requirements 5, estimate 5, API and data model 5, high-level design 10, deep dives 15, trade-offs and wrap-up 5
URL shortener: 100M new links a month, 100 reads each, 500 B per link
peak writes 114/s, peak reads 11,574/s, storage in 5 years with 3 copies: 9.1 TB
- load balancer + at least 2 stateless app servers
- cache hot reads (reads 11,574/s > 1,000)
- read replicas (100 reads per write)
- one primary database is enough for now
chat: 50M daily users, 40 messages each a day, 2 reads per message, 200 B
peak writes 69,444/s, peak reads 138,888/s, storage in 5 years with 3 copies: 2.2 PB
- load balancer + at least 2 stateless app servers
- cache hot reads (reads 138,888/s > 1,000)
- shard the data (writes 69,444/s, storage 2.2 PB)
- WebSocket gateways + pub/sub between them (real-time delivery)
Output টা পড়ো:
- plan: ছয়টা ধাপ মিলে 45 মিনিট, সবচেয়ে বেশি সময় high-level design আর deep dive এ।
- URL shortener: peak এ সেকেন্ডে মোটামুটি 114 write আর 11,574 read, কপি সহ পাঁচ বছরে 9.1 TB। Rule গুলো দেয় একটা cache (read বেশি), read replica (write প্রতি 100 read), আর একটা primary database ই যথেষ্ট। এটা দ্বিতীয় candidate এর design, প্রথমজনের না।
- chat: সেকেন্ডে 69,444 write আর 138,888 read, 2.2 PB। এখন write আর storage দুইটা দিয়েই sharding এর যুক্তি আছে, আর real-time delivery যোগ করে pub/sub সহ WebSocket gateway।
- মূল কথা: একই rule একদম আলাদা design দেয়, কারণ সংখ্যা আলাদা। Interview তে আগে সংখ্যাটা বলো, তারপর box টা।
- কোন language এ কী বদলালো: বাড়তে পারে এমন list এ সিদ্ধান্ত জমা করা (
append,.append,push,add,push_back) আর বড় সংখ্যায় comma বসানো। TypeScript সাধারণ number ব্যবহার করতে পারে কারণ 2.2 PB মানে মোটামুটি 2.2 × 10^15 byte, 2^53 এর নিচে; বাকিরা 64-bit integer ব্যবহার করে।
বাস্তবে HLD interview
- জোরে জোরে অনুশীলন করো, timer সহ, whiteboard বা shared doc এ। একবার নিজেকে record করো; বেশিরভাগ মানুষ যতটা ভাবে তার চেয়ে কম বলে আর নিজের যুক্তি বাদ দিয়ে যায়।
- কয়েকটা building block এর ছোট একটা ভাণ্ডার বানাও যেগুলো ভালো করে বোঝাতে পারো: load balancer, cache-aside, replica, consistent hashing দিয়ে sharding, queue আর idempotent consumer, object storage আর CDN, rate limiting, unique ID বানানো, WebSocket। Course এর এই part টাই সেই ভাণ্ডার।
- চেনা সমস্যাগুলো অনুশীলন করো (URL shortener, news feed, chat, notification, rate limiter, ride-hailing, video streaming) HLD problems part এ, কিন্তু উত্তর মুখস্থ না করে approach এ মন দাও। এই course এর শেষ নীতি: ভালো যুক্তিতে সমাধান করা একটা অচেনা সমস্যা মুখস্থ করা একটার চেয়ে ভালো।
- Production এর বোধ দেখাও: monitoring আর SLO, failure সামলানো, deploy, খরচ। সব কিছু বলতে হবে না; শেষে প্রতিটা নিয়ে একটা বাক্য বোঝায় তুমি system চালিয়েছ, শুধু এঁকেছ না।
- সৎ থাকো: কোনো technology না জানলে, যে গুণটা লাগবে সেটা বলো ("replay সহ একটা টেকসই log"), এমন product এর নাম বোলো না যেটার পক্ষে দাঁড়াতে পারবে না।
- কোনো company র practice বানিয়ে বোলো না। নির্দিষ্ট company কীভাবে করে সেটা সত্যিই না জানলে বলো "একটা সাধারণ approach হলো…"।
যে design গুলো পরে problem create করে
Interview এর ভুলটা ধরো
50,000 user এর জন্য multi-region active-active
একটু থামো, ভাবো
"একটা notification system design করো" এর 45 মিনিটের interview এর তুমি 30 মিনিটে আছো। Clarify, estimate আর high-level design আঁকা হয়ে গেছে। Interviewer বললেন: "ধরো একটা marketing campaign একসাথে 2 কোটি user এর সবাইকে push notification পাঠাবে।" বাকি 15 মিনিট কী করবে?
উত্তর দেখো
এটাকে তাদের চাওয়া deep dive হিসেবে ধরো। আগে সংখ্যা: একসাথে 2 কোটি notification। Push provider রা যদি তোমার কাছ থেকে সেকেন্ডে ধরো 10,000টা নেয়, তাহলে লাগে প্রায় 33 মিনিট, তাই campaign টা জেনেবুঝে ছড়িয়ে দিতে হবে। তারপর design: campaign টা একটা queue তে দাও আর worker দিয়ে batch এ ছড়াও, প্রতি provider এ rate-limit সহ; প্রতিটা send কে (campaign, user) ধরে idempotent করো যাতে retry দুইবার notify না করে; প্রতিটা user এর পছন্দ আর quiet hours মানো; campaign এর traffic আলাদা, কম অগ্রাধিকারের queue তে রাখো যাতে password-reset বা OTP message দেরি না করায়। শেষে failure আর observability: provider throttle করলে কী হবে (backoff, একটা checkpoint থেকে আবার শুরু), আর কোন metric অগ্রগতি দেখায় (পাঠানো, fail, বাকি, provider এর error rate)। Trade-off দিয়ে শেষ করো: পাঠানো ছড়িয়ে দেওয়া system আর provider দুইটাকেই বাঁচায়, দাম হলো কিছু user message টা কয়েক মিনিট পরে পায়।
ছোট্ট quiz
1. HLD interview এর মূল ধাপগুলো কী, আর প্রতিটায় মোটামুটি কত সময়?
উত্তর
Clarify (মোটামুটি 5 মিনিট), estimate (5), API আর data model (5), high-level design (10), deep dive (15), trade-off আর শেষ কথা (5)। Interviewer যেদিকে নিয়ে যান সেই অনুযায়ী বদলাও।
2. Design এর আগে আন্দাজ কেন?
উত্তর
সংখ্যাই design ঠিক করে: read বেশি নাকি write বেশি, কত storage, peak এ কত request। এগুলো ছাড়া component এর যুক্তি দেওয়া যায় না, design হয়ে যায় আন্দাজ।
3. কোন অংশে deep dive করবে সেটা কীভাবে বাছবে?
উত্তর
যে অংশগুলো এই সমস্যার জন্য সবচেয়ে কঠিন বা সবচেয়ে বিশেষ সেগুলো বাছো (যেখানে ভয়ের সংখ্যাটা, বা যেখানে সঠিকতা জরুরি), আর interviewer কী ঘাঁটতে চান তার ইঙ্গিত মেনে চলো।
4. কোন জিনিস একটা উত্তরকে senior এর মতো শোনায়?
উত্তর
যুক্তি সহ স্পষ্ট trade-off, প্রতিটা কাজ ধরে consistency বাছা, failure আর চালানো নিয়ে আলোচনা (monitoring, deploy, খরচ), আর 10x scale এ কী বদলাবে বলা, সবকিছু করে design টাকে requirement যতটা দেয় ততটাই সহজ রাখা।
তোমার challenge: তৃতীয় একটা সমস্যা, আর একটা rule যেটা ঠিক করা দরকার
একটা rule আর একটা সমস্যা যোগ করো। Rule: একটা item অন্তত 100 KB হলে যোগ করো files in object storage + CDN (N KB each)। সমস্যা, আগের দুইটার মাঝখানে: "photo feed: 400K uploads a day, 250 views each, 500 KB per photo", দিনে 400,000 write, write প্রতি 250 read, item প্রতি 500,000 byte, real-time না।
- নতুন rule টা real-time rule এর আগে বসাও।
- বাকি দুইটা সমস্যার output বদলাবে না।
তুমি পেরেছো যখন photo feed এর অংশটা হবে:
photo feed: 400K uploads a day, 250 views each, 500 KB per photo
peak writes 12/s, peak reads 3,471/s, storage in 5 years with 3 copies: 1.1 PB
- load balancer + at least 2 stateless app servers
- cache hot reads (reads 3,471/s > 1,000)
- read replicas (250 reads per write)
- shard the data (writes 12/s, storage 1.1 PB)
- files in object storage + CDN (500 KB each)
এবার "shard the data" ভালো করে দেখো। এটা চালু হয়েছে 1.1 PB এর কারণে, কিন্তু ওই byte গুলো ছবি, আর ছবি যায় object storage এ, database এ না। Database এ থাকে প্রতিটা ছবির কয়েকশো byte এর metadata, বড়জোর কয়েক terabyte। চোখ বন্ধ করে সংখ্যা মেনে চলা rule ও ভুল হতে পারে: interview তে কিছু shard করার আগে দেখে নাও প্রতিটা সংখ্যা আসলে কোন store এ গিয়ে পড়ে।
এক লাইনে
Requirement পরিষ্কার করে লিখে ফেলো, যে সংখ্যাগুলো গুরুত্বপূর্ণ সেগুলো আন্দাজ করে ভয়েরটার নাম বলো, API আর data ঠিক করো, সবচেয়ে সহজ design আঁকো আর প্রতিটা box এর যুক্তি দাও একটা সংখ্যা দিয়ে, failure সহ সবচেয়ে কঠিন অংশগুলোয় গভীরে যাও, trade-off আর 10x এ কী বদলাবে সেটা বলো, আর পুরো সময় জোরে জোরে ভাবো।
Station stamp
Chapter শেষ করো
পড়া শেষ হলে Complete চাপো, progress save হবে। XP শুধু Pro তে — Pro থাকলে এই chapter এ +10 XP পেতে।
Pro নিয়ে XP পাও →