System Design: Photo Sharing App
A pragmatic walkthrough for designing a photo sharing service: upload paths, storage tiers, CDN delivery, feed generation, and the trade-offs that matter at scale.
What you'll learn
- ✓How uploads and storage are typically split
- ✓Why CDNs dominate the read path
- ✓Feed generation strategies
- ✓The key trade-offs to discuss in interviews
Prerequisites
- •Basic distributed systems vocabulary
Photo sharing is a classic system design interview prompt because it hits almost every backend concern: large binary blobs, heavy reads, social graphs, and a tight latency budget. This post walks through a clean baseline architecture and the decisions behind each piece.
What and why
A photo sharing app lets users upload images, browse a feed of photos from people they follow, and interact through likes and comments. The hard parts are not the features themselves but the volumes. A modest service ingests millions of photos per day and serves billions of views. Reads dominate by orders of magnitude, and the median image is far larger than the metadata that describes it.
The design goals are predictable upload success, fast feed loads, and low cost per view. Everything else flows from those three.
Mental model
Think of the system as two loosely coupled pipelines that meet at a metadata database. The write pipeline accepts a photo, stores the bytes once, and records a row. The read pipeline takes a user request, fetches the right rows, and points the client at cached image URLs. The metadata is small and consistent. The bytes are large and immutable.
Once you internalize that split, most decisions become clearer. The database does not need to know about pixels. The CDN does not need to know about followers. Each layer optimizes for its own workload.
Architecture
A reasonable baseline keeps the upload path simple and pushes as much read traffic as possible to the edge.
Upload path:
client -> API -> object storage (originals)
-> queue -> worker -> resized variants -> object storage
-> metadata DB (photo row)
Read path:
client -> API -> feed service -> metadata DB
-> CDN -> object storage (on miss) The API layer is stateless and horizontally scaled behind a load balancer. Originals go straight to object storage using pre-signed URLs so the API never touches the bytes. A worker pool consumes an upload event, generates thumbnails and feed-sized variants, and writes them back. Metadata lives in a sharded relational database keyed by user id, with a separate table for the social graph.
For reads, the feed service queries metadata, assembles a list of photo ids, and returns CDN URLs. The CDN handles the vast majority of image traffic; origin storage only sees cache misses.
Trade-offs
The first trade-off is push versus pull for the feed. Pull (compute the feed at read time) is simple and storage cheap but slow for users who follow many people. Push (fan out new photos into per-user feed lists) is fast to read but expensive for celebrities with millions of followers. Most real systems use a hybrid: push for normal users, pull for high-follower accounts, merge at read time.
The second is consistency on the social graph. Strong consistency is easy with a single shard but limits scale. Eventual consistency lets follows propagate over seconds, which is usually fine for social features but surprising for engineers used to ACID guarantees.
The third is variant generation. Generating every size on upload wastes storage for photos no one views. Generating on demand adds latency to the first viewer. A common compromise is to generate the two or three most common sizes eagerly and lazy-create the rest.
Practical tips
Use pre-signed upload URLs so the API tier never proxies image bytes. This alone saves an enormous amount of bandwidth and CPU. Make object keys content-addressed (a hash of the bytes) so duplicate uploads do not waste storage and cache invalidation becomes trivial.
Keep the metadata schema small and indexed for the access patterns you actually have. A photos table indexed by user id and created at handles ninety percent of queries. Resist the urge to put EXIF data in the hot row; push it to a side table.
Treat the CDN as part of the system, not an afterthought. Set long cache TTLs on variant URLs, use immutable URLs by including the hash, and pre-warm caches for trending content. Monitor cache hit rate as a first class metric.
Wrap-up
A photo sharing app at scale is mostly about separating concerns: bytes go to object storage, metadata to a sharded database, and reads to a CDN. The interesting decisions are around feed fan-out, variant generation, and consistency on the social graph. Get those right and the rest is mostly plumbing.
Related articles
- System Design System Design: A File Storage Service Like Dropbox
Design a file storage service with uploads, sync, deduplication, and sharing, scaling to petabytes while keeping reads fast and cheap.
- System Design Designing Rate Limiters: A System Design Deep Dive
A senior-engineer guide to designing rate limiters: algorithms, distributed coordination, trade-offs, and production patterns that actually scale.
- System Design System Design: Building a Scalable Chat Application
Design a real-time chat system that supports millions of users with low latency messaging, presence, and message persistence at scale.
- System Design System Design: Real-Time Leaderboard with Redis
Design a real-time leaderboard using Redis sorted sets, handling millions of score updates per second with low latency rankings.