Skip to content

The browser uploaded the photo. Why is it not yet part of the account?

The File Left the Origin. Was It Safe to Deliver? · Part 1 of 6 · 5 min read

Read the worked explanation ↓

Design profile-photo uploads for 40 million accounts where upload bandwidth must bypass the API tier. Define the exact object lifecycle and prevent another customer from completing someone else's upload.

Think first

Design profile-photo uploads for 40 million accounts where upload bandwidth must bypass the API tier. Define the exact object lifecycle and prevent another customer from completing someone else's upload.

Take a few minutes to form an approach, then compare it with the worked explanation.

Reveal the worked explanation

Interview question. A photo-sharing product accepts 25 MB uploads from millions of users. Application servers are spending most of their network capacity relaying bytes to object storage. Design direct browser uploads without making storage credentials public, and explain when an uploaded file becomes a trusted user asset.

Imagine someone selects a photo on their phone. In the obvious implementation, the browser sends 25 MB to your API, your API validates the session, receives every byte, and then sends the same bytes to an object store. The application fleet is now a bandwidth relay. Adding servers solves capacity for a while, but why should the business-logic service carry the image at all?

A presigned upload lets the application authorize an operation without transporting its payload. The client calls POST /uploads with the proposed file type and size. Your API authenticates the user, checks the account's quota, allocates a random object key such as incoming/account-42/upload-8f3..., records an upload attempt, and returns a short-lived signed upload capability. The browser sends its bytes directly to object storage. Your server has not handed out bucket credentials. It has delegated one permitted request using credentials and permissions held on the server side.

That last distinction is the first interview checkpoint. A presigned URL is effectively a bearer capability. Anyone holding it may use it within its permissions and valid lifetime. If a URL leaks in a log or analytics event, the fact that the recipient never logged into your product does not automatically stop the storage operation. Keep it out of logs, use HTTPS, bind it to a specific key and method, keep its lifetime short, and ensure the signing identity has narrowly scoped permissions.

There is also an overwrite trap. A presigned S3 PUT can be used more than once until expiry, and uploading to an existing key can replace the object. Do not issue a predictable key that is already the public photo URL. Generate an unguessable per-attempt key and publish a different, stable logical asset reference only after verification. If you require create-once semantics, design a compatible conditional-write protocol and verify that every upload client actually signs and sends the required condition. A unique key by itself makes collision unlikely, not a cryptographic guarantee that no second upload can change its contents.

Upload authorization goes through the API, bytes go directly to private storage, and only a verified object becomes visible.
Upload authorization goes through the API, bytes go directly to private storage, and only a verified object becomes visible.

A completed HTTP PUT is not an approved photograph. The browser then calls POST /uploads/{id}/complete. That endpoint must not blindly believe uploaded: true. It reads the upload record, checks ownership, examines the exact staged object and object version where supported, and verifies length, checksum, and permitted media properties. It can enqueue malware scanning and image decoding in a sandbox before changing the asset's state from PENDING to READY. Until then, the user's gallery does not serve the staged bytes. Publication must bind to exactly the version that passed those checks. Pin its immutable object version, enforce create-once writes, or promote a verified version to a separate immutable serving key. Serving the latest bytes at the still-writable staging key would let a reused upload URL replace them after scanning.

Decide how size restrictions are actually enforced. A presigned POST policy can carry a content-length-range condition. A presigned PUT URL, by itself, does not imply that same POST policy constraint. With PUT you may need a signed content length or a provider-specific enforcement mechanism, followed by authoritative size verification and deletion of rejected uploads. Similarly, Content-Type: image/jpeg is client-supplied metadata, not evidence that the bytes are a safe JPEG. Decode and inspect the data rather than trusting its filename.

A client can disconnect after sending the last byte but before receiving success. The correct recovery is not necessarily another upload. Let the browser query its upload attempt. If storage has the expected content, finalization can resume. If nothing arrived, issue a new attempt or retry the narrowly authorized operation. If the finalization request is repeated, use the upload ID and a conditional state transition so that only one public asset record wins. Treat each object version carefully if replacing bytes at the same key is permitted.

Now consider the user who starts ten uploads, finishes two, and closes the tab. Eight staged objects will be left behind. Put staged objects under an identifiable prefix or tag with bounded retention, then clean them with a lifecycle or reconciler only after checking that they are not referenced by a completed asset. Do not make the cleanup race a new data-loss problem. Expiring a URL is not the same as deleting an object or reversing a completed write.

At production scale I would track issuance rate, completed PUT rate, finalized asset rate, bytes staged versus bytes published, rejected media, orphan age, and failure reasons per storage provider and region. A widening gap between uploaded and finalized objects could indicate failed scanners, bad browser retries, expired capabilities, or abusive uploads. These are more useful than a single successful-upload percentage.

The interview answer should end with a clear trust boundary. The app authorizes a limited storage operation, storage accepts untrusted bytes, and an independent verification step makes those bytes eligible for public delivery. This is different from designing multipart transfer internals. The central skill is moving bandwidth off the API without accidentally moving authorization and publication authority into the browser.

Go deeper

For S3, SigV4 presigned URLs use the issuing principal's permissions and effective credential lifetime. Expiration of the signed URL does not itself revoke an already completed write. Where strict upload limits are needed, compare presigned POST policies, signed request headers, scoped temporary credentials and backend verification rather than assuming a single provider-independent PUT constraint. S3 object-created notifications can be delivered at least once and out of order, so an asynchronous scanner should be idempotent and bound to the exact object identity, not to the latest contents of a reusable key. For multipart objects, understand checksum composition and distinguish an ETag from a guaranteed MD5 checksum.

Check your reasoning

  • Separate issuance of an upload capability from accepting its bytes.
  • Restrict key, method, lifetime and content constraints without assuming URL secrecy.
  • Verify the staged object server-side before asset publication.
  • Make completion idempotent and handle duplicate or ambiguous PUTs.
  • Bound abandoned uploads and protect scanner resources.

Common mistake: A valid presigned URL is a one-time login token for the browser.

Which direct-upload workflow moves payload bandwidth off the API while retaining ownership and publication checks?

Read the answer explanation

Sign a short-lived staged key, verify bytes, then conditionally publish — Delegating a storage operation does not delegate ownership checks or the right to publish untrusted content.

Apply it elsewhere

A legal-document product accepts private PDF attachments from external collaborators. Design direct uploads and a publication process that also enforces retention and document-level access controls.

Compare your approach

Identify the delegated capability, staging-key policy, enforceable upload constraints, server-side content verification, idempotent finalization and abandoned-object cleanup.

One more constraint

Does a valid presigned PUT URL guarantee that the client may upload a photo only once?

Quick revision

A direct upload starts with the application authorizing one short-lived, narrow storage operation. The browser sends bytes straight to private object storage. The server then verifies the staged object's identity, size, integrity and content before publishing a logical asset reference. Treat the presigned URL as a bearer capability, expect retries and incomplete uploads, and make finalization idempotent. The most dangerous shortcut is equating a successful storage PUT with an authorized, safe public file.

References