48. Walkthrough: Design Instagram
The prompt. "Design Instagram." Variants: "Design a photo-sharing service" (Google), "Design the Instagram feed" or "Design Instagram Stories" (Meta), "Design a social media app with images and a feed" (Microsoft and others). This is a breadth problem. Your main job is scoping, then going deep on two or three parts.
Step 0 — Clarify the business goal (≈ 3 min)
🎤 Candidate: Before I design, what's the business priority? Instagram earns money from ads, so is the goal engagement (time spent, DAU)? Or are we building v1 of a new product, where speed to market matters more?
🧑💼 Interviewer: Assume Instagram today, at its current scale. Engagement and reliability matter most.
🎤 Candidate: Then feed and Stories latency and freshness drive engagement, media delivery is the main cost, and trust (privacy, blocking, content safety) is a constraint everywhere. Should I cover Stories, Explore, DMs, and Reels, or focus?
🧑💼 Interviewer: Focus on posting photos/videos, following, the home feed, likes/comments, and Stories. Mention Explore briefly.
Why this matters: you've tied the design to a metric (engagement), identified the cost driver (media egress), and cut scope with the interviewer's agreement, all in three minutes.
Step 1 — Functional requirements (≈ 2 min)
| In scope | Out of scope (say so explicitly) |
|---|---|
| Upload photo/video posts (incl. carousels), captions | DMs (separate messaging system, §34) |
| Follow / unfollow; public & private accounts | Reels' recommendation engine (mention only) |
| Home feed (ranked), paginated | Ads auction (only "ads slots are inserted into the feed") |
| Like, comment, view counts | Search (mention: §15) |
| Stories: post, view, 24 h expiry, "seen" state | Shopping, live video |
| Notifications (likes, comments, follows) | Creator analytics |
Step 2 — Non-functional requirements (≈ 3 min)
| NFR | Target | Reason |
|---|---|---|
| Scale | 2B MAU, 500M DAU | Given/assumed |
| Feed load latency | p99 < 300 ms server-side; first image visible < 1 s on 4G | Engagement |
| Post publish | Visible to self immediately; to followers within ~10 s | Read-your-writes for the author; eventual for others |
| Availability | Feed/Stories read 99.99%; posting 99.95% | Reading is the core experience; posts can queue on the device |
| Durability | Media and posts: never lose them (11 nines on blobs) | User trust |
| Consistency | Like counts approximate; blocks/privacy strictly enforced | Privacy is never eventual |
| Cost | Optimize media storage and egress | Largest line items |
Step 3 — Estimates (≈ 4 min)
Posts: 100M posts/day → ~1.2k/s avg, ~3.5k/s peak
Stories: 500M stories/day → ~6k/s avg, ~18k/s peak
Feed reads: 500M DAU × ~10 feed fetches/day = 5B/day → ~60k/s avg, ~180k/s peak
Story tray reads: similar order → ~60k/s avg
Likes: 4B/day → ~46k/s avg, ~140k/s peak (hot posts: thousands/s on ONE post)
Media storage (new per day):
Photos: 100M × 70% × ~3 MB (original + 4-5 resized variants) ≈ 210 TB/day
Videos: 30M × ~60 MB (all renditions) ≈ 1.8 PB/day
Stories: 500M × ~1.5 MB avg (expire, but archived for "highlights"/memories) ≈ 750 TB/day
→ ~2.5-3 PB/day raw → ~1 EB/year → erasure coding (≈1.4×) + hot/warm tiering are mandatory
Egress: 5B feed fetches × 12 items × ~150 KB (adaptive-size images, video previews)
≈ 9 PB/day ≈ ~0.8 Tbps average just for feed images; video adds multiples
→ CDN + ISP-embedded caches; cost is dominated by delivery
Metadata: 100M posts × ~1 KB = 100 GB/day → ~36 TB/yr (+ indexes, replicas) → sharded SQL is fine
Feed cache: 500M DAU × 500 post IDs × 8 B ≈ 2 TB → a memory tier of tens of nodes (+ replicas)
🎤 Candidate: Three decisions come out of this. First, media is exabyte-scale, so blob storage and CDN design dominate cost. Second, feed reads outnumber posts about 50:1, so I'll precompute candidate feeds. Third, likes have extreme per-key skew, so counters need special handling.
Step 4 — APIs & data model (≈ 5 min)
POST /v1/media/upload_sessions {type, size, sha256} → {media_id, upload_urls[] (resumable parts)}
POST /v1/posts {media_ids[], caption, location?, audience} + Idempotency-Key → {post_id}
GET /v1/feed?cursor=... → {items[], next_cursor}
POST /v1/users/{id}/follow DELETE /v1/users/{id}/follow
POST /v1/posts/{id}/likes DELETE /v1/posts/{id}/likes (idempotent: PUT semantics)
GET /v1/posts/{id}/comments?cursor=... POST /v1/posts/{id}/comments
POST /v1/stories {media_id} GET /v1/stories/tray POST /v1/stories/{id}/seen
| Entity | Store | Primary / partition key | Notes |
|---|---|---|---|
| users | Sharded SQL (TAO-like graph cache in front) | user_id |
Profile, privacy flag |
| posts | Sharded SQL | post_id with shard bits in the ID (Instagram-style 41b time + 13b shard + 10b seq), shard = author's shard |
Author's posts colocated → "profile grid" is a single-shard query |
| media | Sharded SQL (metadata) + blob store | media_id |
Variants map: size → blob key; status: uploading/processing/ready/blocked |
| follow edges | Graph store (two directions) | (follower_id → followee_id) and (followee_id → follower_id) |
Both directions are needed: "who do I follow" and "who follows me" |
| likes | Wide-column / sharded SQL | (post_id, user_id) |
PK uniqueness = no double likes. A second index (user_id, post_id) answers "did I like these?" |
| like/comment counters | Counter service | post_id |
Sharded, aggregated (Deep dive 3) |
| feed candidates | Memory cache / wide-column | user_id → [post_id...] |
Capped at ~500 |
| stories | KV with TTL | author_id → [story_id, expires_at] |
Plus a (viewer_id, author_id) → last_seen state |
Step 5 — High-level architecture (≈ 8 min)
Mobile app ──▶ Edge (CDN for media; API edge for TLS + routing)
│
▼
API gateway (auth, rate limit) ──▶ GraphQL/BFF aggregation for mobile
┌──────────────┬───────────────┬─────────────┬──────────────┬────────────────┬─────────────┐
▼ ▼ ▼ ▼ ▼ ▼ ▼
Upload/Media Post svc Graph svc Feed svc Engagement svc Stories svc Notification
svc (publish, (follow, (candidates, (likes, (tray, svc
(sessions, visibility) block, mute) ranking, comments, seen state)
processing) hydration) counters)
│ │ │ │ │ │
▼ ▼ ▼ ▼ ▼ ▼
Blob store Sharded SQL Graph store Feed cache Likes store Stories KV (TTL)
(hot→warm, (posts, + cache (Redis/ + counter + seen-state store
erasure-coded) media meta) memcache) aggregator
│ │
│ └─▶ outbox ─▶ Kafka "post_published" ──┬─▶ Fan-out workers ─▶ feed cache
│ ├─▶ Notification svc ─▶ APNs/FCM
└─▶ Media pipeline (transcode, resize, moderation) ├─▶ Search/Explore indexing
└─▶ Analytics / ML features
Ranking svc (ML models, feature store) ◀── Feed svc, Explore svc
Walk the write path out loud (posting a photo):
- The app asks for an upload session and uploads parts directly to the blob store with presigned URLs, so bytes never pass through API servers.
- Upload complete triggers the media pipeline: virus/format checks →
strip EXIF GPS data → resize to variants → video transcode → integrity
checks (hash matching against known abusive content) → media status
ready. - The app calls
POST /postswith media IDs and an idempotency key. The Post service verifies the media isreadyand owned by the caller, writes the post row plus an outbox row in the author's shard in one transaction, and returns it. - The app shows the post optimistically in the author's own feed and profile (read-your-writes).
- Kafka
post_publishedtriggers fan-out to followers' feed caches (hybrid, Deep dive 2), notifications to close friends who opted in, and indexing.
Walk the read path (opening the app):
GET /feed→ Feed service reads the precomputed candidate list (~500 IDs) plus celebrity posts (pull) → ranking → top 20.- Hydration: batch-fetch post metadata, author info, "viewer liked?", and counts, all multi-get from caches.
- Visibility filter: deleted? author blocked me? private and I'm no longer a follower? (always checked at read time).
- The response contains CDN URLs for media sized for this device. The app fetches images from the CDN, preloading the next page.
Step 6 — Deep dives (≈ 15 min)
Deep dive 1: media storage and delivery (the cost center)
Upload ─▶ Hot blob tier (SSD/HDD, 3× replication or fast EC) ← first ~days: most reads happen here
─▶ age/popularity policy ─▶ Warm tier (erasure coded ~1.4×, HDD, fewer IOPS)
Delivery: CDN (edge) ─miss─▶ regional cache ─miss─▶ origin "photo server" ─▶ blob store
URL: https://cdn.example/v/t51/<media_id>_<variant>.jpg?sig=…&exp=… (signed, expiring)
- Small-file problem: billions of small photos as separate files kill file-system metadata performance. Facebook's Haystack (OSDI 2010) packs photos into large append-only volume files with an in-memory index (one disk seek per read). f4 (OSDI 2014) moved older, "warm" blobs to erasure-coded storage, cutting the replication factor significantly.
- Variant strategy: precompute 4–5 common sizes at upload (instant delivery), or generate on the fly at the edge and cache them (less storage, more CPU, and risk on cache misses). Common answer: precompute the popular sizes, generate rare ones on demand.
- Formats: WebP/AVIF for images and adaptive bitrate for video (§37). A 20–30% byte reduction on egress is millions of dollars.
- Privacy at the CDN: a private account's media URL must not be shareable forever. Signed, expiring URLs, with the signature bound to the media ID and an expiry.
Deep dive 2: home feed generation and ranking
Candidate generation (≈ 30 ms) Ranking (≈ 80-120 ms) Post-processing (≈ 30 ms)
───────────────────────────── ───────────────────── ─────────────────────────
feed_cache[user] (pushed, ~500) features: viewer×author affinity, diversity (no 5 posts
+ celebrity posts I follow (pulled) post age, media type, engagement from one author in a row)
+ "unseen since last session" filter velocity, viewer history insert ads slots
→ ~500-1500 candidates multi-task model predicts apply visibility filter
P(like), P(comment), P(save), hydrate top 20
P(dwell>N s) → weighted score
- Hybrid fan-out: push for normal accounts, pull for accounts with more than ~100k followers (§33). Skip inactive followers.
- Ranking needs features fast: a feature store with online KV lookups, batch-fetched per request. Precompute author-level features; compute viewer-author affinity offline daily, with real-time updates for recent interactions.
- Latency budget: 300 ms p99 total, so the ranking model must run in about 100 ms for ~1,000 candidates. Use a cheap first-stage ranker to prune to ~200, then a heavy model.
- Why ranked, not chronological: Instagram moved to a ranked feed in 2016 because people were missing most posts from accounts they cared about. That's the business outcome behind the architecture.
Deep dive 3: likes and counters on hot posts
🧑💼 Interviewer: A celebrity's post gets 2 million likes in an hour. What happens?
🎤 Candidate: That's ~550 likes/s on one post, and peaks are much higher in the first minutes. Three separate concerns:
- The like edge
(post_id, user_id): a uniqueness-protected insert. It's idempotent, because a double tap is a no-op. Partitioned bypost_id, the hot post is one partition, but at 550–5,000 writes/s a wide-column partition copes. Writes are small and append-like.- The counter: never
UPDATE posts SET likes = likes + 1. Like events go to Kafka, and an aggregator sums them in memory per post and flushes every second to a sharded counter (N sub-counters per hot post, summed on read and cached). The display is approximate ("2.1M"), which is fine.- "Did I like it?" on every feed item: answer from the
(user_id, post_id)index with a batch multi-get for the 20 posts in a page, cached per user.
Deep dive 4: Stories (ephemeral, high-churn)
- Why not reuse the feed? Stories expire after 24 h and are viewed as a tray of authors, not posts, and the tray order depends on "unseen first" plus affinity. Precomputing fan-out for 500M stories/day that expire is wasteful.
- Design: each author's active stories are a short list in a KV store with TTL. The tray is built at read time: take who I follow (~hundreds), keep those with active stories (a per-author "has active story" bitmap or cache), sort by unseen-first, then affinity. Cache the tray for a few minutes.
- Seen state:
(viewer_id, author_id) → last_seen_story_tsis very high write volume (billions of views a day). Batch writes from the client (send seen events every few seconds), and store them in a write-optimized store with TTL ~48 h. - "Seen by" list for authors: a celebrity story has 10M viewers, so store an approximate count plus the viewer list paginated, or capped for very large accounts.
Step 7 — Failure modes, scale, cost, evolution (≈ 5 min)
| Failure | Impact | Mitigation |
|---|---|---|
| Feed cache cluster loss | Feed reads fall back to pull, causing a DB storm | Replicated cache across AZs; pull path with strict concurrency limits; serve a stale "last feed" snapshot from the client |
| Ranking service slow | Feed p99 blows up | Timeout at ~100 ms → fall back to a lightweight ranker or chronological order (graceful degradation) |
| Media pipeline backlog | Posts stuck "processing" | Separate queues per media type; autoscale on queue age; publish a photo before its video renditions finish |
| One region down | Users in that region affected | Home-region per user, async replication, evacuation (§20); uploads queue on the device and retry |
| Fan-out backlog (celebrity storm) | Followers see posts late | Pull path for celebrities, priority lanes for active followers |
10× scale: the first bottlenecks are media egress cost, fan-out write volume, and ranking compute. Levers: better codecs, more aggressive inactive-user skipping, and model distillation.
Cost: the largest lines are CDN egress, warm storage, and ranking GPU/CPU, in that order. Erasure coding plus tiering and the egress levers are your cost story.
Ownership: Media platform, Feed and Ranking, Graph, Integrity (safety), and Notifications are separate teams. Shared infrastructure (blob store, CDN, graph store, Kafka) belongs to platform teams. Instagram inside Meta reuses company infrastructure (TAO, blob storage, CDN), which is a build-vs-reuse decision worth naming.
Step 8 — Wrap-up (≈ 1 min)
🎤 Candidate: To summarize: we optimized for engagement through a precomputed, ranked feed with p99 under 300 ms, kept privacy strictly enforced at read time, and treated media delivery as the main cost center with tiered, erasure-coded storage behind a CDN. Biggest risks: fan-out load from very large accounts (hybrid pull), and ranking latency (timeouts with fallback to a simpler ranker). Next I'd go deeper on Explore, which reuses the same ranking stack with an embedding-based candidate generator.
Interviewer follow-ups & strong answers
| Follow-up | Strong answer |
|---|---|
| "How does the author see their post immediately?" | Optimistic client insert plus write-through into their own feed and profile cache; read their own posts from the primary for N seconds (§8.2) |
| "User blocks someone. How fast is it enforced?" | Synchronously: the block edge is written to the strongly consistent graph store, and every read path filters on block edges. Cached feeds may still contain the item, but it's filtered at hydration |
| "Account switches from public to private?" | Followers stay; non-followers lose access immediately via the read-time check. CDN URLs expire quickly (signed); cached public URLs are purged by tag |
| "Delete a post?" | Soft delete (tombstone) → filtered at read time immediately → async purge from caches, CDN (by surrogate key), search, and ML features; hard-delete media after a retention window (legal holds respected) |
| "Explore?" | Candidate generation from account and post embeddings (nearest neighbors to accounts you engage with) → the same multi-stage ranking with integrity filters. Instagram's 2019 blog describes this exact funnel |
| "Multi-region?" | Users have a home region for writes; reads are served from local replicas; media is replicated by popularity; the CDN is global |
🌍 Real-world references
- Instagram Engineering, "What Powers Instagram" (2011): Django, Postgres, Redis, Memcached, and a small team; "Sharding & IDs at Instagram" (2012): the ID scheme used in Step 4.
- The 2014 "Instagration": Instagram moved from AWS into Facebook's data centers, the classic "reuse the parent company's infrastructure" migration.
- "Open-sourcing a 10x reduction in Apache Cassandra tail latency" (2018): Instagram replaced Cassandra's storage engine with RocksDB ("Rocksandra") to remove JVM GC from the tail.
- "Dismissing Python Garbage Collection at Instagram" (2017): disabling GC improved memory sharing between worker processes (fewer copy-on-write page copies). A great "corner case at scale" story.
- "Powered by AI: Instagram's Explore recommender system" (2019): embedding-based candidate generation plus multi-stage ranking.
- Facebook Haystack (OSDI 2010) and f4 (OSDI 2014): photo storage at scale.
⚠️ Corner cases
- Carousel with 10 media, one fails processing: publish is atomic. The
post goes live only when all media are
ready. Otherwise the user gets a retry or remove choice. - Retry after timeout creates duplicate posts: use an idempotency key per compose session (§5.2).
- EXIF location leaks home addresses: strip it at ingest. Location is only what the user explicitly tags.
- Story expiry: storage TTL cleanup lags behind exact expiry, so filter by
expires_atat read time, never rely on deletion timing. - Hot comment threads: a viral post gets 100k comments/hour. Paginate by
(post_id, bucket), rank top comments separately, and rate-limit per user. - Hashtags and Unicode: normalize (NFC, case folding) and handle emoji-only tags and right-to-left scripts.
- Account deletion (GDPR): a cascading async workflow across posts, media, likes, comments on others' posts, backups (crypto-shredding), search, and ML features, with an auditable completion record.
- Minors and safety: default-private accounts for teens and message restrictions. Policy requirements become architecture (age signals in visibility checks).
Signals by level on this problem
| Senior | Staff | Principal |
|---|---|---|
| Draws upload, feed, and like services correctly; walks the write and read paths | Scopes with the interviewer; quantifies read:write and media volume | Opens with "engagement and media cost are the two forces" and ties every deep dive to them |
| Picks fan-out on write | Hybrid fan-out with a celebrity threshold; skips inactive users | Adds the ranking latency budget and degradation path; weighs fan-out cost against engagement |
| "Store images in S3 behind a CDN" | Precomputed variants, signed URLs, CDN hit-ratio thinking | Hot/warm tiers, erasure coding, egress as the main cost line with concrete levers |
| Counts likes with a counter | Separates the like edge from an async sharded counter | Separates edge, counter, and "did I like it", each with its own consistency, and says why |
| Mentions privacy checks | Enforces blocks and privacy at read time | Adds deletion/GDPR, the integrity pipeline, org ownership, and platform reuse (TAO/blob/CDN) |
EM / Sr. EM: the Staff column plus ownership boundaries (media, feed and ranking, graph, integrity), a v1 cut, and on-call staffing for the media pipeline.