Design problems
Media & streamingMediumSDE-1 25 min

Design an image upload and processing pipeline

Asked at
FlipkartMeeshoMetaMyntraNykaaShareChat

The interviewer asks

“Design the image pipeline for a social-commerce app like Meesho or Instagram: users and sellers upload photos from phones, the system validates, moderates and resizes them, and the images are shown quickly in feeds and product pages on slow networks.”

What a strong answer looks like

A strong answer keeps big bytes away from application servers: the client uploads directly to object storage with a pre-signed URL, and an object-created event starts an asynchronous pipeline. Workers validate the file (real type, dimensions, decompression bombs), strip EXIF location data, fix orientation, compute a perceptual hash for duplicate detection, run a moderation model, and produce a few standard variants in modern formats (WebP/AVIF with JPEG fallback). Metadata and status live in a database; the client learns the result by polling or push. Delivery goes through a CDN, with either pre-generated variants or an on-the-fly resize service behind the CDN for less common sizes. The deep dives are direct upload security, the idempotent processing pipeline with retries and a dead-letter queue, pre-generate vs on-demand resizing, and moderation without blocking good uploads.

Concepts to mention

Pre-signed URLs with size and type limitsMultipart, resumable uploadsObject-created event triggers async processingValidate magic bytes and guard against decompression bombsStrip EXIF GPS dataIdempotent steps with deterministic output keysRetries, visibility timeout and dead-letter queuePre-generated variants vs on-demand resizingWebP/AVIF format negotiationImmutable keys with long CDN cache TTLModeration thresholds with human reviewPerceptual hashing for near-duplicate detection

1 Requirements

Functional

  • Upload one or many images (up to 20 MB each) from mobile and web, resumable on bad networks
  • Validate type and size; reject corrupt, malicious or oversized files
  • Strip EXIF metadata (GPS location) and fix orientation
  • Generate variants: thumbnail, feed, full-screen, zoom, in WebP/AVIF plus JPEG fallback
  • Moderate for nudity, violence and prohibited products; send unclear cases to human review
  • Detect duplicate or near-duplicate images (for example the same product photo reused by many resellers)
  • Serve images fast with the right size and format for each device; let owners delete images

Non-functional

  • ~20M uploads/day (peak ~1,000/s during sale events), ~2B image views/day
  • Upload acknowledged quickly; processed variants ready < 10 s p95 after upload
  • Image load from CDN < 200 ms p95 in India; CDN hit ratio > 95%
  • No uploaded original is lost (object storage durability); processing is retried until success or sent to a dead-letter queue
  • Never serve an image publicly before moderation passes (for public surfaces)
  • Deleted images stop being served within minutes (CDN purge)

Out of scope (say it out loud)

  • Video uploads and transcoding
  • Photo editing filters in the app
  • Visual search (search by image) beyond mentioning embeddings
  • Detailed moderation model training

2 Estimates

Upload rate~230/s avg, ~1,000/s peak20M ÷ 86,400 ≈ 230/s; ×4 for evening and sale peaks
Original storage~40-60 TB/day20M × ~2-3 MB (phone JPEG/HEIC after client-side downscale to max 4096 px)
Variant storage~10 TB/day~4 sizes × 2 formats × ~60 KB average ≈ 0.5 MB per image × 20M
Processing CPU~500-1,000 cores at peakDecode + 8 encodes with libvips ≈ 0.3-0.8 CPU-seconds per image; AVIF encoding is the most expensive. 1,000/s × ~0.7 s
Image views~25k/s avg, ~100k/s peak2B/day ÷ 86,400 ≈ 23k/s
CDN egress~2-3 GB/s avg25k/s × ~80-100 KB per image
Origin load~1-5k/s95-98% CDN hit ratio leaves 2-5% of 100k/s peak to the origin or resize service

3 API

EndpointWhat it does
POST/v1/images/uploadsRequest upload slots: {count, content_types, sizes} → [{image_id, presigned_url or multipart upload id, expires_at}]
PUT<presigned S3 URL>Client uploads bytes directly to object storage (multipart for > 5 MB)
POST/v1/images/{id}/completeOptional client confirmation; the pipeline also starts from the storage event
GET/v1/images/{id}Status (uploaded/processing/ready/rejected) and variant URLs
GEThttps://img.cdn…/{id}/{variant}.{webp|avif|jpg}CDN delivery of a pre-generated variant
GEThttps://img.cdn…/{id}?w=360&q=75&fmt=auto&sig=…Signed on-demand resize for less common sizes
DELETE/v1/images/{id}Delete image: mark deleted, purge CDN, remove objects asynchronously

4 Data model

imagesPostgres (or DynamoDB)
image_id (PK, ULID)owner_idstatusoriginal_keywidthheightmimebytessha256phash (64-bit)moderation_statusvariants (JSON)created_atdeleted_at

Index on owner_id; index on sha256 for exact dedup. Status drives what the app shows.

originalsS3 private bucket
key = originals/{image_id}

Never publicly served. Lifecycle rule moves to infrequent-access after 30 days; incomplete multipart uploads aborted after 1 day.

variantsS3 public-read via CDN only
key = v/{image_id}/{variant}.{fmt}

Immutable keys; long cache headers (1 year). A changed image gets a new id or version suffix.

processing jobsSQS / Kafka + DLQ
image_idattemptstageerror

One message per uploaded object; failed messages after N attempts go to a dead-letter queue for inspection and replay.

phash indexRedis / vector index (BK-tree or LSH buckets)
phash bands → image_ids

Near-duplicate lookup by Hamming distance; used for catalogue dedup and to block re-uploads of banned images.

moderation_reviewsPostgres
image_idmodel_scoresdecisionreviewer_iddecided_at

Human review queue for borderline scores and appeals.

5 Architecture

CLIENTSEDGESERVICESASYNCDATArequest uploadrow: pendingPUT presigned URLObjectCreatedread originalscorededup checkwrite variantsstatus: readystatus, URLsGET imagemissodd sizesMobile / web clientAPI gatewayImage CDNUpload serviceImage metadata serviceOn-demand resizerQueue + DLQProcessing workersModeration modelPostgres (image rows)S3 originals(private)Redis (phash index)S3 variants
ClientEdge / gatewayService / workerQueue / streamCacheDatabase / storageAI / modelThird partyrequestasync (event / queue)

Hover a box to highlight its connections.

6 Request flows

Seller uploads 5 product photos

  1. 1The app downsizes photos on the device to at most ~2048-4096 px and converts HEIC to JPEG if needed, cutting upload size on mobile data.
  2. 2It calls POST /images/uploads for 5 slots; the upload service checks the user's quota and rate limit, creates 5 rows with status=pending, and returns pre-signed PUT URLs valid for 15 minutes, restricted to the expected content type and max size.
  3. 3The app uploads each file directly to S3 (multipart for larger files, so a dropped connection resumes per part).
  4. 4S3 emits ObjectCreated events into the queue; one message per image.
  5. 5A worker sets status=processing, verifies magic bytes and dimensions, decodes safely with pixel limits, strips EXIF, applies orientation, and computes sha256 and a perceptual hash.
  6. 6It checks the phash index: an exact or near duplicate of a banned image is rejected; a duplicate of an existing catalogue image is linked instead of stored again.
  7. 7It runs the moderation model; scores above the block threshold reject the image, borderline scores go to human review, and the rest pass.
  8. 8It encodes variants (150, 360, 720, 1440 px wide) in WebP and AVIF plus a JPEG fallback, writes them under immutable keys, and updates the row to ready with the variant URLs.
  9. 9The app, polling /images/{id} or receiving a push, shows the processed images.

A buyer scrolls the feed

  1. 1The feed API returns image ids and a URL template; the app picks the variant for its screen width and density (e.g. 360 px on a budget phone).
  2. 2The app requests the image with Accept: image/avif,image/webp; the CDN serves the best format it has cached for that Accept header (Vary on format).
  3. 3On a miss the CDN fetches the pre-generated variant from S3, or calls the on-demand resizer for an uncommon size, which reads the original, resizes, and returns with long cache headers.
  4. 4Placeholders (a tiny blurred preview or dominant colour stored in metadata) are shown while the real image loads on slow networks.

7 Deep dives

Direct-to-storage uploads, done safely

Proxying 3 MB uploads through API servers wastes their memory, bandwidth and threads, and a slow 2G client can hold a connection for a minute. Pre-signed URLs let the client upload straight to object storage while the API stays small. The URL must be tightly scoped: one object key chosen by the server (never by the client), a short expiry (5-15 minutes), and a content-length range and content type in the signature (S3 POST policies support size limits). Use multipart upload for files over ~5-10 MB so a failed part is retried alone. The server still treats the object as untrusted: the content-type header can lie, so workers check magic bytes and decode with strict limits on pixel count to stop decompression bombs (a tiny PNG that expands to 50,000 × 50,000 pixels and exhausts memory). Rate-limit upload slots per user and per IP so a bot cannot fill your bucket. Incomplete multipart uploads and pending rows that never complete are cleaned up by lifecycle rules and a sweeper job.

An idempotent, retryable processing pipeline

Treat the pipeline as at-least-once: storage events can be delivered twice, workers crash mid-job, and the moderation API times out. Make every step idempotent. Output keys are deterministic (v/{image_id}/360.webp), so a retry simply overwrites with the same bytes. Status changes use conditional updates (set ready only if status is processing and version matches), so a slow duplicate worker cannot overwrite a newer result such as rejected. Use a visibility timeout longer than the worst-case job time, and extend it for large images. Split slow and fast work if needed: a quick stage that validates, strips EXIF and creates a thumbnail (so the user sees something within 2-3 seconds), and a heavier stage that produces AVIF and zoom variants. After N failed attempts, move the message to a dead-letter queue with the error, alert on DLQ size, and provide a replay tool. Workers autoscale on queue depth; during a sale, a backlog grows harmlessly instead of failing uploads.

Pre-generate variants vs resize on demand

Pre-generating a fixed set of variants makes delivery trivial (static files, perfect caching) but wastes storage on variants nobody requests and requires a backfill job whenever designers add a new size. On-demand resizing (an imgproxy or Thumbor-style service behind the CDN) creates any size from the original when first requested and lets the CDN cache it, so new sizes need no backfill; the cost is CPU on cache misses and a risk of abuse, since attackers can request endless size combinations to burn CPU and pollute the cache. Protect it by signing URLs (the app gets a template with an HMAC) or by allowing only a fixed list of widths. A common hybrid: pre-generate the 2-3 variants that serve 90% of traffic (feed and thumbnail) at upload time, and use on-demand resizing for the rest. Put an origin shield in front of the resizer so each new variant is generated once, not once per CDN edge. Format negotiation (AVIF where supported, else WebP, else JPEG) usually saves 30-50% bytes versus JPEG, which matters on Indian mobile networks.

Moderation and duplicates without blocking everyone

Moderation must sit before public visibility but should not make every upload slow. Run a fast classifier (NSFW, violence, weapons, prohibited items) inside the pipeline; it typically takes tens of milliseconds on GPU or a bit more on CPU. Use two thresholds: above the high one, reject automatically; between thresholds, publish to the owner only and queue for human review; below, publish. Track the review queue size and the share of uploads it receives, because a model update that doubles borderline cases will swamp reviewers. Perceptual hashes (pHash, dHash) map visually similar images to hashes with small Hamming distance, unlike SHA-256 which changes with one pixel. Use them to block re-uploads of images already removed, and in commerce to cluster the same product photo uploaded by hundreds of resellers, which helps catalogue quality and saves storage. For scale, index hashes with LSH bands or a BK-tree, or use image embeddings in a vector index when you need semantic similarity.

8 Trade-offs

Presigned direct upload vs proxy through API

Direct upload scales without app servers handling bytes and is cheaper. Proxying gives simpler validation before storage, but ties up servers on slow clients. Direct upload plus asynchronous validation is the standard choice.

Synchronous vs asynchronous processing

Processing in the upload request gives an instant final result but makes uploads slow and fragile under load. Asynchronous processing with a fast first stage gives a quick thumbnail and absorbs spikes in the queue.

Keeping originals

Keeping originals costs storage but lets you regenerate variants for new sizes or formats (for example when adopting AVIF). Move them to cheaper storage classes instead of deleting them.

Client-side downscaling

Resizing on the phone before upload cuts bytes and upload time a lot on mobile networks, at the cost of not having the full-resolution original. For commerce and social apps that is almost always fine.

9 Bottlenecks

Sale-day upload spike

Sellers upload catalogues before a big sale. The queue absorbs the spike; scale workers on queue depth and keep AVIF generation in a lower-priority stage so thumbnails stay fast.

CDN cache misses on a new viral image

Thousands of edges miss at once. An origin shield with request collapsing turns them into one fetch from S3 or the resizer.

Moderation GPU capacity

Batch requests to the model, autoscale GPU workers on queue depth, and fall back to 'visible only to owner until reviewed' rather than failing the upload.

10 Failure handling

Worker crashes mid-processing

The message reappears after the visibility timeout and another worker redoes the job; deterministic output keys make the retry safe.

Moderation service down

Images stay in 'pending review' and are visible only to the owner; the pipeline retries with backoff. Do not fail open for public surfaces.

Client disconnects during upload

Multipart upload resumes the missing parts with a refreshed URL; abandoned uploads are cleaned up by lifecycle rules and a sweeper that expires pending rows after 24 hours.

A poison image keeps crashing workers

After N attempts it goes to the DLQ instead of looping forever; strict decode limits and running decoders in a sandbox stop one file from taking down the fleet.

11 What impresses

  • Keeps image bytes off application servers using scoped pre-signed URLs
  • Mentions decompression bombs and EXIF location stripping as security and privacy concerns
  • Designs every pipeline step to be idempotent and explains the DLQ
  • Compares pre-generation with on-demand resizing and protects the resizer from abuse with signed URLs
  • Uses perceptual hashes for duplicates and banned re-uploads
  • Thinks about Indian mobile networks: client downscaling, small variants, AVIF/WebP, placeholders

12 Mistakes

  • Uploading through the API servers and processing synchronously in the request
  • Trusting the client's content-type or file extension
  • Serving originals directly to every device
  • Mutable image URLs that require CDN purges for every edit
  • No retry or dead-letter strategy for failed processing
  • Publishing images before moderation

13 Follow-ups

Q1How would you add search by image (find similar products)?
Q2How do you handle GDPR/DPDP deletion requests across originals, variants and CDN caches?
Q3How would you support video uploads with the same pipeline?
Q4How would you migrate all existing images to AVIF without downtime?
Q5How do you prevent hot-linking of your images from other sites?
Q6What if moderation must support regional context, like content allowed in one state but not another?

Saved on this device. Create a free account to keep it on every device.

Practise this with Sam

A real mock interview, out loud, with an honest scorecard. Free to start.

Start free