Frontend Labs
⋯

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):

  1. 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.
  2. 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.
  3. The app calls POST /posts with media IDs and an idempotency key. The Post service verifies the media is ready and owned by the caller, writes the post row plus an outbox row in the author's shard in one transaction, and returns it.
  4. The app shows the post optimistically in the author's own feed and profile (read-your-writes).
  5. Kafka post_published triggers 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):

  1. GET /feed → Feed service reads the precomputed candidate list (~500 IDs) plus celebrity posts (pull) → ranking → top 20.
  2. Hydration: batch-fetch post metadata, author info, "viewer liked?", and counts, all multi-get from caches.
  3. Visibility filter: deleted? author blocked me? private and I'm no longer a follower? (always checked at read time).
  4. 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:

  1. The like edge (post_id, user_id): a uniqueness-protected insert. It's idempotent, because a double tap is a no-op. Partitioned by post_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.
  2. 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.
  3. "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_ts is 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_at at 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.


Generated from real-life-system-design-guide.md · 8 Oct 2026 16:36