Cloud Compute Options and Trade-offs Questions
Choosing among compute abstractions independent of provider: virtual machines, containers, managed container services, serverless functions, and bare metal. Covers the cost, control, cold-start, scaling, and operational trade-offs of each model; how to pick an instance family or hardware accelerator (general-purpose, compute-optimized, memory-optimized, GPU, TPU) and a purchasing model (on-demand, reserved, spot); and how workload characteristics (latency, statefulness, burstiness) drive the decision. Managed-versus-self-managed reasoning lives here.
Your ML platform team runs a mix of training and inference workloads and needs to decide between GPU, TPU, and CPU-only instances for each. Walk through how you'd make that decision: what would push you toward one option over another, and what would change your answer between the training and inference workload?
Sample Answer
Direct answer
Choose based on what the workload's computation actually looks like. Graphics processing units, or GPUs, the general workhorse for parallel numeric computation, are the safe default for both training and inference because of their broad software and framework support. Tensor processing units, or TPUs, custom accelerator chips built specifically for the matrix-multiply-heavy operations behind neural-network training and inference and offered as a managed service by their vendor, win when the workload is large-scale, compatible with them, and running at enough volume that their throughput-per-dollar advantage outweighs a narrower ecosystem. Processor-only compute is the right call when the model is small enough, or the request rate low enough, that an accelerator's cost isn't justified. Training and inference pull this decision in different directions even for the same model: training rewards raw throughput and accelerator memory capacity, while inference rewards low per-request latency and, at scale, cost per request.
Decision factors
What pushes toward a GPU: the broadest framework and tooling compatibility, availability across most infrastructure providers, and a wide range of chip tiers, letting you match accelerator size to the model's actual size rather than over- or under-provisioning. This is the default unless there's a specific reason otherwise.
What pushes toward a TPU: a workload already built on a framework with strong native support for it, running at a scale where its interconnect and matrix-multiply throughput deliver meaningfully lower cost per unit of training throughput than an equivalent GPU cluster. The trade-off is a narrower software ecosystem and less flexibility for custom or non-standard model operations that aren't well supported on the accelerator.
What pushes toward processor-only compute: small models, such as classical machine-learning methods or small neural networks, low request volume where an idle accelerator's cost would dominate, or inference workloads dominated by non-numeric work, such as input and output handling or business logic, where the model itself is a small fraction of total request time.
What changes between training and inference: training wants maximum sustained throughput and can batch aggressively, since larger batches better amortize fixed overhead and nothing is waiting on a single response, so it rewards the largest accelerator with the most memory and fastest interconnect a team can afford to run continuously. Inference has to answer one, or a small batched group of, requests within a latency budget a person or a downstream system is actively waiting on, so a smaller, cheaper accelerator sized to keep per-request latency inside budget, run as many parallel replicas, is usually more cost-effective than a few very large accelerators sitting mostly idle between requests. Also weigh accelerator memory capacity, since a training job with a large model or batch size needs enough memory to hold weights, activations, and gradients at once or must fall back to slower techniques, such as splitting the model or data across multiple accelerators. Weigh storage throughput, since training over a large dataset is often bottlenecked on reading data fast enough to keep the accelerator fed, not on the accelerator itself. And weigh networking, since multi-accelerator training needs fast accelerator-to-accelerator interconnect, as synchronizing results across nodes can dominate wall-clock time if the network, not the accelerators, is the bottleneck.
Worked example
Consider three candidate GPU tiers for a training job, labeled generically by generation from an older, smaller tier through a mid tier to a newer, larger tier, which maps to a real progression such as earlier, mid-range, and newer high-end GPU generations, each offering more throughput and memory per chip at a proportionally higher hourly cost. Suppose, for illustration, one epoch over the training set takes 40 minutes on the smallest tier, 22 minutes on the mid tier, and 13 minutes on the largest tier, with illustrative relative hourly cost ratios of 1.0, 2.2, and 4.0. Cost per epoch in relative units:
CostTier 1CostTier 2CostTier 3=6040×1.0=0.667=6022×2.2=0.807=6013×4.0=0.867Here the smallest tier is actually the cheapest per epoch despite being the slowest in wall-clock time, because its hourly cost drops faster than its hourly throughput does. The largest tier would only win on cost per epoch if finishing sooner were valuable enough to justify the premium, such as faster iteration during active development, or if its extra memory allowed a large-enough batch-size increase to more than proportionally cut epoch time, which this simple example doesn't assume. The method, measuring wall-clock time per epoch on each candidate and multiplying by that tier's hourly cost, is what should actually drive the choice, not an assumption that a bigger accelerator is always more cost-effective.
Trade-offs and pitfalls
A common pitfall is picking the largest accelerator by default for training "to be safe," which the worked example shows can be the most expensive per unit of actual progress; always compute cost per unit of work, such as per epoch or per processed token, not cost per hour. A second is reusing the training accelerator choice unchanged for inference; a chip sized for training throughput is frequently oversized and cost-inefficient for a single inference request, where a smaller, more numerous fleet usually wins on cost per request. A third is ignoring data-loading and storage throughput and treating a slow epoch as purely an accelerator problem when the accelerator may actually be sitting idle waiting on input and output.
A team needs to run nightly batch jobs with variable parallelism and a limited budget; jobs can run up to 4 hours. Compare using serverless functions, container-based batch runners, and a managed batch service for this. Recommend an approach and justify it on cost, execution-time limits, cold starts, and operational effort.
Sample Answer
Direct answer
For nightly batch jobs up to 4 hours with variable parallelism and a limited budget, I'd recommend a managed batch service over both serverless functions and a self-built container-based batch runner. Serverless is ruled out immediately: most serverless platforms cap execution time well under an hour, so a 4-hour job can't run as a single invocation at all without an artificial checkpoint-and-resume workaround. Between the two container-based options, a managed batch service wins specifically because of the budget constraint: it typically integrates spot (or preemptible) capacity natively, which is where the real savings are for interruption-tolerant batch work, without the team having to build that integration themselves.
Structured elaboration
| Option | Execution-time fit | Cold starts | Cost | Operational effort |
|---|---|---|---|---|
| Serverless functions | Fails outright: most platforms cap execution time well below 4 hours, forcing an artificial split into many short invocations even for a naturally sequential job | Irrelevant here, since serverless isn't a viable option for this duration | Would be low per-invocation, but that's moot given the duration limit | Low, but again moot |
| Container-based batch runner (self-scheduled, e.g. a scheduled job on a container platform you manage) | Fits: no execution-time ceiling | Not applicable to a scheduled batch job the way it is to a request-serving function | Moderate: you can use spot/preemptible capacity yourself, but you build and maintain the interruption-handling logic | Higher: your team owns scheduling, retry logic, and parallelism management |
| Managed batch service | Fits: designed for exactly this shape of workload | Not applicable | Lowest achievable here: native spot/preemptible integration with built-in retry-on-interruption, capturing most of the cost savings without building it yourself | Lowest: the service handles queuing, scaling parallelism, and interruption recovery |
Worked example
A nightly extract-transform-load (ETL) pipeline needs to process a variable number of data partitions each night, sometimes 20, sometimes 200, each partition taking anywhere from a few minutes to just under the 4-hour ceiling. On a managed batch service, each partition is submitted as an independent job; the service scales the number of concurrently running workers to match how many partitions are ready, uses interruption-tolerant (spot) capacity by default for the bulk of the work since ETL partitions are independently retryable, and automatically resubmits any partition whose worker was reclaimed mid-run. The budget-conscious part of the design isn't a special decision the team has to make, it's the default behavior of the managed service, whereas building the same interruption-aware retry logic on a self-managed container batch runner is exactly the kind of undifferentiated infrastructure work a small or budget-limited team is worst positioned to build and maintain well.
A distinct concern worth naming for ETL specifically: debugging a failed partition and keeping the pipeline portable across environments both matter more here than for a generic batch job. A managed batch service (or a comparable managed ETL/data-pipeline offering, naming the category rather than a specific vendor tool) that surfaces per-partition logs and lets a failed partition be re-run in isolation, with the same container image used in staging and production, avoids the common failure mode where a nightly job fails at 3am and the on-call engineer can't reproduce the failure locally because the batch environment differs from anywhere they can debug it.
Trade-offs and pitfalls
- Justification against the four named criteria: cost favors the managed batch service due to native spot integration; execution-time limits rule out serverless outright; cold starts are not a meaningful factor for scheduled batch work at all (a common wrong turn is treating cold starts as decisive here, when they matter far more for latency-sensitive request-serving workloads than for a job that already runs for minutes to hours); and operational effort favors the managed service over a self-built runner because interruption handling, retries, and parallelism scaling come built in rather than needing to be engineered and maintained.
- The most common wrong turn on this kind of question: reaching for serverless because "it's cheap and scales automatically" without first checking the execution-time ceiling against the stated 4-hour job length; the duration constraint alone eliminates the option before cost even enters the comparison.
- What would flip the recommendation: if any individual partition genuinely cannot tolerate interruption (a job with expensive, non-checkpointable intermediate state), spot-based savings stop being safely available regardless of which platform runs it, and the team would need to run that specific job on guaranteed (non-preemptible) capacity, accepting the higher cost for that one job while keeping the rest of the pipeline on interruption-tolerant capacity.
You have a small daily ETL job that processes 50,000 records and runs for about 15 minutes. Which compute option would you choose among a VM, a managed batch service, a container on Kubernetes, and a serverless function, and why? Address cost, operational effort, scalability, and reliability.
Sample Answer
Direct answer
For a small daily extract-transform-load job processing 50,000 records in about 15 minutes, recommend a managed batch service, a platform purpose-built for running scheduled or triggered jobs to completion without managing servers or a cluster, or, if none fits the existing stack, a serverless function with an execution-time limit that comfortably covers the 15-minute run. Both avoid paying for or operating standing infrastructure for a job that runs briefly once a day, which is the dominant consideration at this scale.
Comparing all four options
Virtual machine. Cost: you pay for the instance continuously unless you build explicit start-and-stop scheduling around the 15-minute window, which is wasteful and adds its own machinery. Operational effort: you own operating-system patching and the scheduling logic yourself. Scalability: a single virtual machine doesn't scale within the job; you'd resize it manually if the record count grew. Reliability: if the instance or the job process dies mid-run, you need your own retry and monitoring logic, since nothing manages that for you. This option is overkill for a job this size.
Managed batch service. Cost: you pay only for the compute-minutes the job actually runs, similar economics to serverless but designed for job-shaped workloads with a defined start and completion rather than request-and-response traffic. Operational effort: the lowest of the four, since the platform handles scheduling, retry on failure, and resource provisioning for the job's duration. Scalability: typically absorbs a growing record count by scaling the job's resource allocation without a re-architecture. Reliability: built-in retry-on-failure and completion tracking are usually native to the platform, which fits a job that needs to reliably finish, not just start. This is the strongest fit here.
Container on Kubernetes (Kubernetes automates deploying, scaling, and scheduling containers across a cluster of machines). Cost: close to free incrementally if a cluster already runs other workloads; a large fixed cost if a cluster would need to be stood up just for this one job. Operational effort: low if the cluster already exists and is operated by someone else on the team, high if cluster operations would be built from scratch for this. Scalability: scales cleanly by adjusting the job's resource requests. Reliability: Kubernetes' native job and retry semantics handle re-running a failed job. This is a good fit only if the cluster already exists for other reasons.
Serverless function. Cost: pay only for actual execution time, nothing when idle, which fits a job running once a day for 15 minutes very well, as long as the platform's maximum execution duration comfortably covers that with margin. Operational effort: the lowest, with no infrastructure to manage at all. Scalability: fine for a fixed daily record count, but if that count grew enough to push against the platform's execution-time or memory ceiling, the job would need to be re-architected, for example split into parallel chunks, rather than simply resized, which a managed batch service or a cluster job would handle more gracefully. Reliability: depends on the platform's retry-on-failure behavior for scheduled invocations, which is usually available but should be explicitly configured, not assumed.
Recommendation
Choose a managed batch service if the cloud provider offers one that fits the existing tooling, since it's purpose-built for exactly this job shape: bounded duration, retry-to-completion, no idle cost. Choose serverless instead if a managed batch service isn't available or convenient and the job comfortably fits within the platform's execution-time limits with room to grow. Reserve the Kubernetes-job option for when a cluster already exists for other workloads, and avoid the virtual machine entirely, since none of its trade-offs favor it at this scale.
Trade-offs and pitfalls
A common pitfall is defaulting to "we already have a Kubernetes cluster, just run it there" without checking whether the job's growth trajectory, in record count or run time, will eventually create resource contention with other workloads on a busy shared cluster; a small extra job can quietly become a noisy neighbor. A second pitfall is putting the job on serverless without checking the platform's maximum execution duration against the job's real worst-case run time; 15 minutes today doesn't guarantee 15 minutes forever as data volume grows, and the job can get silently killed mid-run once it passes the limit, rather than failing loudly with a clear cause.
Spot (or preemptible) instances offer a steep pricing discount in exchange for the provider being able to reclaim the instance on short notice. Describe how an architecture can safely use spot instances for stateless web tiers, batch processing, and other compute-intensive jobs, and what you'd design in to handle interruptions gracefully.
Sample Answer
Direct answer
Spot (or preemptible) instances are the same underlying hardware as on-demand capacity, sold at a steep discount because the provider can reclaim it with little warning when it needs the capacity back. That makes them safe for exactly one category of workload: work where losing an individual instance mid-task costs you nothing more than retrying that unit of work elsewhere. The architecture you build around them isn't really about spot instances at all, it's about making every unit of work idempotent (safe to run more than once with the same correct end result), checkpointed or cheaply restartable, and never the sole copy of anything.
Structured elaboration
- Stateless web tier: run spot instances as part of a larger autoscaling group that also includes a baseline of on-demand capacity. When a spot instance is reclaimed, the load balancer's health check simply routes around it and the autoscaler replaces it, exactly the same failure mode as a normal instance crashing, provided the tier holds no local, unreplicated state.
- Batch processing: design each job as a small, independently retryable unit rather than one long monolithic run. If an instance is reclaimed mid-unit, the orchestrator (a job queue or a workflow scheduler) simply re-dispatches that unit to another instance. The smaller the unit, the less work is lost per interruption.
- Other compute-intensive jobs (a rendering farm, a large parallel simulation): checkpoint progress periodically to durable storage outside the instance, so a reclaimed instance loses at most the work since the last checkpoint, not the whole job.
Designing for graceful interruption, concretely:
- Listen for the interruption notice. Providers give some advance warning before reclaiming an instance, but the length of that window varies a lot by provider and isn't safe to assume: AWS EC2 Spot gives a fixed two-minute notice, while Google Cloud's and Azure's default Spot/preemptible notice is far shorter, as little as zero to thirty seconds unless you explicitly opt into a longer notice window on the platforms that offer one. Design the shutdown handler for the shortest window your actual provider guarantees, not for the longest one any provider offers, since assuming a two-minute AWS-style window on a platform that only gives thirty seconds will leave in-flight work unfinished when the instance is actually reclaimed. A small agent on the instance watches for that signal and triggers an orderly shutdown: stop accepting new work, finish or checkpoint what's in flight, deregister from the load balancer, then exit.
- Diversify instance types and availability zones. Spot capacity availability and reclaim risk vary independently across instance types and zones; spreading a fleet across several types and zones means a capacity crunch in one pool doesn't take out the whole fleet simultaneously.
- Keep a floor of on-demand (or reserved) capacity for anything that must never fully disappear. A pure spot fleet can, in the worst case, be reclaimed almost entirely at once if the provider needs that capacity broadly; a baseline of guaranteed capacity underneath the spot layer keeps the service alive even in that scenario.
- Make retries idempotent. If a unit of work is re-dispatched after an interruption, running it twice (once partially, once fully) must produce the same correct result as running it once, otherwise interruption doesn't just cost time, it corrupts output.
Worked example
A video-rendering batch pipeline processes 10,000 independent frame-ranges, each taking about 3 minutes on one spot instance:
- Each frame-range is submitted as a separate job to a queue, not one 500-hour job for the whole batch.
- A worker pulls one job, renders it, and writes the output to durable object storage under a deterministic, content-addressed key (say, keyed by the input range and a version hash) before acknowledging the job as complete.
- If the instance is reclaimed mid-render, the job's visibility timeout expires unacknowledged, the queue makes it available again, and a different worker picks it up and re-renders it. Because the output key is deterministic, re-rendering the same range twice (once by the interrupted worker, if it partially wrote output before being killed, and once by the replacement worker) either overwrites the same key harmlessly or is caught by a "does this key already exist" check before starting, so no partial or duplicate output survives.
- The blast radius of losing any one instance is bounded to at most one 3-minute unit of work, not the whole 500-hour job, which is the entire point of choosing a small unit size.
Trade-offs and pitfalls
- Where spot goes wrong: putting a stateful primary (a database, a message broker holding unacknowledged state only it has) on spot capacity. Losing that instance doesn't cost you a retry, it costs you the state itself, unless it's independently replicated elsewhere, which defeats the cost savings if you now need extra replicas just to make spot safe.
- A subtler pitfall: sizing the on-demand floor too thin. Teams sometimes run "mostly spot with a token on-demand instance," which technically has a floor but not enough capacity to serve real traffic if a large fraction of spot capacity is reclaimed at once during a genuine capacity crunch.
- The real trade-off being made: significant cost savings in exchange for engineering effort spent making work interruptible, checkpointed, and idempotent. That engineering cost is often paid once and amortized across every future workload you run on spot, so it's worth investing in the primitives (a resilient job queue, a checkpointing convention, idempotent writes) rather than solving it ad hoc per workload.
A small team needs to implement an event-driven ingestion pipeline that receives occasional webhooks (hundreds per hour) and writes them into object storage. Justify choosing serverless functions over a small always-on container service. Discuss cost at low and burst traffic, latency, concurrency limits, retries, and operational burden.
Sample Answer
Direct answer
For a small team ingesting hundreds of webhooks an hour and writing them to object storage, serverless functions are the right default. You pay for milliseconds of actual compute instead of a server that sits idle between events, you get a built-in HTTPS endpoint and automatic scaling without provisioning anything, and the operational load (patching, capacity planning, process supervision) mostly disappears. The one real cost is a small amount of extra latency from occasional cold starts, which almost never matters for a workload where nothing is waiting on an interactive response.
How each factor plays out
Cost shape. An always-on container (say a small AWS Fargate task, or an EC2 instance) is billed for every hour it exists, whether a webhook arrives that hour or not. A serverless function is billed per invocation and per unit of memory-time actually consumed. At hundreds of events an hour, the container is paying for thousands of idle minutes for every minute of real work; the function pays for the real work only. This gap only narrows as sustained throughput climbs into a much higher range than "hundreds per hour" (see the worked example for the actual numbers).
Latency. A function that has not run recently has to spin up a fresh execution environment: download the code, start the runtime, run any top-level initialization, before it can handle the request. This is what people mean by a "cold start." It adds real but bounded latency, and it happens more often for a bursty, low-frequency workload like this one, because the function goes idle between webhooks. An always-on container has no equivalent to this; it is already running. What matters is whether the caller cares: a webhook sender is not a human waiting on a page to render, and most webhook providers (payment processors, source-control platforms, and so on) tolerate several seconds of response time before they consider a delivery failed. Cold start latency is a real line item here, it is just not the deciding one.
Concurrency limits. Serverless platforms cap how many invocations of a function can run at the same instant, shared across everything in the account and region unless you carve out a reservation. On a major cloud provider this default sits at four figures (low thousands), and it is a soft limit you can ask to raise. Hundreds of webhooks an hour is, at most, a small number of concurrent invocations even if they arrive in a tight cluster, so this ceiling is nowhere near this workload; it becomes a real design constraint only at throughput several orders of magnitude higher, which is a different problem than the one described here.
Retries. This is where teams most often get the architecture wrong. A function invoked directly and immediately, the way a raw HTTPS endpoint works, does not get the platform's automatic retry behavior; that behavior applies only to invocations routed through an event source in between. If the webhook points straight at the function's HTTPS endpoint and the write to object storage fails partway through, the ingestion pipeline is relying entirely on the sending system's own retry policy, which you do not control and which varies by provider. The fix is to decouple: have the function do almost nothing except validate the payload and drop it onto a managed queue, then let a second function (or the same one, triggered by the queue) do the actual write to object storage. Queue-triggered invocations come with configurable retry attempts and a dead-letter queue (a place failed messages land after repeated retries, so a bad payload is captured for inspection instead of silently dropped) essentially for free.
Operational burden. With the container path, a small team owns: keeping the host OS and container image patched, a process supervisor (systemd, or the container platform's own restart policy) to bring the process back after a crash, log rotation so disk does not fill up, and capacity planning if traffic ever grows. None of that exists on the serverless side: there is no host to patch, no process to keep alive, and scaling is automatic. What you take on instead is smaller and mostly one-time: configuring the event source (the queue, and the trigger from it), a sensible function timeout, and enough memory that the write completes before that timeout. For a two-person team, that trade is usually the whole argument by itself.
Worked example: what this actually costs
Assume a realistic shape for this workload: 500 webhooks an hour on a normal day (mid-range of "hundreds"), each invocation configured with 256 MB of memory and taking about 300 ms to parse the payload and write it to object storage. Using a 30-day month for round numbers:
- Monthly invocations:
500/hr x 24h x 30d = 360,000 - Memory-time used: a function's compute is billed in GB-seconds (memory allocated, in GB, multiplied by execution time, in seconds). Here that is
0.25 GB x 0.3s = 0.075 GB-sper invocation, or360,000 x 0.075 = 27,000 GB-sfor the month. - A major serverless platform's current published pricing includes a monthly free allowance of 1,000,000 requests and 400,000 GB-seconds, at $0.20 per million requests and roughly $0.0000167 per GB-second beyond that. Both 360,000 requests and 27,000 GB-s fall inside the free allowance, so the steady-state bill is $0.00/month.
- Now stress-test that against a sustained 10x burst for the entire month (an unrealistic worst case, but a useful upper bound): 5,000/hr x 24h x 30d = 3,600,000 invocations, 270,000 GB-s. GB-seconds still fits the free tier. Requests exceed it by
3,600,000 - 1,000,000 = 2,600,000, costing2,600,000 / 1,000,000 x $0.20 = $0.52. So even at ten times the stated volume, sustained for a full month, the bill is $0.52/month.
Now price the always-on alternative: a small container task sized at 0.25 vCPU and 0.5 GB memory, running all 730 hours in a month regardless of traffic, at current published on-demand rates (roughly $0.0405 per vCPU-hour and $0.0044 per GB-hour):
- Compute:
0.25 x $0.0405 + 0.5 x $0.0044 = $0.0123/hour, or about $9.00/month, whether or not a single webhook arrives. - To receive webhooks over HTTPS with proper TLS termination and health-based failover, that task typically sits behind a load balancer, adding a published base charge of about $0.0225/hour, or roughly $16.43/month, before any usage-based charges on top of that.
- Realistic floor for the container path: about $25/month, fixed, before processing a single event.
The container's cost does not move with traffic; the function's cost tracks traffic almost exactly and stays near zero at this volume. That asymmetry, not a vague preference for "serverless," is the actual justification.
Trade-offs and pitfalls
The recommendation flips well before you reach a webhook-scale workload if the service needs to hold a long-lived connection (a persistent websocket, a database connection pool you want to keep warm across requests), needs full control over the OS or runtime for compliance reasons, or if sustained throughput climbs high enough that the per-invocation cost overtakes a right-sized reserved instance, which is a different calculation than the one above and needs its own model once you are there. The common wrong turn on the serverless side is pointing the webhook directly at the function and calling that "handled": without a queue in between, a transient failure in the write to object storage is silently swallowed unless the sender happens to retry, which is a bet on someone else's system rather than a designed guarantee.
Unlock Full Question Bank
Get access to all 7 Cloud Compute Options and Trade-offs interview questions and detailed answers.
Sign in to ContinueJoin thousands of developers preparing for their dream job.