How does Blob.slice() work for chunked file uploads?
The Short Answer
Blob.slice() lets you cut a large Blob (or File) into smaller pieces without copying the data. This is the foundation of chunked file uploads — instead of sending a 500MB file in one request that might timeout or fail, you split it into manageable chunks (say 5MB each) and upload them individually. If one chunk fails, you only retry that chunk, not the entire file.
How slice() Works
blob.slice(start, end) returns a new Blob representing bytes from start up to (but not including) end. It works like Array.slice() — it creates a lightweight view, not a copy of the data.
// A 100-byte blob
const blob = new Blob(['A'.repeat(100)]);
// Slice into chunks of 25 bytes
const chunk1 = blob.slice(0, 25); // bytes 0-24
const chunk2 = blob.slice(25, 50); // bytes 25-49
const chunk3 = blob.slice(50, 75); // bytes 50-74
const chunk4 = blob.slice(75, 100); // bytes 75-99
// Each chunk is a new Blob
console.log(chunk1.size); // 25
console.log(chunk1 instanceof Blob); // true
// The original blob is unchanged
console.log(blob.size); // 100
// Negative indices work (like Array.slice)
const lastTen = blob.slice(-10); // last 10 bytes
No data copying
Slicing a 500MB file into 100 chunks does NOT create 500MB of copies. Each slice is a lightweight reference to a portion of the original data. This is why chunked uploads don't double your memory usage.
Basic Chunked Upload
Here's the simplest chunked upload implementation — split the file and upload each piece sequentially:
const CHUNK_SIZE = 5 * 1024 * 1024; // 5MB per chunk
async function uploadFile(file: File) {
const totalChunks = Math.ceil(file.size / CHUNK_SIZE);
for (let i = 0; i < totalChunks; i++) {
const start = i * CHUNK_SIZE;
const end = Math.min(start + CHUNK_SIZE, file.size);
const chunk = file.slice(start, end);
const formData = new FormData();
formData.append('chunk', chunk);
formData.append('chunkIndex', String(i));
formData.append('totalChunks', String(totalChunks));
formData.append('fileName', file.name);
await fetch('/api/upload-chunk', {
method: 'POST',
body: formData
});
console.log(`Uploaded chunk ${i + 1}/${totalChunks}`);
}
// Tell the server all chunks are uploaded
await fetch('/api/upload-complete', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ fileName: file.name, totalChunks })
});
}
Chunked Upload with Progress Tracking
type UploadProgress = {
percent: number;
uploadedBytes: number;
totalBytes: number;
currentChunk: number;
totalChunks: number;
};
async function uploadWithProgress(
file: File,
onProgress: (progress: UploadProgress) => void
) {
const CHUNK_SIZE = 5 * 1024 * 1024;
const totalChunks = Math.ceil(file.size / CHUNK_SIZE);
let uploadedBytes = 0;
for (let i = 0; i < totalChunks; i++) {
const start = i * CHUNK_SIZE;
const end = Math.min(start + CHUNK_SIZE, file.size);
const chunk = file.slice(start, end);
const formData = new FormData();
formData.append('chunk', chunk);
formData.append('chunkIndex', String(i));
formData.append('totalChunks', String(totalChunks));
formData.append('fileName', file.name);
await fetch('/api/upload-chunk', { method: 'POST', body: formData });
uploadedBytes += chunk.size;
onProgress({
percent: Math.round((uploadedBytes / file.size) * 100),
uploadedBytes,
totalBytes: file.size,
currentChunk: i + 1,
totalChunks
});
}
}
// Usage
uploadWithProgress(file, (progress) => {
progressBar.style.width = `${progress.percent}%`;
statusText.textContent = `${progress.percent}% (${progress.currentChunk}/${progress.totalChunks} chunks)`;
});
Resumable Upload
The real power of chunked uploads: if the connection drops mid-upload, you can resume from where you left off instead of starting over. The server tracks which chunks it received, and the client skips those.
async function resumableUpload(file: File) {
const CHUNK_SIZE = 5 * 1024 * 1024;
const totalChunks = Math.ceil(file.size / CHUNK_SIZE);
// Ask the server which chunks it already has
const res = await fetch(`/api/upload-status?fileName=${file.name}`);
const { uploadedChunks } = await res.json() as { uploadedChunks: number[] };
// e.g., [0, 1, 2] means first 3 chunks are already uploaded
for (let i = 0; i < totalChunks; i++) {
// Skip chunks the server already has
if (uploadedChunks.includes(i)) {
console.log(`Chunk ${i} already uploaded, skipping`);
continue;
}
const start = i * CHUNK_SIZE;
const end = Math.min(start + CHUNK_SIZE, file.size);
const chunk = file.slice(start, end);
await uploadChunkWithRetry(chunk, i, totalChunks, file.name);
}
await fetch('/api/upload-complete', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ fileName: file.name, totalChunks })
});
}
async function uploadChunkWithRetry(
chunk: Blob,
index: number,
total: number,
fileName: string,
maxRetries = 3
) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const formData = new FormData();
formData.append('chunk', chunk);
formData.append('chunkIndex', String(index));
formData.append('totalChunks', String(total));
formData.append('fileName', fileName);
const res = await fetch('/api/upload-chunk', { method: 'POST', body: formData });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return; // Success
} catch (err) {
if (attempt === maxRetries) throw err;
console.log(`Chunk ${index} failed, retrying (${attempt}/${maxRetries})...`);
await new Promise(r => setTimeout(r, 1000 * attempt)); // Exponential backoff
}
}
}
Why this matters
On mobile networks or flaky connections, uploading a 200MB file without chunking means any hiccup restarts the entire upload. With chunked + resumable uploads, users never lose progress. This is how Google Drive, Dropbox, and YouTube handle large file uploads.
Parallel Chunk Uploads
Sequential uploads are reliable but slow. You can upload multiple chunks in parallel to maximize bandwidth, while limiting concurrency to avoid overwhelming the server:
async function parallelChunkedUpload(file: File, concurrency = 3) {
const CHUNK_SIZE = 5 * 1024 * 1024;
const totalChunks = Math.ceil(file.size / CHUNK_SIZE);
// Create all chunk tasks
const chunks = Array.from({ length: totalChunks }, (_, i) => ({
index: i,
blob: file.slice(i * CHUNK_SIZE, Math.min((i + 1) * CHUNK_SIZE, file.size))
}));
// Upload with limited concurrency
const inFlight = new Set<Promise<void>>();
for (const chunk of chunks) {
const task = uploadSingleChunk(chunk.blob, chunk.index, totalChunks, file.name)
.then(() => { inFlight.delete(task); });
inFlight.add(task);
// If we hit the concurrency limit, wait for one to finish
if (inFlight.size >= concurrency) {
await Promise.race(inFlight);
}
}
// Wait for all remaining uploads
await Promise.all(inFlight);
}
async function uploadSingleChunk(chunk: Blob, index: number, total: number, fileName: string) {
const formData = new FormData();
formData.append('chunk', chunk);
formData.append('chunkIndex', String(index));
formData.append('totalChunks', String(total));
formData.append('fileName', fileName);
await fetch('/api/upload-chunk', { method: 'POST', body: formData });
}
Common Mistakes
Wrong chunk size
Too small (10KB) = thousands of HTTP requests with overhead. Too large (100MB) = defeats the purpose of chunking, timeouts still likely.
✅Use 1-10MB chunks. 5MB is a common sweet spot that balances request overhead vs failure recovery.
Off-by-one on the last chunk
The last chunk is usually smaller than CHUNK_SIZE. Forgetting to cap `end` at `file.size` can cause issues.
✅Always use `Math.min(start + CHUNK_SIZE, file.size)` for the end boundary.
No unique upload identifier
If two users upload a file with the same name simultaneously, chunks get mixed up on the server.
✅Generate a unique upload ID (UUID) per upload session and include it with every chunk request.
Why Interviewers Ask This
Chunked file upload is a classic system design question for frontend engineers. It tests whether you understand large file handling, network reliability, progress tracking, and concurrency control. It also comes up in machine coding rounds where you might need to implement it from scratch.
Quick Revision Cheat Sheet
blob.slice(start, end): Creates a lightweight view of a portion — no data copy
Chunk size: 1-10MB typical, 5MB is a common default
Resumable uploads: Ask server which chunks exist, skip those, retry only failed ones
Parallel uploads: Upload multiple chunks concurrently with a concurrency limit
Progress tracking: Track uploaded bytes per chunk completion
Real-world examples: Google Drive, Dropbox, YouTube, S3 multipart uploads