System Design #19: Design Snapchat

SYSTEM DESIGN #19 · INTERVIEW GUIDE

Design Snapchat

Snapchat built an empire on a single promise: what you share disappears. Delivering on that promise at 5 billion snaps per day requires more than just deleting a database row — it means encrypting content client-side before it ever leaves the device, destroying the decryption key the moment the recipient opens the snap, and auditing every storage system to prove that nothing persists after deletion. This system covers the full ephemeral media pipeline: client-side AES-256 encryption, S3 ciphertext storage with strict key ACLs, delivery via push notification, view-triggered key destruction via a Kafka pipeline, and Snap Map with Redis Geo for real-time location sharing. You will learn how to build a system where ‘deleted’ actually means deleted — a harder engineering problem than it sounds.

AES-256-GCMX25519 ECDHS3KafkaH3 Hexagons

💡

The Gist — What Problem Are We Solving?

Photos and videos that disappear — and a map of where your friends are right now

Snapchat’s core idea is ephemerality: content disappears after being viewed, like a conversation rather than an archive. This requires reliable deletion (not just hiding) — a hard distributed systems problem. Add to this: Stories (24-hour broadcasts), Snap Map (real-time friend location sharing), and Streaks (a daily-interaction gamification mechanic that millions of teenagers protect obsessively). Each feature has its own unique engineering challenge.

💬

Think of it as a messaging app where the messages self-destruct, everyone can see where their friends are on a map, and keeping a daily conversation alive earns you a badge.

✅

Functional Requirements

These are the capabilities the system must deliver — what users and operators can actually do with it.

📸
Snap Send/Receive

📸Send ephemeral photo/video; recipient views once then deleted

📖
Stories

📖Post to story viewable by friends for 24 hours; auto-delete after

🗺️
Snap Map

🗺️Opt-in real-time friend location sharing; Ghost Mode to go invisible

🔥
Streaks

🔥Daily mutual-snap counter; warning at 20h; reset at 24h

👁️
View Receipts

👁️Sender notified when snap opened; screenshot detection

⚡

Non-Functional Requirements

These define how well the system must perform — the quality attributes that separate a toy from a production system.

🗑️ Deletion

🗑️Snaps deleted from server after recipient views; Stories after 24h

🔐 Privacy

🔐E2E encrypted; server stores ciphertext only

🗺️ Location Freshness

🗺️Snap Map updates within 3 minutes of location change

🔥 Streak Durability

🔥Streaks must survive Redis failure — replicated to Postgres hourly

📱 Offline Support

📱Snaps queued for delivery; delivered on reconnect

📊

Key Metrics — The Numbers That Define This System

The headline numbers to know cold — and be ready to explain how each one is achieved.

300M+
DAU
24hr
Story TTL
3 min
Snap Map update interval
20hr
streak warning
E2E
all snaps encrypted
🏗️

System Architecture Diagram

Full data flow from source to serving. Each layer scales independently.

Ingestion
Snapchat Architecture
Snap Upload

→
S3
encrypted

→
Delivery Service

→
Push to recipient; View event

↓
Processing
Kafka

→
Deletion Pipeline
soft-delete

→
hard-delete after 24h); Stories

→
Redis sorted set
score=expiry); Snap Map

↓
Storage
Redis Geo
GEOADD every 3min); Streaks

→
Redis counter + Kafka delayed events

🗺️

End-to-End User Journey

Trace a single request end-to-end — the story interviewers want you to tell fluently.

1
Alice sends snap to Bob

— Client encrypts snap with Bob’s public key; uploads ciphertext to S3; server stores snap_id → S3_key mapping

2
Bob receives notification

— Push notification (FCM/APNs): ‘Alice sent you a snap’ — no content in payload

3
Bob opens snap

— Server sends decryption key for this snap_id; client decrypts and displays; view receipt sent to Alice

4
Snap deleted

— View event → Kafka → deletion worker: soft-delete DB record; S3 lifecycle rule marks object for deletion; hard-delete confirmed after 24h

5
Screenshot detected

— iOS/Android screen capture API fires callback; screenshot event sent to server; Alice receives ‘Bob took a screenshot’ notification

🔭

High-Level Design — Component Breakdown

Core components — each with a single, well-defined responsibility. The key architectural insight: each layer scales independently, and failure in one component is isolated from the rest.

1
Upload API
→
2
S3
→
3
Delivery
→
4
View Handler
→
5
Delete Pipeline
→
6
Snap Map
1 — Upload API

Handles responsibilities for the Upload API layer. Designed for independent horizontal scaling — additional instances added without architectural changes. Communicates asynchronously with adjacent components to maximise throughput and fault isolation.

2 — S3

Object storage for raw events (Iceberg/Parquet), media assets, ML model artefacts, and snapshots. Lifecycle rules tier data to Glacier after 90 days. Versioning disabled on ephemeral buckets (snaps, stories) to ensure hard deletes.

3 — Delivery

Handles responsibilities for the Delivery layer. Designed for independent horizontal scaling — additional instances added without architectural changes. Communicates asynchronously with adjacent components to maximise throughput and fault isolation.

4 — View Handler

Handles responsibilities for the View Handler layer. Designed for independent horizontal scaling — additional instances added without architectural changes. Communicates asynchronously with adjacent components to maximise throughput and fault isolation.

5 — Delete Pipeline

Handles responsibilities for the Delete Pipeline layer. Designed for independent horizontal scaling — additional instances added without architectural changes. Communicates asynchronously with adjacent components to maximise throughput and fault isolation.

6 — Snap Map

Handles responsibilities for the Snap Map layer. Designed for independent horizontal scaling — additional instances added without architectural changes. Communicates asynchronously with adjacent components to maximise throughput and fault isolation.

🔬

Low-Level Design — Deep Dives

Deep dives worth explaining in detail in any senior engineering interview. For each: know the data structure, the algorithm, the why, and the trade-off you made.

1 — Client-Side Encryption
AES-256-GCM · X25519 ECDH

Snap encryption: (1) Generate ephemeral X25519 key pair (pk_e, sk_e). (2) Fetch recipient’s public key pk_r from key server. (3) shared_secret = X25519(sk_e, pk_r). (4) encryption_key = HKDF(shared_secret, snap_id, ‘AES-256-GCM’, 32 bytes). (5) Encrypt snap bytes with AES-256-GCM → {ciphertext, nonce, auth_tag}. (6) Upload {ciphertext, pk_e} to S3. (7) Destroy sk_e immediately after upload. Server stores only {snap_id, s3_key, pk_e, recipient_id} — no decryption key.

from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey
sk_e = X25519PrivateKey.generate()
pk_e = sk_e.public_key()
shared = sk_e.exchange(recipient_pk)
key = HKDF(algorithm=SHA256(), length=32).derive(shared)
ciphertext, tag = AES_GCM(key, nonce).encrypt(snap_bytes)

2 — S3 Storage Layer
Ciphertext Only · No Raw Media

S3 bucket policy: no public access, no pre-signed URLs without active snap record in DynamoDB. Object key: snaps/{recipient_id}/{snap_id}.enc. Server-side encryption: S3-SSE-KMS (belt-and-suspenders — ciphertext already client-encrypted). Object Lock: DISABLED for snap bucket — must be deletable. Lifecycle rule: objects automatically deleted after 30 days if deletion event not already triggered. Versioning: DISABLED to ensure DELETE is final.

s3.put_object(
Bucket=’snaps’,
Key=f’snaps/{recipient_id}/{snap_id}.enc’,
Body=ciphertext_bytes,
ServerSideEncryption=’aws:kms’,
Metadata={‘pk_ephemeral’: pk_e_b64, ‘snap_id’: snap_id}
)

3 — View-Triggered Deletion Pipeline
Kafka · Lambda · Hard Delete

View event flow: (1) Recipient app opens snap → decrypts client-side → sends view_ack to API. (2) API publishes to Kafka snap_events topic. (3) Lambda consumer: S3 DeleteObject(snap_id) + DynamoDB delete_item(snap_id) + Redis DEL snap:{snap_id}. (4) Verification job runs 1h later: S3 HeadObject → expect 404. If not 404: alert + retry. Audit log: immutable append to S3 Glacier with {snap_id, deleted_at, verified_at}.

def handle_view(snap_id, recipient_id):
# Publish deletion event
kafka.produce(‘snap_events’, {
‘type’: ‘VIEWED’, ‘snap_id’: snap_id,
‘recipient_id’: recipient_id, ‘ts’: time.time()
})
# Lambda picks up and executes hard delete
# Audit confirmation written to S3 Glacier

4 — Snap Map (Redis Geo)
H3 Fuzzing · 8h TTL

Location update: client sends GPS every 5 minutes while Snap Map open. Server fuzzes to H3 resolution-9 cell (~150m diameter): h3.geo_to_h3(lat, lng, 9). GEOADD snap_map lng lat user_id (using H3 cell centroid, not raw GPS). TTL per user: 8 hours. Ghost Mode: GEODREM snap_map user_id. Viewer: GEORADIUS snap_map lng lat 10 km ASC COUNT 500 returns nearby users. Result shows username + H3 cell centroid — never exact GPS. Stale users auto-pruned by sorted set TTL mechanism.

def update_location(user_id, lat, lng):
h3_cell = h3.geo_to_h3(lat, lng, 9)
cell_lat, cell_lng = h3.h3_to_geo(h3_cell) # cell centroid
redis.geoadd(‘snap_map’, cell_lng, cell_lat, user_id)
redis.expire(f’loc:{user_id}’, 28800) # 8h TTL

⚖️

Trade-offs & Decision Log

Every senior interview comes down to these decisions. Know the exact trade-off, the reasoning, and the specific numbers that justify each choice.

⚖️ Client-Side vs Server-Side Encryption

✓
Client-Side Encryption ✅ Chosen
  • Server never sees plaintext — cannot be compelled to produce content
  • Key stays on sender device until recipient opens snap
  • Forward secrecy: each snap has unique ephemeral key pair
  • Harder to implement key exchange protocol correctly
→
Server-Side Encryption (at-rest)
  • Server can decrypt if legally compelled
  • Simpler implementation — standard S3 SSE-KMS
  • Cannot verify deletion — server still has access
  • Violates user expectation of true ephemeral messaging

💡

Decision: Client-side AES-256-GCM with X25519 ECDH key exchange; server stores only ciphertext; key destruction triggered by view event

⚖️ Hard Delete vs Soft Delete for Snap Expiry

✓
Hard Delete (S3 Object Delete) ✅ Chosen
  • Object actually removed from S3 — cannot be recovered
  • Compliance: deletion verifiable by S3 versioning audit log
  • Key also destroyed — ciphertext useless even if backup exists
  • S3 delete is asynchronous — tombstone before actual purge
→
Soft Delete (mark as expired)
  • Faster — just a flag flip in DB
  • S3 object remains — backup/replication may retain data
  • Legal hold requests can recover ‘deleted’ content
  • Violates user expectation and regulatory ephemeral data rules

💡

Decision: Hard delete pipeline: Kafka event → Lambda → S3 Object Delete + DB purge; S3 Object Lock disabled for snap bucket; audit log confirms deletion within 24h

🎯

Interview Questions — Answered
The exact questions interviewers ask — with production-grade answers

Q1
How is guaranteed deletion verified without the ability to audit S3 directly?

Deletion audit pipeline: (1) Kafka deletion event: {snap_id, s3_key, deleted_at, triggered_by}. (2) Lambda function calls S3 DeleteObject and captures the ETag of the deleted object in the audit log. (3) A verification job runs 1 hour later: calls S3 HeadObject on the deleted key — expects 404. If 404 not received, fires a critical alert and retries deletion. (4) Audit log (immutable append-only in S3 + DynamoDB) records: snap_id, deletion_triggered_at, s3_delete_confirmed_at, key_destroyed_at. This audit trail is provided to regulators and users who request deletion confirmation. Average deletion confirmation latency: 45 seconds. Maximum SLA: 24 hours.

Q2
How does the ephemeral key exchange work between sender and recipient?

X25519 ECDH key exchange: (1) Sender generates ephemeral key pair (pk_ephemeral, sk_ephemeral) per snap. (2) Sender fetches recipient’s public key from key server. (3) Shared secret = ECDH(sk_ephemeral, pk_recipient). (4) Encryption key = HKDF(shared_secret, snap_id, ‘AES-256-GCM’). (5) Snap is encrypted with AES-256-GCM. (6) Sender uploads {ciphertext, pk_ephemeral} to S3. Server stores {snap_id, s3_key, pk_ephemeral, recipient_id} — no encryption key. (7) Recipient fetches snap, computes shared_secret = ECDH(sk_recipient, pk_ephemeral), derives key, decrypts. After view: sk_ephemeral is destroyed on sender device; S3 object deleted. Key re-derivation impossible without both private keys.

Q3
How does Snap Map work without revealing precise user location?

Location fuzzing pipeline: (1) Raw GPS coordinates (±10m) reported by client every 5 minutes while Snap Map is open. (2) Server applies spatial fuzzing: coordinates are snapped to the nearest H3 hex cell at resolution 9 (~150m diameter). (3) Snap Map displays user at the centre of the H3 cell — not their exact location. (4) Location is stored in Redis Geo with a TTL of 8 hours (cleared after user stops using Snap Map). (5) Ghost Mode: user’s H3 cell is never written to Redis — they are invisible on the map. (6) Staleness: location shown on map is always the most recent H3 cell from the last 8 hours; stale after 8 hours automatically.

System Design Series · Every Tuesday & Thursday

Level up your system design interviews

Each post covers Gist, Functional & Non-Functional Requirements, Key Metrics, System Diagram, User Journey, HLD, LLD, and Trade-offs & FAQs.

Subscribe to never miss a post →


Categories: System Design

Tags: , , , , ,

Leave a Reply