Ok, before we go into the depths of these concepts, I want to tell you that we will take it easy. I don't want you to get overwhelmed by terms like pre-signed URLs, object storage, chunked upload pipelines, or CDN edge nodes.
We are going to explore all of this through the lens of how a web developer would understand it. If you are coming from a web background and stepping into mobile for the first time, this transition introduces a completely new domain — and covering it from that angle will make it click much faster.
You've probably saved a Reel draft at 2 AM, opened Instagram four hours later, and it was right there — untouched. Or you've watched a Reel, swiped away, came back, and it loaded instantly without buffering. None of that happens by accident.
The system behind it is thoughtful, layered, and surprisingly easy to reason about once you see the full picture.
It Starts With a Familiar Problem: Pre-Signed URLs
If you have ever built a frontend with your own backend, you've almost certainly encountered this pattern.
When a user uploads a file, say, a profile picture or a video, you don't want to route that file through your own backend server. Instead, you generate a pre-signed URL: a link signed by your private key, valid strictly for the purpose you assigned to it, and handed directly to the client. The client uploads straight to a storage provider like AWS S3. Your backend never touches the file.
This keeps your servers fast and your upload costs low. The storage provider handles the heavy lifting.
But here's the catch. The moment you hand that URL to the client, you lose visibility. You no longer control the process. Did the upload succeed? Did it fail at 60%? You have no idea.
That's where webhooks come in.
Think of a webhook as a system that says, "Let me know when the job is done."
Let me explain it in simple words. You expose a secure endpoint on your backend. Once the upload completes, AWS S3 calls that endpoint automatically, saying "job done", ping. You didn't watch the upload happen. You just got notified when it was over.
Web developers solve this on the browser side easily enough. The browser temporarily holds the file in memory. If a page refresh happens, sure, the data is gone, but for the duration of a normal upload session, the browser manages it just fine.
Now take that same problem to mobile. And suddenly everything gets harder.
The Mobile Upload Problem Nobody Talks About
On mobile, starting a direct upload means the media sits in memory. That's it, just memory.
Networks drop. Apps close. The OS kills background processes when it needs resources. Imagine a user uploading a 3-minute video. It reaches 98%, and then the connection drops or the app refreshes. Back to 0%. They wait for 100% all over again.
That is a terrible user experience. And it is entirely avoidable.
You might be thinking, "Can't we just retry the upload automatically in the background?"
You can. But if the media only lives in memory, there is nothing left to retry after the app closes. The file is gone. Retrying from zero is the only option. The fix has to happen earlier, the moment the upload begins.
Writing to the File System First
The moment a user starts an upload, we immediately write the file to a local directory.
On mobile, tools like Expo File System give apps access to a sandboxed environment on the user's device — a real file system with real directories. The app can read and write files there freely, independent of what the network is doing.
There are generally two types of directories in this sandbox. A temporary directory for short-lived data and a persistent data directory for things that must survive app restarts, OS cleanup, or low-storage pressure.
Since we only need the media until the upload finishes, the temporary directory is the right call here. The moment the user initiates the upload, we write the file into temp storage. The user sees the upload progress as normal. Behind the scenes, we've created our own local backup, and now we have something to resume from if anything goes wrong.
Chunking and Streaming: Why Not Just Upload the Whole File?
Once the file is written locally, we take the storage URL, divide the file into chunks, and stream them one by one.
Chunking does two important things.
First, it handles memory efficiently. The entire media file never loads into RAM at once. A 500MB video doesn't need 500MB of device memory to upload; each chunk gets loaded, sent, and released.
Second, it enables background processing. Because we're streaming chunks rather than sending one massive request, the app can hand the task off to a background process. The UI stays responsive. The app doesn't feel laggy. And if a chunk fails, only that chunk retries — not the entire file.
This is why you've seen a progress bar on Instagram pause and then quietly resume. The upload wasn't frozen. It was retrying one chunk.
The Storage Comparison: Web vs Mobile
If this pattern feels familiar, it should. Web developers have been solving the same problem in the browser for years.
On the web, Local Storage handles quick key-value data, but it caps at around 5MB. For anything larger, there's IndexedDB, which offers a much bigger capacity. More recently, browsers added OPFS (Origin Private File System), which essentially runs a sandboxed file system through WebAssembly, structured, persistent, and purpose-built for larger data.
Mobile mirrors this hierarchy exactly. Async Storage on React Native is the direct equivalent of Local Storage, with the same ~5MB ceiling and the same "never put media here" rule. For real files, Expo File System is your IndexedDB — a sandboxed directory that can handle actual binary data.
You might be thinking, "Why not just throw everything into a SQLite database on the device?"
Because you never store blobs, i.e. media files, in an SQL database. Technically possible. Never recommended. SQL databases are for structured relational data. Blob storage belongs in systems built for it: file systems, object storage, CDNs. The moment you start storing video frames in SQLite rows, you have a performance problem and a maintenance nightmare.
What Happens When You Save a Draft
Whenever a user saves a draft, like the "Save as Draft" option on Instagram Reels or LinkedIn, the app grabs all the serialized data: video frames, audio, filter state, caption text, edit history. It writes everything to a structured location in local storage and leaves it there.
Think of a photographer you've hired to take your pictures at a wedding. They don't back up every single photo to Google Drive between shots. They capture everything on their memory card first, stay focused on the job, and deal with the backup later. Losing Wi-Fi mid-shoot doesn't cost them a single frame.
Instagram works the same way. Capture locally first. Upload when the time is right.
Draft data survives app restarts because the OS writes it to disk, not just to memory. This is also why, when you check "App Info" on your phone, you see the initial install size versus the much larger current size. That difference is data; it is split between things like cache storage and app storage. Everything the app is quietly managing on your behalf.
Sounds simple, right? But there's a subtle problem: if the user clears the app's cache, or if the OS decides to reclaim storage under pressure, drafts can disappear. This is exactly why heavy apps often warn you before clearing the cache or quietly sync draft metadata to their servers even before you tap publish.
Cloud Storage — Where Your Media Lives After Upload
Once you hit Share, Instagram's servers take over.
Instagram doesn't store your media in a regular file system like your laptop's hard drive. They use object storage — a system where every photo or video becomes a single blob of data with a unique identifier.
Object storage is a data storage architecture where data is managed as discrete units called "objects," each with its own ID, metadata, and content.
This gives Instagram the flexibility to store billions of photos across countless servers and retrieve any one of them in milliseconds.
The Real Engineering Part-Backend handling of uploads
Here is the portion that almost stays the same for web or mobile apps, and you could follow if you are ever dealing with systems involving media
When your Reel lands on Instagram's servers, it goes through a media processing pipeline before anyone else can watch it.
Here's what that pipeline does:
Transcoding: Your phone records in whatever format it supports — H.264, H.265, HEVC. Instagram converts it into multiple output formats and multiple resolutions. One source video becomes dozens of versions: 1080p, 720p, 480p, and lower for HLS streaming ( i.e. the player picks the right one based on the viewer's connection speed at the time of playback ). In this stage, the most commonly used library is ffmpeg, which is the standard for these conversions and the most notorious of all to handle on the server
Thumbnail generation: The system extracts the first frame (or a smart-selected moment) and generates a low-resolution preview image. As you scroll your feed, thumbnails just keep appearing instantly, so you don't have to wait. That's because object storage extracts the first frame at processing time, generates a low-res photo, and stores it directly in cache. The feed serves it from there. No network call per thumbnail. Instead, batch calls happen all at once, the data gets saved, and it sits ready for whenever you scroll to it.
Audio processing: Background music rights checks, audio normalization, and sync validation all happen here.
Metadata extraction: Duration, aspect ratio, codec — all indexed so the content delivery layer knows exactly what it is about to serve.
The whole pipeline runs asynchronously. You tap Share, get a "processing" confirmation, and Instagram's servers work through it without blocking your session.
Content Delivery Using CDNs
Once the pipeline finishes, Instagram pushes the final video files to a CDN — a Content Delivery Network.
A CDN is a distributed network of servers placed physically close to users around the world. When someone in Bengaluru watches your Reel, they are not fetching that video from a data center in Virginia. They fetch it from the nearest CDN edge server — which might sit in Mumbai.
I have already covered CDN in a previous blog, you can check it out here: CDNs
Caching — The Secret Behind Instant Loads
CDN caching handles content at the network level. But caching also happens inside the app itself.
When you scroll your feed, Instagram prefetches the next few videos before you reach them. The app estimates your scroll speed, predicts which Reels you'll hit next, and starts downloading them in the background. By the time you arrive at that Reel, the app already has it sitting in memory.
This prefetched data lives in an in-memory cache, which is fast to access, and is gone when you close the app. A second layer, the disk cache, keeps recently viewed content around a bit longer so reopening the app feels instant.
You might be thinking, "Won't caching everything eat up my phone storage?"
It does, which is why the cache has a size limit. Instagram's app typically caps its disk cache at a few hundred MBs. When that limit hits, the oldest cached files get evicted first. The content always remains available from the CDN — the local cache is just a shortcut that saves a network round-trip.
DIY: Build It Yourself
Here is a simplified yet production-ready version of the local-write-then-chunk-upload pattern in a React Native app, using the Expo File System and a pre-signed URL from S3.
import * as FileSystem from "expo-file-system";
const CHUNK_SIZE = 5 * 1024 * 1024; // 5MB per chunk
async function writeToTempAndUpload(
localUri: string,
uploadId: string,
getPresignedUrl: (partNumber: number) => Promise<string>
) {
// Step 1: Copy to temp directory for resilience
const tempPath = `${FileSystem.cacheDirectory}upload_${uploadId}.mp4`;
await FileSystem.copyAsync({ from: localUri, to: tempPath });
const fileInfo = await FileSystem.getInfoAsync(tempPath, { size: true });
const totalSize = (fileInfo as FileSystem.FileInfo & { size: number }).size;
const totalChunks = Math.ceil(totalSize / CHUNK_SIZE);
const uploadedETags: string[] = [];
for (let i = 0; i < totalChunks; i++) {
const partNumber = i + 1;
const start = i * CHUNK_SIZE;
const end = Math.min(start + CHUNK_SIZE, totalSize);
// Step 2: Get a fresh pre-signed URL per part
const presignedUrl = await getPresignedUrl(partNumber);
// Step 3: Upload this chunk
const result = await FileSystem.uploadAsync(presignedUrl, tempPath, {
httpMethod: "PUT",
headers: {
"Content-Type": "video/mp4",
"Content-Range": `bytes ${start}-${end - 1}/${totalSize}`,
},
uploadType: FileSystem.FileSystemUploadType.BINARY_CONTENT,
});
if (result.status !== 200 && result.status !== 206) {
throw new Error(`Part ${partNumber} failed: ${result.status}`);
}
const eTag = result.headers["ETag"];
uploadedETags.push(eTag);
console.log(`Part ${partNumber}/${totalChunks} done — ETag: ${eTag}`);
}
// Step 4: Signal completion to your backend (it finalizes the S3 multipart upload)
await fetch("/api/complete-upload", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ uploadId, parts: uploadedETags }),
});
// Step 5: Clean up temp file after success
await FileSystem.deleteAsync(tempPath, { idempotent: true });
}
The cacheDirectory here is your temp storage — it survives app restarts but can be reclaimed by the OS. The copy step in Step 1 is what buys you resilience. If the upload fails at chunk 3, the full file is still sitting at tempPath. Only that chunk needs to retry.
Notice that each part returns an ETag. S3's multipart upload system uses these to verify and reassemble all parts in the correct order when your backend calls CompleteMultipartUpload. You aren't uploading blind — every chunk is tracked and acknowledged server-side.
Try implementing this yourself! It is one thing to read about chunked uploads, but it is a whole different feeling when you watch a progress bar actually pause, retry a single chunk, and keep climbing.
With all this discussion, I hope you had a better understanding of how the uploads on devices behave differently from the web. The backend handling is almost the same for web or mobile.
Happy Exploration!




Top comments (0)