Upload Large Files UX
Frontend system design for large uploads — multipart, resume, progress, concurrency, failures, and virus-scan states.
- system-design
- interview
- architecture
- upload
Scope the problem
Design UX + client architecture for uploading large files (video, datasets, high-res images): reliability on flaky networks, progress honesty, resume, and server processing states.
Out of scope: virus scanner cluster design; define the API contract only.
Requirements
| Type | Examples |
|---|---|
| Functional | select files, progress, cancel, retry, multi-file |
| Non-functional | survive tab refresh if possible; 1GB+ files |
| Product | completion rate; time-to-available |
High-level flow
Validate → request upload session → multipart PUT parts
→ complete session → processing (async) → ready URL
Direct-to-object-storage with signed URLs keeps app servers off the byte path.
API sketch
// 1) init
POST /uploads
{ filename, size, contentType, checksum? }
→ { uploadId, partSize, parts: [{ partNumber, url }] }
// 2) client PUTs each part to signed url
// 3) complete
POST /uploads/:id/complete
{ parts: [{ partNumber, etag }] }
→ { status: "processing" }
// 4) poll or WS
GET /uploads/:id → { status: "ready", assetId } | { status: "failed", error }
Client architecture
UploadManager
├── queue (concurrency N)
├── per-file state machine
├── part uploader (retry)
└── persistence (IndexedDB) for resume
File state machine
queued → hashing? → uploading → completing → processing → ready
↘ failed
↘ canceled
type FileUpload = {
localId: string;
file: File;
uploadId?: string;
progress: number; // 0..1
status: "queued" | "uploading" | "processing" | "ready" | "failed" | "canceled";
error?: string;
completedParts: number[];
};
Multipart & concurrency
- Part size e.g. 8–16MB (balance request count vs retry cost)
- Upload 3–4 parts in parallel per file; limit global concurrency
- On part failure: exponential backoff; refresh signed URL if expired
async function uploadPart(url: string, blob: Blob, signal: AbortSignal) {
const res = await fetch(url, { method: "PUT", body: blob, signal });
if (!res.ok) throw new Error("part failed");
return res.headers.get("ETag");
}
Progress UX
- Progress = weighted completed part bytes / total
- Smooth UI with rAF; don’t thrash React every byte
- Show speed + ETA estimates carefully (they lie on variable networks)
- Multi-file: overall + per-file
Resume after refresh
- Persist
uploadId, part etags, file fingerprint (name+size+lastModifiedor hash) in IndexedDB - On relaunch, if file still selectable (File System Access API) or user re-picks same file, continue remaining parts
- Without FS API, may require re-select file but can skip completed parts if server supports list parts
Validation
- Max size / MIME allowlist client and server
- Optional client checksum (SHA-256 web worker) for integrity
- Image/video duration probes via browser APIs
Cancellation
AbortController per file; call server abort endpoint to stop processing and delete incomplete uploads (cost control).
Post-upload processing
UI states after bytes done:
| Status | UX |
|---|---|
| processing | spinner “Transcoding…” |
| ready | show asset; enable insert |
| failed | error + retry processing if safe |
Use polling with backoff or websocket notification.
Reliability tactics
- Idempotent complete calls
- Refresh signatures on 403
- Detect offline → pause queue (offline indicator)
- Don’t hold entire file in memory twice; slice
File/Blob
Security
- Signed URLs short-lived
- Auth on init/complete
- Don’t echo internal storage paths
- CSP and malware: show “scanning” if product requires
Performance & mobile
- Respect data saver; warn on large cellular uploads
- Lower concurrency on
navigator.connection.saveData - Background: limited guarantees — use Background Upload APIs where exist; otherwise document “keep tab open”
Tradeoffs
- Single PUT vs multipart — size threshold
- Client hash cost vs integrity
- Strict resume complexity vs retry whole file
- Through-server proxy (simpler auth) vs bandwidth cost
Interview close
Init session → parallel signed part uploads with backoff → complete → async processing poll → progress and cancel → IndexedDB resume. Call out direct-to-storage and mobile data warnings.
Related on this site
- Media CDN and Image Pipeline
- Offline Status Indicator
- Design Video Player Experience
- System Design Interview Framework