1. Requirements & Scope (5 min)

Functional Requirements

  1. Users can upload photos with captions
  2. Users can follow/unfollow other users
  3. Users can view a personalized news feed (photos from people they follow)
  4. Users can like and comment on photos
  5. Users can view any user’s profile (grid of their photos)

Non-Functional Requirements

  • Availability: 99.99% — social feeds being down is immediately visible to millions
  • Latency: Feed load < 300ms at p99, photo upload acknowledgment < 2s
  • Consistency: Eventual consistency for feed (2-5 seconds stale is fine). Strong consistency for uploads (post → refresh → must see it)
  • Scale: 500M DAU, 50M photo uploads/day, average user views feed 10 times/day
  • Storage: Photos are large; need cost-efficient media storage

2. Estimation (3 min)

Traffic

  • Uploads: 50M/day ÷ 100K = 500 writes/sec, peak 2,500/sec
  • Feed reads: 500M × 10/day = 5B reads/day ÷ 100K = 50,000 reads/sec, peak 250,000/sec
  • Read-to-write ratio: 100:1 — extremely read-heavy

Storage

  • Average photo: 2MB original, store 4 sizes (thumbnail 50KB, small 200KB, medium 500KB, large 2MB) = ~2.75MB total per photo
  • 50M photos/day × 2.75MB = 137TB/day
  • Per year: ~50PB — this is a massive storage system

Bandwidth

  • Feed: 10 photos per load × 500KB avg = 5MB per feed load
  • 50,000 feeds/sec × 5MB = 250GB/sec peak read bandwidth
  • CDN is absolutely critical

3. API Design (3 min)

POST /api/v1/photos
  Content-Type: multipart/form-data
  Body: {
    "photo": <binary>,
    "caption": "Sunset vibes",
    "location": "Mumbai, India"     // optional
  }
  Response 201: {
    "photo_id": "p_abc123",
    "urls": {
      "thumbnail": "https://cdn.ig.com/thumb/p_abc123.jpg",
      "full": "https://cdn.ig.com/full/p_abc123.jpg"
    }
  }

GET /api/v1/feed?cursor={cursor}&limit=10
  Response 200: {
    "photos": [
      {
        "photo_id": "p_abc123",
        "user": { "id": "u_1", "username": "chirag", "avatar": "..." },
        "caption": "Sunset vibes",
        "photo_url": "https://cdn.ig.com/med/p_abc123.jpg",
        "like_count": 2847,
        "comment_count": 43,
        "liked_by_me": true,
        "created_at": "2026-02-22T18:00:00Z"
      }
    ],
    "next_cursor": "ts_1708632000"
  }

POST /api/v1/photos/{photo_id}/like
DELETE /api/v1/photos/{photo_id}/like

POST /api/v1/users/{user_id}/follow
DELETE /api/v1/users/{user_id}/follow

GET /api/v1/users/{user_id}/photos?cursor={cursor}&limit=30

Key decisions:

  • Cursor-based pagination using timestamp — handles real-time insertions correctly
  • Pre-signed upload URLs alternative: instead of uploading through our API, return a pre-signed S3 URL for direct client→S3 upload, reducing server load
  • liked_by_me included in feed response to avoid N+1 queries on the client

4. Data Model (3 min)

Users & Follows (PostgreSQL)

Table: users
  user_id      (PK) | bigint
  username           | varchar(30), unique
  avatar_url         | text
  bio                | varchar(300)
  follower_count     | int (denormalized)
  following_count    | int (denormalized)

Table: follows
  follower_id        | bigint (FK → users)
  followee_id        | bigint (FK → users)
  created_at         | timestamptz
  PK: (follower_id, followee_id)
  Index: (followee_id, follower_id)  -- for "who follows me" queries

Photos (PostgreSQL + S3)

Table: photos
  photo_id     (PK) | bigint
  user_id            | bigint (FK → users)
  caption            | text
  s3_key             | varchar(200)
  location           | varchar(100)
  like_count         | int (denormalized)
  comment_count      | int (denormalized)
  created_at         | timestamptz
  Index: (user_id, created_at DESC)  -- for profile grid

Feed (Redis)

Key: feed:{user_id}
Value: Sorted set of photo_ids, scored by created_at timestamp
TTL: 7 days (rebuild from DB on cold start)

Likes (Cassandra)

Table: likes
  photo_id     (partition key) | bigint
  user_id      (clustering key)| bigint
  created_at                   | timestamp

Why Cassandra for likes? High write volume (millions of likes/sec), simple access pattern (check if user liked a photo, get all likers), and append-heavy workload.


5. High-Level Design (12 min)

Photo Upload Path

Client → Load Balancer → Upload Service
  → Get pre-signed S3 URL → Client uploads directly to S3
  → Upload Service notified (S3 event / callback)
  → Image Processing Service (async via SQS/Kafka):
    → Resize to 4 sizes (thumbnail, small, medium, large)
    → Store resized images in S3
    → Push to CDN
  → Write metadata to PostgreSQL
  → Trigger fan-out: Kafka → Feed Service → update followers' Redis feeds

Feed Read Path

Client → CDN (cache feed API? usually no — personalized)
  → Load Balancer → Feed Service
    → Read from Redis sorted set (feed:{user_id})
      → Get top N photo_ids
      → Batch fetch photo metadata from PostgreSQL (or cache)
      → Batch fetch "liked_by_me" from Cassandra
      → Assemble response
    → Redis miss → Fall back to fan-out-on-read (pull from followees)

Components

  1. Upload Service: Handles photo upload coordination
  2. Image Processing Service: Async resizing pipeline (SQS → Lambda or dedicated workers)
  3. Feed Service: Generates and serves personalized feeds
  4. Fan-out Service: Pushes new posts to followers’ feed caches
  5. PostgreSQL: User/photo metadata (primary + read replicas)
  6. Cassandra: Likes, comments (high write throughput)
  7. Redis Cluster: Pre-computed feed caches
  8. S3: Photo storage (all sizes)
  9. CDN (CloudFront): Serve photos — this handles 99%+ of photo bandwidth
  10. Kafka: Event stream for fan-out, analytics, notifications

6. Deep Dives (15 min)

Deep Dive 1: Feed Generation (Fan-out Strategy)

The problem: When user A posts a photo, all of A’s followers need to see it in their feed. If A has 10M followers, that’s 10M Redis writes for a single post.

Hybrid approach (push + pull):

  • Regular users (< 10K followers): Fan-out-on-write (push model)

    • When they post, immediately push photo_id to all followers’ Redis feeds
    • ZADD feed:{follower_id} {timestamp} {photo_id} — for each follower
    • This is fast because < 10K writes per post
    • Followers get near-instant feed updates
  • Celebrity users (> 10K followers): Fan-out-on-read (pull model)

    • When they post, DON’T update followers’ feeds
    • Instead, maintain a celebrity_posts:{user_id} sorted set in Redis
    • When a follower loads their feed, merge their pre-computed feed with a real-time fetch of posts from celebrities they follow
    • Feed Service does: ZUNIONSTORE of feed:{user_id} + celebrity_posts for each followed celebrity

Feed ranking: The basic feed is reverse-chronological. For an Instagram-like ranked feed, add a lightweight ML scoring layer:

  • Features: recency, user-author engagement history, photo popularity
  • Score each candidate post, sort by score, return top N
  • This runs in < 50ms using pre-computed features stored in Redis

Deep Dive 2: Image Processing Pipeline

Upload flow in detail:

  1. Client requests pre-signed S3 URL from Upload Service
  2. Client uploads original photo directly to S3 (bypasses our servers entirely)
  3. S3 event notification triggers Image Processing pipeline
  4. Pipeline generates 4 sizes:
    • Thumbnail: 150×150, 50KB (profile grid, notifications)
    • Small: 320×320, 200KB (small screens)
    • Medium: 640×640, 500KB (feed view)
    • Large: 1080×1080, 2MB (full-screen view)
  5. All sizes uploaded to S3 with deterministic keys: {size}/{photo_id}.jpg
  6. CDN pre-warmed for medium size (most requested)

Processing at scale:

  • 500 uploads/sec → 500 image processing jobs/sec
  • Each job: ~2 seconds of CPU (resize 4 times)
  • Need: ~1000 processing cores continuously
  • Solution: Auto-scaling worker fleet or AWS Lambda (scales to thousands of concurrent executions)

Failure handling: If image processing fails (corrupt image, OOM):

  • Retry 3 times with exponential backoff
  • Dead letter queue for persistent failures
  • Photo shows as “processing” in UI until complete
  • Alert on DLQ depth > threshold

Deep Dive 3: CDN Strategy for Photo Delivery

Photos account for 99%+ of bandwidth. Without CDN, we’d need 250GB/sec of origin bandwidth.

CDN architecture:

  • Multi-CDN: Use CloudFront + Cloudflare for redundancy and cost optimization
  • Cache tiers: Edge → Regional → Origin Shield → S3
  • Cache hit ratio target: > 98% (most photos are viewed within first 24 hours of posting)

Cache key design: /{size}/{photo_id}.jpg

  • Deterministic keys → no cache invalidation needed (photos are immutable once processed)
  • Different sizes cached independently (thumbnail has much higher hit ratio than large)

Cost optimization:

  • Photos older than 30 days: move to S3 Infrequent Access tier (50% cheaper)
  • Photos older than 1 year: move to S3 Glacier (90% cheaper)
  • Access pattern: 80% of CDN traffic is for photos < 7 days old

7. Extensions (2 min)

  • Stories: Ephemeral content (24-hour TTL). Separate storage and feed pipeline. Higher write volume but short retention reduces storage costs.
  • Reels/Video: Fundamentally different pipeline — video transcoding (HLS/DASH), adaptive bitrate streaming, higher storage and bandwidth costs.
  • Explore/Discovery: Content recommendation engine using collaborative filtering + content-based features. Separate from the follow-based feed.
  • Direct messages: Real-time messaging (WebSocket-based). Separate service entirely. See the Messenger/WhatsApp design.
  • Push notifications: Fan-out service publishes to notification queue when a user posts. APNs/FCM for delivery.
  • Content moderation: ML pipeline to detect nudity, violence, spam. Runs async post-upload, can remove content retroactively.