Designing a Social Media Platform
Instagram's defining engineering problem isn't storage or media delivery — it's the fanout problem: feed generation has to work completely differently for an account with 50 followers versus one with 50 million. This walkthrough goes from a blank whiteboard to a production-grade distributed system built around that constraint.
Problem Definition
We are designing a social media platform where users post photos and videos, follow each other, and expect a personalized feed of recent content from everyone they follow — fast and reliably.
The surface looks simple: post a photo, see a feed. Underneath is one of the most nuanced system-design problems in software engineering. The challenge isn't storing posts or delivering media — it's how to efficiently determine what each of hundreds of millions of users should see when they open the app, given a constantly changing social graph and a follower distribution that varies from zero to tens of millions.
Photos and videos dominate storage, processing, and network bandwidth, so media ingestion and delivery form a separate scaling problem from feed generation — this design treats both as first-class concerns rather than letting one overshadow the other.
The follower distribution is the whole story
Most users have a handful of followers. Some accounts have tens of millions. This power-law distribution means no single feed-generation strategy works for every account. That asymmetry is the design challenge — and understanding it is what separates a thoughtful answer from a template one.
Visual Design Journey
Watch a single-server photo app evolve, bottleneck by bottleneck, into a hybrid-fanout feed architecture that treats a 200-follower account and a 40-million-follower account completely differently.
Requirements
Functional and non-functional requirements, and the specific design freedoms that unlock large-scale optimizations.
Functional
Create photo/video posts. Delete a post. Follow other users. View a personalized home feed. Like and comment on posts. View media quickly, globally. Support private accounts and conceptual visibility rules for who can see a given post.
Non-functional
High read-to-write ratio
Users read feeds far more often than they create posts. This is the dominant workload and drives the entire architecture.
Low feed latency
Opening the app should feel instant. Latency budgets for feed generation are tight — 100–200ms is a useful illustrative design target for this walkthrough, not a documented production figure.
High availability
Feed browsing and media delivery must survive individual service failures without a full outage.
Scalable media delivery
Photos and videos represent the vast majority of bytes served. They must be served efficiently at global scale.
Eventual consistency is acceptable
A post appearing in a follower's feed with a few seconds' delay is acceptable. Like counts that lag by a few seconds are acceptable. Strong consistency is not required for most social interactions.
Durable, asynchronous media handling
Media uploads must be durable even while processing is still in progress, processing itself should happen asynchronously rather than blocking the upload response, and the system should degrade gracefully — not fail outright — when feed, ranking, or media-processing components are unavailable.
Capacity estimate (illustrative)
These are illustrative planning numbers, not measurements of any real system. Assumptions: 500 million daily active users (DAU); 10% of DAU publish one post per day (50 million posts/day); each DAU opens the feed 8 times per day; fanout-eligible authors average 150 followers (very high-follower accounts are excluded from fanout, as in the hybrid design); an average post stores about 3 MB across the original and processed variants (a mixed photo and short-video assumption); peak traffic is 3× the daily average; 86,400 seconds per day.
| Quantity | Formula | Result |
|---|---|---|
| Posts per second | 50M ÷ 86,400 s (×3 for peak) | about 580/s average, about 1,740/s peak |
| Feed reads per second | 500M × 8 ÷ 86,400 s (×3 for peak) | about 46,300/s average, about 139,000/s peak |
| Read-to-write ratio | 4B feed opens/day ÷ 50M posts/day | about 80 : 1 |
| Feed-store writes (fanout) | 50M posts × 150 followers = 7.5B/day (upper bound: treats every post as fanout-eligible); ÷ 86,400 s (×3 for peak) | about 86,800/s average, about 260,000/s peak |
| One 50M-follower post, naive fanout | 1 post × 50M followers | 50M feed writes — about 576 s of the whole fleet's average fanout load |
| New media storage | 50M posts × 3 MB | about 150 TB/day, roughly 55 PB/year before replication or tiering |
| Feed-store size | Assumed 500 post references per feed × 16 B = 8 KB; × 500M feeds | about 4 TB |
What this implies: the 80:1 read ratio is why feeds are precomputed; fanout writes (tens of thousands per second) dominate post writes (hundreds per second), so the fanout queue and feed store are the write bottleneck; and media bytes dwarf feed metadata (150 TB/day versus a 4 TB feed store in total), which is why media goes direct to object storage and out through the CDN rather than through the application tier.
Initial Architecture
Start simple. One user, one API, one database, one file store. Introduce complexity only when a specific, demonstrated problem demands it.
The API gateway accepts all requests. An application service handles business logic. A relational database stores users, posts, relationships, comments. Media lives entirely separately.
Why media must be separated from metadata
Media files are large, binary, immutable after processing, and highly cacheable — exactly what object storage and CDN are designed for. Metadata is small, mutable, and relational. Storing media in a relational database wastes database resources on binary blobs that do not benefit from SQL at all. The separation is one of the most important early decisions.
This is a starting architecture, not the final one. Later chapters evolve the upload path specifically to avoid unnecessarily proxying large media through general application servers, and evolve the feed path to avoid the read-time cost of naive fanout.
Media Upload Flow
What happens from the moment a user presses 'Share' to the moment their followers can view the photo or video.
A production-grade upload flow looks like: Client → upload authorization → object storage → processing event → media processor → processed variants → CDN. Rather than streaming raw bytes through the application tier, the system authenticates and authorizes the upload, issues a temporary/signed upload target, and lets the client upload directly to object storage. Once the transfer completes, the client or object storage notifies the Post Service, which creates or updates the post's metadata and triggers asynchronous processing.
Why asynchronous processing?
Resizing images and transcoding video takes real time. Blocking the upload response on that work would make the app feel slow. Asynchronous processing decouples upload acknowledgement from processing time: the user gets confirmation immediately, and processing completes in the background.
Lifecycle and variants
A post's media moves through a simple lifecycle: UPLOADING → PROCESSING → READY, with a failure path of PROCESSING → FAILED. Processing produces multiple variants — thumbnails and several sizes for photos; multiple resolutions/bitrates plus a poster/thumbnail for video — the mechanics of which belong to Media Processing Pipeline.
Feed Generation
The central system-design problem: a user opens the app. How does the system determine what posts they should see?
Naive approach: fanout-on-read
The simplest implementation: on every feed request, query the social graph for the user's followed accounts, fetch recent posts from each, merge and rank, return. This works at small scale. As the number of followed accounts grows into the hundreds or thousands, read-time composition must retrieve recent candidates across many followed accounts on every request — causing the work per feed open to grow with follow count, and aggregate feed traffic to grow with total active users.
Fanout-on-Write
Pre-build each user's feed at write time, so reads are trivially fast.
When a post is created, the Post Service puts an event on a queue. Fanout workers consume the event, look up the follower list from the social graph, and write a reference to the post into each follower's pre-built feed store. When a user opens the app, they just read from their feed store — one query, instant response.
Benefit
Reading the precomputed recent feed becomes a bounded lookup independent of the number of accounts the reader follows. Latency is low and predictable.
Cost
Write amplification: one post becomes N feed writes, where N is the poster's follower count. For normal users, this is manageable. For accounts with millions of followers, this explodes.
The Hot User Problem
A celebrity account creates a post. The fanout-on-write strategy produces write operations proportional to that account's follower count — for an account with tens of millions of followers (an illustrative example, not a specific documented figure), this can be enough to overwhelm the worker fleet.
With fanout-on-write, one post from a very high-follower account requires a proportionally huge number of writes to the feed store. At any realistic posting frequency, the aggregate write load from a handful of such accounts can consume worker capacity, increase queue lag, overload feed-store writes, and delay ordinary users' posts from propagating on time.
The queue depth grows to millions. Workers run at 100% load processing a single user's post. Other users' fanout is delayed. The architecture that works beautifully for normal users becomes catastrophic at the extreme end of the follower distribution.
Hybrid Fanout
Neither fanout-on-write nor fanout-on-read is universally optimal. The production answer is a hybrid that uses different strategies for different account types.
Normal users (low follower count): fanout-on-write. Pre-build feeds. Fast reads. The write amplification is small and manageable.
Celebrity accounts (high follower count, above a threshold): skip fanout-on-write entirely. When any follower opens their feed, the Feed Service identifies which followed accounts are 'hot', fetches their recent posts directly from the database, and merges them with the pre-computed portion of the feed.
Feed composition
Pre-computed feed entries (from normal accounts) + live fetch of hot-user recent posts + ranking = final feed.
Hybrid Fanout Strategy covers strategy transitions and adaptive classification beyond a single follower-count threshold in depth.
Feed Storage & Caching
Feed data is stored and cached across distinct layers, each with a different lifecycle and consistency property.
Three layers are worth distinguishing rather than treating 'the feed cache' as one thing. The feed store holds derived/materialized candidate state — a time-ordered list of recent post IDs/references per user, built by fanout workers. The post/object cache caches post metadata during hydration, avoiding a database round trip for posts that were just fetched. The assembled/ranked feed cache caches the final, ordered result of composing and ranking a feed for a short period, so a repeated request within that window is served instantly.
The feed store's window can be bounded — for illustration, keeping something like the last few thousand entries per user — but this is an illustrative bounded-window example, not a claim about any specific platform's real implementation.
Eventual consistency
A new post appearing in a feed after a short delay is not a bug — it is an accepted trade-off in exchange for dramatically reduced read-time work.
Likes & Comments
High-frequency write interactions — likes and comments — must be handled without blocking the main feed experience or creating a database hotspot.
Likes: relationship vs. count
A like has a logical relationship — this user liked this post — which can be modeled as a uniqueness key of (user_id, post_id) so a retried like request does not create multiple likes. This is separate from the aggregate count shown on the post: a viral post should not require every single interaction to synchronously update one relational counter row. Fast, sharded counters absorb the write volume, with asynchronous aggregation reconciling them into a durable total over time.
Comments
Comments are separate from the main feed path. They require their own pagination at scale, and a feed response typically carries only a preview or a count rather than the full comment thread — detailed comments are fetched separately when a reader expands a post.
Post Lifecycle & Feed Correctness
Before assembling the final architecture, it's worth naming a problem that fanout alone doesn't solve: a feed entry can become invalid long after it was written.
A post can be deleted. An account can become private or be suspended. Two users can block each other. A reader can unfollow the author. The underlying media can fail or be removed. In every one of these cases, a feed entry that was already fanned out to a follower's feed store may no longer represent something that follower should actually see.
Post Lifecycle, Privacy & Visibility covers the full serving-path mechanics behind this idea.
Final Architecture
The complete system — every component earns its place by solving a specific, demonstrated problem.
Media write path
Client → upload authorization → Object Storage → processing queue → Media Processor → processed variants → CDN.
Post / feed write path
Post Service → metadata store → fanout queue → workers → Social Graph → Feed Store.
Feed read path
Feed request → Feed Service → Feed Cache / Feed Store + pull sources → filtering/dedup → ranking → hydrate post/media metadata → response.
Interaction path
Likes and comments follow their own separate service and storage paths rather than being routed through the feed service — they scale differently and should not add latency to feed reads.
- 01API Gateway routes all client traffic to appropriate services
- 02Upload authorization + direct-to-object-storage transfer keep large media off the application tier
- 03Media Processor turns raw uploads into READY variants asynchronously
- 04Object Storage + CDN serve media globally with minimal origin load
- 05Post Service writes metadata to the primary database as the durable source of truth
- 06Social Graph Service serves both follower and following lookups for fanout and feed composition
- 07Fanout Queue + Workers distribute posts to normal users' feed stores, with batching, checkpointing, and idempotent writes
- 08Hybrid fanout excludes high-fanout accounts from push and fetches their posts at read time instead
- 09Feed Service composes precomputed + pulled candidates, filters, dedups, and ranks
- 10Feed Cache and Feed Store hold derived/rebuildable state, distinct from durable sources of truth
- 11Post Lifecycle handling filters deleted/blocked/private content at serving time, with async cleanup after
- 12Like Service + Comment Service handle interactions with sharded counters, without blocking the feed path
Social Graph
The social graph — who follows whom — is the foundation of feed generation. How it is stored and accessed shapes everything downstream.
Follow relationships form a directed graph: User A follows User B, and B may or may not follow back. This graph is read in two distinct directions, and both must be fast.
Following lookup
user → accounts user follows. Used for feed composition, pull-source lookups when reading a feed, and relationship signals for ranking.Follower lookup
author → users following author. Used for fanout-on-write — the write side of feed generation.High-degree nodes
Follower counts follow a power law. Most users have a few hundred followers. Some accounts have tens of millions. This asymmetry is the source of the hot-user problem — the central architectural challenge in this story.