Design an image and GIF hosting service without sign-up
Anonymous uploads served worldwide, with abuse handled and bandwidth-limited clients in mind.
- Object and blob storage — Immutable files by name, replicated and cheap.
- CDNs and edge caching — Copies of static bytes near the user, and how they stay fresh.
- Video transcoding — Turning one upload into many renditions, in parallel, by segment.
- Rate limiting — Token buckets, sliding windows, and enforcing them across many servers.
- Idempotency and exactly-once — Making a repeated request harmless, which is how 'exactly once' is really built.
01The design, in the six-step framework
1. Requirements
Functional
- Anyone can upload an image, GIF or short video without an account and get a link that works immediately.
- Viewers see the media fast anywhere in the world, including on slow mobile connections (the server picks a size that fits).
- The uploader gets a secret delete link; abusive content can be taken down everywhere within minutes.
- Out of scope: accounts, albums, comments, a feed.
Non-functional
- 10 million uploads a day, average 2 MB; 1 billion views a day, read to write ratio 100:1.
- Time to first byte under 100 ms worldwide for a cached image; an upload is viewable within 2 s.
- Available in several regions; the loss of one region must not lose media or links.
- Abuse resistance: rate limits per IP, malware and illegal content scanning, no open redirect or script execution through uploads.
107 uploads/day × 2 MB = 20 TB/day in, about 7 PB/year; 107 / 86,400 ≈ 115 uploads/s average, about 500/s at peak.
109 views/day ≈ 11,600/s average, 50,000/s at peak; at 300 KB per served rendition that is 15 GB/s at peak, almost all of it from CDN edges.
Metadata: 107 × 365 × 3 years ≈ 1010 items at 300 bytes = 3 TB. One sharded key-value table.
2. Core entities
- Media item: public id (short, unguessable), delete token hash, content hash, type, width, height, byte size, status (processing, live, removed), created-at, uploader IP hash for abuse handling.
- Rendition: media id, variant (original, 1080w, 480w, thumbnail, mp4 of a GIF), blob id, bytes.
- Abuse record: media id, reason, action, timestamp.
3. API
POST /upload→ multipart body; returns public id, view URL and delete URL. The request carries a client-generated idempotency key so a retried upload does not create two items.GET /i/{id}→ HTML page or, with an image Accept header, the best rendition;GET /i/{id}.jpg?w=480→ a specific width.DELETE /i/{id}?token=→ the uploader's delete link.POST /admin/takedown→ mark removed and purge from every edge.
4. High-level design
Upload to first view:
- The load balancer applies a per-IP rate limit (say 30 uploads an hour) and a size cap, then hands the body to the upload service.
- The upload service sniffs the real file type (never trusts the extension), computes the content hash, stores the original in object storage, writes the metadata row with status "processing" and a random 8-character public id, enqueues a job, and returns the URLs. This takes well under a second.
- Workers produce renditions (a few widths, WebP or AVIF, and an MP4 for GIFs, which is ten times smaller), run malware and policy scanning, then flip the status to "live". Until then the serve path returns the original, so the link works within 2 s.
- A view hits the CDN. On a miss the serve service reads metadata, picks a rendition from the requested width and the client's Accept header, and the edge caches the result by URL for a year, since renditions are immutable.
5. Deep dives
What do bandwidth-limited clients change?
Serving a 2 MB original to a phone on a 1 Mbit/s link is a 16 s wait, so the server must choose the bytes. Three mechanisms: a width parameter the client sets from its screen; content negotiation on the Accept header (AVIF or WebP when supported, which is about half the bytes of JPEG at the same quality); and for GIFs an MP4 rendition, because a 10 MB GIF is typically a 500 KB video. The serve URL includes width and format, so each combination is a separate immutable CDN object and a cache hit for everyone with the same device class. Progressive JPEG or a tiny blurred placeholder (a 20-byte thumbnail embedded in the page) makes the page feel fast before the full image lands. The CDN does the heavy lifting: with a 100:1 read ratio and a 95 percent hit rate, origin sees only 2,500 requests a second at peak.
How does it stay up and keep every link when a region fails?
Media and metadata are both written to two regions before the upload is acknowledged, or to one region synchronously with asynchronous replication to a second if a few seconds of exposure is acceptable; say which and why. The public id is random and generated by the upload service, not by a database sequence, so there is no cross-region coordination on the write path. DNS and the CDN steer viewers to the nearest healthy origin, and because renditions are immutable, an edge that already has a file keeps serving it during the outage. The delete token is stored hashed, like a password, so a metadata leak does not let anyone delete other people's uploads.
No accounts: how do we stop abuse without blocking real users?
Without identities, the levers are the IP, the content and the behaviour. Per-IP rate limits catch the simplest floods; a proof-of-work or CAPTCHA step kicks in only above a threshold so normal users never see it. Content scanning (hash matching against known illegal material, a malware scanner, and a policy classifier) runs in the workers after the link is already live, which is a deliberate trade: a 2 s link for everyone versus a 30 s wait for all. A match flips status to "removed", purges the CDN by URL, and the serve path returns 404 within a minute. Uploads are stored under a content hash, so a re-upload of removed content is rejected instantly. Executable content disguised as images is defused by always re-encoding originals before serving and by serving from a separate domain with a strict content type.
The follow-up: we want to run AI models over the images. What changes?
Almost nothing on the write path, which is the point of the queue. Add a model stage to the worker pipeline writing labels into the metadata row, or a separate consumer group on the same queue so the two pipelines scale independently. The honest costs: GPU workers are expensive, so batch them (process 64 images per call) and prioritise live uploads over a backfill of the 1010 existing items; the backfill reads from object storage at a throttled rate. If results feed a feature such as search by tag, add an index as in Google Photos, but note that anonymous uploads have no user to scope the index to, so the index is global and must be sharded by term.
6. What a strong answer sounds like
- Get to the view path quickly: immutable renditions behind a CDN, with the width and format in the URL.
- Make the upload path fast and the processing asynchronous, and defend the "link live before scanning finishes" trade-off.
- Give the numbers: 500 uploads/s, 50,000 views/s at peak, 15 GB/s mostly from edges.
- Treat no-sign-up as the real constraint: hashed delete tokens, random ids, per-IP limits, content hashing against removed material.
- Expect follow-ups on multi-region writes, GIF to MP4, and the AI backfill; one report says the question was judged "not big enough for L5" when the candidate stopped at a single region, so volunteer the multi-region story.
- Common mistake: putting the scan on the synchronous upload path and then failing the 2 s target.
02Where this question was reported
- LeetCode Discuss · Design Image Hosting System; rated Strong Hire (search snippet)
- LeetCode Discuss · Image hosting website; follow-up: how does the design change if the company wants to run AI models on the images (search snippet)
- Medium · Small image/GIF/video hosting platform that does not require sign-up
- frontend-junction.com · Same no-sign-up prompt; interviewer added multi-region availability and bandwidth-limited clients
- dev.to · Images, GIFs and videos without user sign-up; estimates, CDN edge caching, transcoding pipeline
- LeetCode Discuss · Design photo sharing system; feedback: question not big enough to justify L5 (search snippet)
- dev.to · Photo sharing system similar to Pinterest
- Medium · Per search snippet (page 403): L5 question, described as an 80-minute discussion.