How to Design an Image Upload Service
Learn how to design a scalable image upload service using pre-signed URLs, async processing, and CDN delivery.
Expected Interview Answer
An image upload service accepts files through a pre-signed direct-to-object-storage upload flow, validates and processes them asynchronously through a pipeline of virus scanning, resizing and format conversion, and serves the results through a CDN, so the API tier never has to buffer large binary payloads itself.
The client first requests a pre-signed URL from the upload service, then uploads the raw file directly to object storage (like S3), which bypasses the application servers for the heavy bytes and scales trivially. Once storage confirms the upload via an event notification, a processing pipeline picks it up: validating file type and size, scanning for malware, extracting EXIF metadata, and generating multiple resized derivatives (thumbnail, medium, full) plus format conversion (e.g. WebP/AVIF) for efficient delivery. Metadata about the image (owner, dimensions, storage keys, processing status) is written to a database so clients can poll or receive a webhook when processing completes. Finished images are served through a CDN with long cache lifetimes and content-addressed URLs, and the original is retained separately from derivatives so reprocessing (e.g., a new thumbnail size) never requires a re-upload.
- Pre-signed direct uploads keep large binary traffic off the application tier
- Asynchronous processing pipeline keeps the upload API responsive regardless of image size
- Multiple derivatives and modern formats reduce bandwidth and improve load time
- CDN delivery with content-addressed URLs maximizes cache hit rate and offloads origin traffic
AI Mentor Explanation
An image upload service is like a stadium’s equipment drop-off where players hand gear directly to the storage room rather than routing it through the team manager’s office first, keeping the manager free for other tasks. Once gear is dropped off, staff process it in the background — cleaning, tagging, sizing for different kit bags — without the player waiting around. A tag on each item lets anyone check processing status later, and the original gear is kept separately from the prepared match-day kit so re-tagging never means re-collecting from the player. That direct hand-off plus background processing pipeline is exactly how an image upload service is architected.
Step-by-Step Explanation
Step 1
Request a pre-signed upload URL
The client asks the upload service for a short-lived, scoped URL that authorizes a direct write to object storage.
Step 2
Upload directly to object storage
The client uploads bytes straight to storage, bypassing the application tier entirely for the large payload.
Step 3
Trigger async processing
A storage event notification kicks off a pipeline that validates, virus-scans, extracts metadata, and generates resized/format derivatives.
Step 4
Serve via CDN with tracked status
Finished derivatives are cached behind a CDN with content-addressed URLs, while a metadata record tracks processing status for the client.
What Interviewer Expects
- Describes the pre-signed URL / direct-to-storage pattern to avoid proxying large files through app servers
- Explains asynchronous processing (validation, virus scan, resizing, format conversion) via events/queues
- Discusses CDN delivery and caching strategy for derivatives
- Mentions keeping the original separate from derivatives to support reprocessing without re-upload
Common Mistakes
- Routing raw image bytes synchronously through the application server instead of direct-to-storage upload
- Doing resizing/format conversion synchronously in the upload request, blocking the client
- Forgetting virus/malware scanning and file-type validation before serving user-uploaded content
- Not planning for CDN cache invalidation or content-addressed URLs, causing stale derivatives
Best Answer (HR Friendly)
“An image upload service lets the user’s device send the actual photo straight to storage instead of through the main servers, which keeps things fast and scalable. In the background, the system checks the file is safe, creates different sizes and formats, and once that is done the image is served quickly to everyone through a content delivery network.”
Code Example
async function requestUploadUrl(userId, contentType) {
const key = `uploads/${userId}/${crypto.randomUUID()}`
const url = await storage.getPresignedPutUrl({
bucket: "user-images",
key,
contentType,
expiresInSeconds: 300,
})
await db.images.insert({ key, userId, status: "PENDING" })
return { uploadUrl: url, key }
}
// Triggered by an S3-style storage event once the client's PUT completes
async function onObjectCreated(event) {
const { key } = event
await queue.publish("image-processing", { key })
}
async function processImage(key) {
const raw = await storage.get(key)
await scanForMalware(raw)
const derivatives = await generateDerivatives(raw, [
{ name: "thumb", width: 200 },
{ name: "medium", width: 800 },
])
await storage.putMany(derivatives)
await db.images.update({ key }, { status: "READY" })
}Follow-up Questions
- How would you prevent abuse of pre-signed upload URLs, such as uploading disallowed file types?
- How do you handle a client uploading a huge file that never finishes or times out?
- How would you invalidate CDN-cached derivatives if the original image is deleted?
- How would you support resumable uploads for large files on unreliable connections?
MCQ Practice
1. Why do image upload services typically use pre-signed URLs for direct-to-storage uploads?
Pre-signed URLs let clients write directly to storage, keeping large binary payloads off the app tier and improving scalability.
2. Why is image processing (resizing, format conversion) typically done asynchronously after upload?
Processing derivatives synchronously would add latency to every upload and does not scale; an async pipeline keeps the API responsive.
3. Why keep the original uploaded image separate from generated derivatives?
Preserving the original lets the pipeline regenerate any derivative later without needing the client to upload again.
Flash Cards
Why use pre-signed URLs for image uploads? — They let clients upload directly to object storage, bypassing the application servers for large payloads.
What triggers image processing? — A storage event notification once the direct upload to object storage completes.
Why keep the original separate from derivatives? — So new derivatives can be regenerated later without requiring a re-upload.
How are finished images served efficiently? — Through a CDN with long cache lifetimes and content-addressed URLs.