Dev Skills

Video upload and playback — a full worked answer

The whole thing, start to finish, in the order you would actually say it. Read it to learn the shape of a strong answer — then go and produce one yourself, because reading is not the same skill as producing.

hard
Common trap
Read this second, not first. Reading a good answer feels like learning and only builds recognition. If you have not tried this problem yet, close this page and write your own answer first. It will feel worse and it is the part that works.

The question

Design a service where people upload videos and other people watch them. Uploads come from phones on unreliable networks. Playback should start quickly and adapt to the viewer's connection.

What kind of problem this is

Big files. The payload is far bigger than the metadata, and it should not go through your servers at all.

The moment you draw the file passing through your application server, you have made bandwidth your problem. Let the client talk to storage directly.

Before anything: where is the slack?

How long the system has before a person notices. Ask this before you draw a single box — it decides more of the design than scale does.

Processing: minutes. Playback start: under 2 seconds. Upload: hours if needed.

Uploading and processing have hours of slack. Starting playback has almost none. Designing both paths the same way is the usual way to fail this problem.

Uploads have hours of slack. Users do not expect a video to be watchable the instant the progress bar completes; video products show "processing" and users accept it. That makes the design tractable: transcoding into five resolutions is expensive, parallelisable, and off the user's critical path. You can retry it, queue it behind other work, and run it on cheap interruptible capacity.

Playback start has a hard budget of about two seconds. Beyond that, a growing share of viewers give up before the video starts, and video services track this number closely. The read path is built around that budget: content near the viewer, small first segments, a low starting bitrate that steps up.

Mid-playback has a different budget again. The player keeps a buffer, typically 10 to 30 seconds, which is slack it can spend fetching the next segments and adapting quality. Adaptive streaming depends on that buffer.

So there are two systems. One is optimised for throughput and cost, with hours to play with; the other is optimised for latency, with two seconds. They share only a storage bucket.

So: Hours of processing slack means transcoding is fully asynchronous, retryable, and can run on interruptible capacity. A two-second playback budget means segments must be static files on a CDN, which is why video is chopped into segments in the first place.

1

Requirements

about 5 min

Why this stage exists: Because the question is deliberately vague, and every box you draw before this is a guess.

What you are being asked for: State your assumptions, ask one or two questions that change a box, and propose the scope.
What a strong answer sounds like

Assumptions: global audience, mobile-first for both upload and playback. Videos average a few minutes. Reads far outnumber writes: one upload is watched many times, and the design rests on that ratio. Storage is effectively unbounded and object storage is available.

First question, and it changes the caching design: is content public, or is it access-controlled per viewer? Public content can be cached at the edge and served to everyone identically. Per-viewer authorisation means either signed URLs with short expiries or authorisation at the edge, and getting that wrong means one person's private video cached and served to a stranger. I will assume public with some private videos, handled by signed URLs.

Second question: on-demand only, or live streaming too? These are different systems: live has no processing slack, needs low-latency segment production, and cannot pre-transcode. I will assume on-demand, and say I am consciously excluding live because it would change the pipeline fundamentally.

Scope: upload from an unreliable network, transcode to several qualities, and adaptive playback. Out of scope: recommendations, comments, monetisation, and copyright matching, though I would note the last one is a production requirement that sits in the processing pipeline.

The costs named here
Signed URLs for private content → costs: Short expiries mean the player must refresh them mid-playback for long videos, and a leaked URL is valid until it expires.
Say this
"Assuming on-demand, mobile-first, global, and one upload watched many times. One question: is content public or per-viewer authorised? That decides whether I can cache segments at the edge for everyone or need signed URLs, and getting it wrong leaks private video. I will assume mostly public with signed URLs for private."
Common trap
Not asking about live streaming and then designing an on-demand pipeline. If the interviewer wanted live, you have designed the wrong system and spent twenty minutes on it.
What an interviewer is ticking off in this stage
  • Stated the read:write ratio: one upload, many views
  • Asked public versus access-controlled and named the edge-caching consequence
  • Asked on-demand versus live and explained they are different systems
  • Assumed mobile and unreliable networks for upload
  • Proposed a scope and excluded recommendations and monetisation
2

Actors and lifecycle

about 6 min

Why this stage exists: Because the happy path is the easy half. The failure branches are where the design actually lives.

What you are being asked for: List the actors — including the system — then write a video's life as a chain of states, and add the failure branches.
What a strong answer sounds like

Actors: the uploader, the viewer, and the system, which decides which resolutions to produce, in what order, when a video is watchable, what to do with a file it cannot decode, and when to give up retrying.

The chain: upload started → bytes stored → queued for processing → transcoded into renditions → published and playable → viewed → possibly taken down.

Failure branches. At upload: the connection drops at 80%. With a single request that is 400 MB thrown away and an angry user, so uploads must be chunked and resumable. Given the mobile assumption, that is a requirement, not an optimisation. At upload: the same chunk is sent twice after a retry. Chunks are addressed by index, so a repeat overwrites the same data and the upload is idempotent. At stored: the processing message is lost, so the video sits forever showing "processing" and nobody is alerted. This needs the event written durably alongside the upload record, and a sweeper that finds videos stuck in a state too long. The sweeper is the branch candidates tend to forget, and a missing one shows up as support tickets.

At transcoding: the worker crashes halfway through. The job retries, and because output is written per rendition to a deterministic path, a retry redoes work rather than corrupting anything. At transcoding: the file is broken or in an unsupported format. This is not retryable, so it must be told apart from a transient failure and moved to a failed state with a clear message to the uploader, rather than retried forever.

At published: some renditions succeeded and others did not. Publish as soon as one usable rendition exists and add the others as they finish, so the video becomes watchable sooner. At viewed: a segment is missing from the origin because of a partial publish, so the player stalls. Make publishing atomic by writing the manifest last, since the manifest is what makes segments discoverable.

At taken down: the video is cached at hundreds of edge locations, so removal is a purge with a real propagation delay, which matters for legal takedowns and is worth naming.

Common trap
A lifecycle that goes upload → transcode → play. The interesting states are "stuck in processing forever" and "three renditions worked and two did not", and at this volume both happen every day.
What an interviewer is ticking off in this stage
  • Named the system as an actor with real decisions
  • Wrote the video lifecycle as a chain
  • Chunked, resumable uploads justified by dropped connections
  • Chunk uploads idempotent by index
  • Handled the lost processing event, including a stuck-state sweeper
  • Distinguished retryable failures from permanently broken files
  • Publish when the first usable rendition is ready, not all of them
  • Manifest written last so publishing is effectively atomic
  • Noted takedown propagation delay at the edge

The lifecycle, drawn

UploadingphoneStoredobject storageQueuedsystemTranscodedworkersPublishedsystemConnection drops at 80%chunked and resumable: ask which chunks exist, send the restProcessing event lostevent written in the same transaction; sweeper finds stuck videosWorker crashes mid-jobretry; deterministic output paths make it idempotentFile is unplayablenot retryable: fail explicitly and tell the uploader whyOnly some renditions donepublish on first usable rendition, add the rest as they landTakedown requestedpurge propagates across edges with real delay; say so
A video's life. "Stuck in processing forever" is the branch that users notice and that is most often missing from a whiteboard.
3

Numbers

about 6 min

Why this stage exists: Because one part of this is hard and the rest is easy, and they look identical until you do the arithmetic.

What you are being asked for: Estimate ingest volume, storage, and (the number that matters most here) egress bandwidth. Then finish "So the hard part here is ___."
What a strong answer sounds like

Ingest: assume 500 hours uploaded per minute. At roughly 1 GB per hour for source quality, that is 500 GB a minute, about 8 GB per second arriving. Meaningful, but it goes straight into object storage and never through my application servers, so it is not a compute problem.

Storage: 500 hours a minute is 720,000 hours a day, about 720 TB a day of source. Transcoding into five renditions roughly doubles to triples the total, so call it 2 PB a day. That is a lot, but it is only money: object storage is cheap per byte, and old, rarely-watched content moves to colder tiers.

Now egress, which is the number that changes my mind. Assume 100 million viewing hours a day, which is about 4 million concurrent viewers on average. At 3 Mbps each that is about 12.5 Tbit/s, or roughly 1,500 GB per second sustained: two orders of magnitude more than ingest, and continuous. If that traffic came from my origin servers, bandwidth would be by far the largest line item in the system, larger than storage or compute.

Transcoding compute: 500 hours a minute, and transcoding roughly real-time per rendition per core, so five renditions is 2,500 core-hours of work arriving every minute. That is about 150,000 cores busy around the clock. Significant, and elastic because of the processing slack.

So the hard part here is egress bandwidth, and the answer is that almost none of it may come from my origin. Storage, ingest and even the transcode fleet are smaller problems. This estimate shows that the CDN is not an optimisation in this design; it is the core of the read path.

Say this
"Ingest is about 8 GB a second; egress is around 1,500 GB a second, nearly two hundred times larger and continuous. So the hard part here is egress bandwidth. That ratio means almost nothing may be served from my origin, and it is why video gets chopped into static segments in the first place."
Common trap
Estimating storage, saying "petabytes", and stopping. Storage is the cheap part. Egress is where the money and the engineering go, and skipping it misses the point of the problem.
What an interviewer is ticking off in this stage
  • Estimated ingest and noted it bypasses application servers
  • Estimated storage including the rendition multiplier
  • Estimated egress and compared it to ingest: the key comparison
  • Estimated transcoding compute and noted it is elastic
  • Concluded egress dominates and the CDN is structural, not an add-on
  • Finished the sentence

The numbers, drawn

Ingest~8 GB / sec
Egress to viewers~1,500 GB / sec
Storage added per day~2 PB / day (5 renditions)

Egress is ~180x ingest and it never stops. So the hard part is bandwidth, and the answer is that almost none of it may come from your origin. That is why video is segmented into static files in the first place.

The comparison that decides the design: what you serve dwarfs what you receive.
4

High-level design

about 12 min

Why this stage exists: Because now — and only now — you have a reason for every box you are about to draw.

What you are being asked for: Draw the design. Justify each box, and be specific about how playback works.
What a strong answer sounds like

Upload path: the client asks the API for a presigned upload URL, then uploads chunks directly to object storage. The bytes never pass through my application servers, justified by the 8 GB per second ingest figure: routing that through my services would mean building a bandwidth tier for no benefit. The API only records metadata and, when the upload completes, writes an upload record and a processing event in the same transaction so the event cannot be lost.

Processing: workers pull from a queue and transcode into renditions (say 240p through 1080p), chopping each into segments of a few seconds and writing an adaptive streaming manifest. Justified by the slack analysis: hours available, so this is asynchronous, retryable and can run on interruptible capacity at a fraction of the cost. Workers are idempotent by writing to deterministic paths per rendition.

Why segments, which is worth explaining properly: chopping video into short chunks turns playback into a series of ordinary static file requests. Each one is cacheable at the edge, resumable, and chosen independently, which is what makes both CDN delivery and adaptive quality possible. The whole read path rests on this decision.

Playback: the player fetches the manifest, then requests segments from the CDN. It starts at a low bitrate so playback begins fast, measures how quickly segments arrive, and steps up or down per segment. Justified by the two-second start budget and the fact that a mobile connection changes constantly.

Delivery: segments are static immutable files with long cache lifetimes and content-addressed paths, so the CDN hit rate is extremely high and purging is almost never needed. Justified by the egress number: the CDN is what makes this system affordable, and the hit rate largely decides what the bandwidth bill is.

Metadata: a normal database holding video records, owner, status, and rendition list. Small, ordinary, and separate from the media path. It is worth saying that the hard scale is in the bytes, not in the metadata.

Access control for private videos: signed URLs with short expiries, issued by the API after an authorisation check. The CDN validates the signature, so the origin is not consulted per segment. Cost: the player must refresh signatures during long videos.

The costs named here
Direct-to-storage upload → costs: Less control over the byte stream: validation and virus scanning happen after the fact rather than inline. Unavoidable at this volume.
Five renditions per video → costs: Triples storage and multiplies transcode cost fivefold. Worth it because it lets a viewer on a bad connection watch at all, instead of leaving.
Interruptible capacity for transcoding → costs: Jobs get killed and must resume, so processing time is less predictable. Acceptable given hours of slack, and it is a large cost saving.
Common trap
Drawing the upload passing through your application servers. At 8 GB per second you have just made yourself a bandwidth company for no reason. Presigned direct upload is the answer the interviewer expects.
What an interviewer is ticking off in this stage
  • Upload goes directly to object storage via presigned URLs, bypassing app servers
  • Justified that with the ingest number
  • Processing is asynchronous, queued, idempotent, and on interruptible capacity
  • Explained why video is segmented, and connected it to caching and adaptivity
  • Adaptive bitrate driven by measured throughput, starting low for fast start
  • Segments are immutable with long TTLs, for a near-perfect CDN hit rate
  • Metadata kept separate and acknowledged as the easy part
  • Access control via signed URLs validated at the edge

The architecture, drawn

get URLchunks, directmissmissUploaderAPIpresigned URLsObject storagesource + segmentsTranscode queueTranscode workersinterruptibleMetadata DBCDNimmutable segmentsRegional cachecoalesces missesViewer
Bytes never pass through the application servers. The upload path and the playback path share only a storage bucket.
ProducerfastQueuebacklogWorkerslow
four inone out — the queue absorbs the difference, and grows
Transcoding: uploads arrive faster than workers finish. Hours of slack mean the queue can absorb this, as long as you watch its age, not its length.
5

Deep dive and tradeoffs

about 14 min

Why this stage exists: Because naming what a decision costs is the single clearest signal of seniority there is.

What you are being asked for: Go deep on the two hardest parts. Name a cost after every decision.
What a strong answer sounds like

Hard part one: keeping origin egress near zero, which means the cache hit rate is the number that matters. Popular videos are easy to cache: a viral video is watched by millions and its segments are hot everywhere, so the hit rate approaches 100%. The problem is the long tail: a huge number of videos watched a handful of times each. Those get evicted between views, so they miss, and while they are a small share of views they can be a large share of origin traffic. Mitigations: tiered caching, where edge misses go to a regional cache rather than straight to origin, which collapses many edge misses into one origin fetch; and keeping only the first few segments of unpopular videos warm, since many views are abandoned in the first seconds and never reach later segments. Cost: another caching layer to run, and more storage held at regional tier.

The first segments are worth special treatment in general: everyone who plays a video fetches segment one, and far fewer reach segment fifty. Prioritise transcoding and warming the opening segments so playback starts fast even for cold content. Cost: a more complex pipeline that treats parts of one video differently.

Regional popularity: a video going viral in one country makes that region's edges hot while others stay cold. The CDN pulling on demand handles this, but the origin sees a burst as many edges fetch the same segments at once. Request coalescing at the regional tier turns thousands of simultaneous edge misses into one origin read. Cost: a small added latency on the very first fetch.

Hard part two: uploads from unreliable networks. Chunked and resumable, with chunks of a few megabytes so a failure loses seconds of progress rather than everything. The client asks which chunks the server already has and sends only the missing ones, so resuming after an app restart, or a day later, works. Chunks are addressed by index, so a duplicate is an overwrite and retries are safe with no extra machinery. Cost: more complex client code, server state tracking partial uploads, and a cleanup job for abandoned ones. Without that job, partial uploads accumulate silently and cost real money.

Processing cost control: transcode lazily for rare resolutions. Produce the common renditions immediately, and generate 4K only when someone asks for it. Most videos are watched a few times at 720p or below, and 4K is by far the most expensive rendition to produce, so this avoids a large share of transcode cost. Cost: the first viewer wanting a rare rendition waits, or gets a lower quality. That is acceptable for the saving.

The stuck-video problem, revisited because it is a common production failure: a video whose processing event was lost or whose job failed silently sits in "processing" forever. A sweeper looking for videos in a non-terminal state past a threshold, which retries or fails them explicitly, is the fix. Cost: a background job to run and monitor. Without it, the failure is invisible to you and highly visible to the user.

Consistency per feature: metadata is strongly consistent, since a user renaming their video should see it immediately. The published state is eventually consistent across regions and a few seconds of lag is fine. Segments are immutable, so they cannot be stale: a benefit of content addressing that is easy to overlook. View counts are approximate and batched.

What I would monitor: playback start time at p50 and p95, rebuffer rate, CDN hit ratio broken down by popularity tier, transcode queue age, and the count of videos stuck in processing.

The costs named here
Tiered caching → costs: An extra layer to operate and pay for, plus a little latency on first fetch. It is what keeps origin egress, the biggest cost in the system, near zero for the long tail.
Lazy transcoding of rare renditions → costs: The first person wanting 4K waits or gets 1080p. Saves the majority of transcode spend, because most videos are never watched at high resolution.
Chunked resumable uploads → costs: Real complexity in the client and partial-upload state on the server, plus a cleanup job. Non-negotiable when uploads come from phones on trains.
Say this
"Segments are immutable and content-addressed with a one-year cache, so the CDN hit rate is near perfect for popular content and I never purge. The long tail is the harder problem, because rarely-watched videos get evicted between views. So edge misses go to a regional tier with request coalescing rather than to origin. Cost: another layer to run, and it is what keeps my largest bill near zero."
Common trap
Optimising for the viral video. That case is easy, because it caches itself. The engineering is in the long tail of videos watched three times, which is where most origin traffic comes from.
What an interviewer is ticking off in this stage
  • Identified the long tail as the real cache challenge, not viral content
  • Tiered caching with request coalescing to protect the origin
  • Special-cased the first segments of a video
  • Resumable chunked uploads with a query for missing chunks
  • Cleanup job for abandoned partial uploads
  • Lazy transcoding of rare renditions as a cost control
  • Sweeper for videos stuck in processing
  • Per-feature consistency answer
  • Named what to monitor, including rebuffer rate and hit ratio by tier
  • Cost named after every decision

The decision this turns on

A
Direct to object storage (presigned)
  • —Bytes never touch your servers, so you never build a bandwidth tier.
  • —Storage handles resumption, parallel chunks and retries natively.
  • —Scales with upload volume without extra work from you.
  • —Validation and scanning happen after the fact, not inline.
B
Through your application servers
  • —Full control: validate, scan and transform the stream as it arrives.
  • —Simpler client, one endpoint, no presigning dance.
  • —You now carry 8 GB/sec of ingest bandwidth and the servers to terminate it.
  • —Every upload holds a connection for minutes, which wrecks your capacity model.
Which one: Direct to storage, once the payload is large. The control you give up can be recovered asynchronously; the bandwidth bill you take on cannot. The same reasoning applies to any big-file problem: file storage, image hosting, backups.
Where the upload bytes go. This is the first fork, and the wrong branch is expensive forever.

And then they push back

A complete answer is where the interview starts, not where it ends. These are the questions this design invites — the interview is decided here far more often than in the diagram.

What does this cost per month?
A weak answer

"I am not sure, it depends on the provider." Interviewers rarely want exact prices. They want to know whether you understand which line item dominates, and that is answerable from your own estimates.

A strong answer

"I would not guess at prices, but I can tell you the shape, which is what matters. The dominant cost here is egress bandwidth: my estimate said 1,350 GB a second to viewers, and at that volume nothing else on the bill comes close. Second is storage, because I keep raw data for two years and it grows every day whether anyone reads it or not. Third is compute, which is elastic and therefore the easiest to control. That ordering tells me where to spend engineering effort: a percentage point of CDN hit rate is worth more than any amount of compute optimisation, so cache hit rate is the number I would put on a dashboard and defend. Storage cost I would attack with lifecycle policies moving cold data to cheaper tiers and with tiered granularity. Compute I would run on interruptible capacity because the work has hours of slack."

The cache just died. What happens?
A weak answer

"Requests would go to the database instead, so it would be a bit slower." At the ratios that justify a cache, the database cannot take that load. This answer means you added a cache without checking what it was protecting you from.

A strong answer

"Every read that was being served from memory lands on the database at once. My cache was absorbing something like 99% of reads, so the database sees roughly 100x its normal load, and it will not survive that. A naive design goes down. What I would do about it, designed in advance: coalesce misses so a thousand simultaneous requests for the same key produce one database read rather than a thousand; keep a small in-process cache on each server as a second layer so the hottest keys never leave the machine; shed load at the edge rather than let the database die, because a degraded site beats a dead one; and bring the cache back warm rather than empty, because restarting into an empty cache reproduces the outage. Cost of the in-process layer: servers can briefly disagree with each other."

10x traffic tomorrow. What fails first?
A weak answer

"We would add more servers and scale horizontally." This says nothing. Every system has a first thing to break, and not naming it means you do not know your own design well enough to have found it.

A strong answer

Name a specific component and the reason. "The stateless services scale out fine, so they are not the constraint. The database primary is, because every write goes through one machine and I am at roughly 40% of its write capacity now. So at 4x I am at the ceiling, and I would see write latency climb and connection pool waits before anything errors. The second thing is the cache, because 10x reads on a fixed working set is fine but 10x on a growing key space means my hit rate drops and the misses land on the database that is already the constraint. In order: database writes, then cache hit rate, then the connection pool. And I would find out from p99 write latency, not from errors."

One user reports a slow request. How do you debug it?
A weak answer

"I would check the logs." Which logs, for what? At scale, "check the logs" without a way to find that specific request is a way of saying you have not thought about observability.

A strong answer

"Work down rather than guessing. First: is it slow for everyone or just them? A dashboard of that endpoint's p50 and p99 answers it in seconds — if p99 is bad and p50 is fine, it is probably data-dependent, meaning that user has far more rows than the median. Second: get a trace of their actual request, which requires request ids propagated through every service, and see whether the time went to one slow call or two hundred fast ones. Two hundred fast ones is an N+1, which is invisible in development and common in production. Third: if it is one slow query, run EXPLAIN with their parameters, not typical ones, because the plan can differ once their row count crosses a threshold. The common outcome is that they are a heavy account and the query has no index for their filter combination. What makes this possible at all is having decided beforehand to propagate request ids and to log with enough structure to find one user's request among billions."

Why a queue instead of just calling the service directly?
A weak answer

"Queues decouple services and are more scalable." True in the abstract and it does not explain why this particular call needs one. Queues also add duplicate delivery, ordering questions and debugging difficulty, and not mentioning those means you added one by reflex.

A strong answer

"Because the user does not need to wait for this work to finish, and because the downstream cannot absorb my spikes. Concretely: sending the receipt email takes 800 ms and can fail, and there is no reason to make the customer wait for it or to fail their order because a mail provider is down. The queue absorbs a 10x spike into a longer backlog instead of a broken service. What it costs: the customer gets 'received' rather than 'done', so the product has to be honest about that; every consumer must be idempotent because delivery is at-least-once; and a failure now happens in a worker minutes later, which is harder to debug. I would not queue the stock check or the payment, because the user needs a real yes-or-no answer before they leave the page. A queue there adds latency and buys nothing."

A whole region is down. What do users see?
A weak answer

"We failover to another region." That is the mechanism, not the answer. The question is what a user experiences, for how long, and what is lost.

A strong answer

"I would answer per path rather than globally. Reads: if replicas exist elsewhere, they keep working, possibly stale, and I would say how stale. Writes: if the leader was in the dead region, writes fail until a new leader is promoted. That takes seconds to a minute, and being aggressive about promotion risks promoting during a brief network blip and ending up with two leaders. For contested resources like inventory I would rather be unavailable for that region's writes than risk selling the same thing twice, and I would say that explicitly as a choice. For non-contested writes like posting a comment, take it locally and reconcile. Data loss: with async replication, anything committed in the last few seconds and not yet replicated is gone, so I would use semi-synchronous replication for anything I cannot lose. And DNS-based failover is slow because clients cache DNS for minutes, so I would not rely on it alone."

Now make it multi-region.
A weak answer

"We would replicate the database to another region." That covers reads. The question is what happens to writes, and that is where the design actually changes.

A strong answer

"Reads are the easy half: replicas in each region, users read locally, and they may be slightly stale — I would say how stale and which reads cannot tolerate it. Writes are the hard part, and there are three options. Single write region: simplest, no conflicts, and users far away pay 150 ms per write, which is fine if writes are rare and unacceptable if they are not. A home region per data partition, so each user's data lives near them: local writes without conflicts, but it needs a routing layer plus a plan for users who move. Multi-leader with conflict resolution: local writes everywhere, and now I own a conflict problem, which is only acceptable where merges are safe. I would pick the second for user-owned data and single-region for anything globally contested like inventory. What changes elsewhere: cross-region replication cost, a failover story per region, and data residency rules that may force certain data to stay in certain places. I would ask about that constraint rather than discover it later."

There is an option that is 40% cheaper but 10% slower. Do you take it?
A weak answer

"I would take the faster one because user experience matters." Reflexive. 10% slower may be entirely invisible, and 40% is a large amount of money that could fund things users would actually notice.

A strong answer

"It depends where in the system, and I would answer with the slack budget. On the playback start path, where the budget is two seconds and users leave past it, 10% slower is meaningful and I would not take it. On the transcoding pipeline, where I have hours of slack, 10% slower is invisible and I would take the 40%. That is what the slack is for. So my answer is: take it everywhere the user cannot perceive the difference, refuse it on the paths where a human is waiting. Concretely that means interruptible capacity for batch work, cheaper storage tiers for cold data, and premium capacity only for the request path. And I would want to know whether the 10% is on the median or the tail, because a 10% worse p99 can be much more damaging than a 10% worse median."

The topics this answer used

Now do it yourself

You have read a good answer. That is worth very little until you can produce one on a blank page — so go and write this problem without looking, and compare afterwards.