Cloud Cost Optimization and FinOps Questions
Controlling and optimizing cloud spend: cost modeling and forecasting, rightsizing, reserved capacity and savings plans, autoscaling for cost, tagging and chargeback, and the FinOps operating model. Covers building the business justification for infrastructure spend and continuously driving efficiency at scale without sacrificing reliability. Cost as a first-class architectural concern.
Design a minimal tagging taxonomy to support cost allocation across teams and environments. What tags would you make mandatory, how would you enforce them at resource creation, and how would you retrofit tagging onto existing untagged resources without disrupting teams?
Sample Answer
Direct answer
A minimal tagging taxonomy needs four mandatory keys: a cost-center or business-unit identifier (who pays), an environment tag (prod/stage/dev), a service or application identifier (what it is), and an owner (who to contact). Enforce them at resource creation with a policy-as-code guardrail that blocks the create call rather than a report that flags it afterward, and retrofit existing resources through an inventory-and-backfill pass that defaults to a safe, auditable guess rather than blocking teams from their own infrastructure while the backfill runs.
Structured elaboration
The mandatory tag set, kept deliberately small
cost-center: the budget this resource bills against. This is the one tag cost allocation cannot function without.environment: prod, staging, dev, or test, from a fixed enum. This is what lets you separate "waste" from "intentional dev sandbox spend" in any report.service(orapplication): a stable identifier for what the resource belongs to, ideally matching a service catalog or repo name so it survives reorganizations.owner: a team alias or distribution list, not an individual, so the tag doesn't go stale the moment someone changes teams.
Keep the mandatory set to these four. Every additional mandatory tag increases the friction of resource creation and the number of ways a resource can end up non-compliant; optional tags (project code, data classification) can be layered on for teams that need them without blocking everyone else.
Enforcing at resource creation
The most reliable enforcement point is admission, not audit: a policy-as-code check wired into the resource-creation path (through the infrastructure-as-code pipeline or a cloud-native guardrail like AWS Service Control Policies, Azure Policy, or GCP Organization Policy) that rejects a create request missing a mandatory tag. Writing the actual policy rules is its own discipline; the FinOps-relevant decision is which four tags are worth that friction, and where in the pipeline the gate sits, early enough to block bad tags cheaply, but not so early it blocks legitimate emergency provisioning. A continuous drift-detection scan (using the cloud provider's config/audit service) is a necessary second layer, because IaC gates don't cover every path a resource can be created through (console clicks, an old script, a partner integration).
Retrofitting untagged resources without disrupting teams
- Inventory first: run a read-only scan across accounts to find every untagged or partially-tagged resource, grouped by service and account, before touching anything.
- Auto-fill what's inferable: an account already dedicated to one team's workload can safely inherit
cost-centerandownerfrom the account-level metadata; apply this in a dry-run mode first so someone can sanity-check the inferred values before they go live. - Route the ambiguous remainder to owners with a short SLA (for example, two weeks) to self-tag, with a clearly-labeled default (
owner: unassigned,cost-center: unallocated) applied automatically if the SLA lapses, so allocation reporting still runs even on the stragglers. - Apply tags without triggering resource recreation wherever the cloud API supports in-place tagging (most compute, storage, and database resources do); reserve any disruptive change (recreation, migration) for the rare resource type that requires it, and schedule those explicitly with the owning team instead of bulk-applying them.
- Close the loop by updating the IaC templates that created the now-tagged resources, so the next deploy doesn't regenerate them untagged, and add the resource to the same admission gate that covers new resources.
Worked example
An organization runs 1,200 resources across its accounts and discovers, via the inventory scan, that 340 (28%) are untagged, concentrated in an older account that predates the tagging policy. Of those 340, 260 map cleanly to a single team by account ownership and get cost-center and owner auto-filled in dry-run, then confirmed and applied. The remaining 80 are ambiguous (a shared account used by two teams historically) and go out with a two-week SLA; 55 get claimed, and the remaining 25 default to owner: unassigned, cost-center: unallocated so they still show up in cost reports as a known, trackable "needs owner" bucket rather than disappearing into unattributed spend. After the retrofit, tagged-resource coverage moves from 72% to over 97%, and the 3% remainder is a visible, bounded backlog instead of a silent gap.
Trade-offs and pitfalls
Making the mandatory set too large is the most common design mistake: every extra required tag is another way for a legitimate deploy to get blocked, and teams route around overly strict gates by disabling the check rather than complying with it. Blocking resource creation hard, with no override path, is the second: real incidents need an emergency provisioning path, and a tagging gate with no break-glass exception will get bypassed entirely rather than respected. On retrofit, the failure to avoid is defaulting silently: an auto-applied guess that's never surfaced for review can quietly misattribute a team's spend for months, which is worse for trust in the allocation data than an honest unassigned bucket.
How would you set up a basic cost anomaly detection system that alerts when a team's weekly spend deviates materially from normal? What data sources and metrics would you ingest, what's a simple first detection rule, and how would you avoid drowning the team in noisy alerts?
Sample Answer
Direct answer
A basic weekly cost anomaly detector needs three things: daily billing data broken down by team and service, a simple statistical baseline (a rolling median works better than a rolling average for this), and a threshold that requires both a large percentage move and a large absolute dollar move before it pages anyone, so a small team's routine variance doesn't generate the same alert as a large team's genuine spike.
Structured elaboration
Data sources and metrics to ingest:
- Daily billing line items from the cloud provider's cost and usage data, not monthly, since daily granularity is what lets you catch a spike before the invoice lands.
- Tag or label mappings so every dollar of spend attributes cleanly to a team, project, and environment. Without reliable tagging, "which team's spend spiked" becomes a manual investigation instead of an automated alert.
- A calendar of known events (planned migrations, release windows, seasonal traffic events) so the detector can tell "we deliberately scaled up" apart from "something is wrong."
A simple first detection rule:
- Compute each team's total spend for the current week.
- Maintain a rolling baseline: the median of that team's weekly spend over the past 8 to 12 weeks. Median rather than mean matters here because a single earlier spike shouldn't drag the baseline up and make the detector blind to a second one.
- Compute the percentage deviation from that baseline.
- Flag an anomaly only if the deviation exceeds a percentage threshold (for example, 50%) and the absolute dollar change exceeds a minimum floor (for example, $1,000). Requiring both conditions is what keeps a team with a $200 baseline from generating the same noisy alert as a team with a $2 million baseline moving by the same percentage.
Keeping the team from drowning in noise:
- Use a robust spread measure like the median absolute deviation instead of standard deviation to size the threshold, since a handful of past outliers otherwise widen the "normal" band and make the detector less sensitive exactly when it should be more sensitive.
- Require the deviation to persist for more than a single day before alerting on a weekly view, so a one-day billing artifact (a delayed invoice line landing all at once) doesn't trigger a page.
- Suppress alerts during a known, calendar-declared event (a planned migration, a load test) rather than making every planned cost increase look identical to an unplanned one.
- Let the team that receives an alert mark it as a false positive, and feed that back into tuning the threshold. A detector that's never allowed to be wrong in a documented way just gets muted instead.
- Tier the alerts: a moderate deviation goes to a low-urgency channel (a message, not a page), and only the largest, most sustained deviations page someone directly.
Worked example
A team's baseline (median of the last 10 weeks) is $8,000 a week. This week they spend $13,500. The deviation is (13,500−8,000)/8,000=68.75%, which clears the 50% threshold, and the absolute change is $5,500, which clears the $1,000 floor, so this fires as an anomaly. Compare that to a small team with an $800 baseline that spends $1,300 this week: the deviation is also over 50% (62.5%), but the absolute change is only $500, below the floor, so it doesn't page anyone, it just shows up on the weekly dashboard for someone to glance at when convenient. That's the point of the two-condition rule: it protects small teams from noisy pages while still surfacing genuinely large moves.
Trade-offs and pitfalls
- A percentage-only threshold looks reasonable until you apply it to a team with a tiny baseline, where normal week-to-week noise routinely exceeds 50%. The dollar floor is what prevents that class of false positive, and it's easy to forget when first designing the rule.
- A rolling average baseline (instead of median) means one real spike stays baked into "normal" for weeks afterward, quietly raising the bar for detecting the next one. This is a common and subtle mistake worth catching in review.
- Daily granularity catches problems faster than weekly but is noisier; weekly smooths noise but means you find out up to six days later. A reasonable middle ground is a daily check against a weekly baseline, which is what the worked example above effectively does.
- The detector is only as good as tag coverage. If a meaningful share of spend is untagged or mis-tagged, anomalies in that bucket are invisible to a team-scoped detector, which is itself worth surfacing as its own metric to track down separately.
For a throughput-oriented service that's moderately stateful, how would you decide between covering it with reserved instances or savings plans versus mixing in spot instances with on-demand? What would you need to assume about utilization and interruption rates, and how would you validate the chosen mix safely before committing to it at scale?
Sample Answer
Direct answer
The decision comes down to how expensive an interruption actually is for this specific service. A reserved instance or Savings Plan (SP) covers a guaranteed baseline at a discount with zero interruption risk; spot buys a deeper discount in exchange for eviction risk. For a moderately stateful, throughput-oriented service, the right structure is usually a reserved or Savings Plan floor sized to the steady minimum load, so core capacity never carries interruption risk, plus spot covering the elastic portion above that floor, with on-demand as the fallback when spot capacity isn't available, validated incrementally rather than committed to at full scale on day one.
Structured elaboration
What you need to know before deciding
- Baseline and peak utilization, so you know how much load is genuinely steady versus how much is elastic.
- Historical interruption rate for the specific instance family and region you'd run spot on; this is workload-specific and should be measured, not assumed from a generic industry figure.
- Mean time to recovery (MTTR): how long it takes the service to recover from an interruption, and what that recovery actually costs, in replayed work, extra network or storage I/O (input/output), or a brief latency hit.
- The specific nature of the statefulness: session affinity, whether writes go through a write-ahead log, how frequently the service checkpoints. "Moderately stateful" is doing a lot of work in this question; a service that checkpoints every few seconds tolerates interruption very differently from one that holds long-lived in-memory session state.
Portfolio design
Monthlymix=FloorHours×ReservedRate+ElasticHours×(1+RetryOverhead)×SpotRate
Size the floor to the steady minimum load the service never drops below, covered by reserved capacity or a Savings Plan so it's never at interruption risk. Size the elastic band to the variable load above that floor, covered by spot, with on-demand as a last-resort fallback when spot capacity isn't available in the target instance family or region. This mirrors the same floor-plus-elastic-band logic that applies to a large batch-job fleet: there, the floor is whatever backlog has to clear even in a slow week, and the elastic band is burst capacity, which is a much easier target for spot than a live stateful service because batch jobs tolerate restarts far more cheaply.
Validating the mix before committing at scale
- Canary a small percentage of production traffic onto the mixed fleet first, with autoscaling and an on-demand fallback path already wired up, rather than assuming the mix works and finding out otherwise in production.
- Run controlled interruption tests (deliberately terminating spot capacity in the canary) to validate the MTTR and recovery-cost assumptions against reality, not just the historical interruption-rate figure.
- Track cost per month, tail latency (P99, 99th percentile), error rate, and any lost or replayed work as the canary scales up, and only widen the spot percentage once those metrics hold at each step.
Worked example
Suppose the service needs a steady floor of 600 instance-hours/month plus an elastic band averaging 400 instance-hours/month on top, for 1,000 hours/month total. On-demand costs $0.10/hour, a 1-year reserved commitment (amortized) costs $0.06/hour, and spot costs $0.02/hour. Because this service is stateful (checkpoint and replay cost money, unlike a stateless batch job), assume a higher interruption-driven retry overhead than a purely stateless workload, 15%, based on the higher end of a typical measured range for this kind of workload.
Option A, all reserved:
1,000 hrs×$0.06=$60.00 per month
Zero interruption risk, but paying for the full 1,000-hour floor at all times even though only 600 of it is steady load.
Option B, floor plus elastic mix:
Floor:600×$0.06=$36.00
Elastic band with 15% retry overhead:400×1.15=460 effective hours,460×$0.02=$9.20
Total:$36.00+$9.20=$45.20 per month
Savings:
$60.00−$45.20=$14.80 per month,60.0014.80≈24.7% cheaper than covering the whole workload with reserved capacity
That saving comes in exchange for accepting eviction risk on the 400-hour elastic band, plus whatever operational cost comes from handling those interruptions gracefully, which isn't captured in this dollar figure and has to be validated separately through the canary process above.
Trade-offs and pitfalls
- Sizing the floor too small exposes steady-state load to interruption risk it shouldn't have to carry. This is the specific mistake the "moderately stateful" framing in the question is pointing at: a stateful service often can't just retry cheaply, so the floor needs to protect whatever load genuinely can't tolerate an interruption.
- Sizing the floor too large gives up savings a fault-tolerant elastic band could have captured, effectively turning the whole workload back into option A without admitting it.
- Skipping the incremental validation step means finding out the MTTR assumption was wrong in production, at full scale, instead of in a controlled canary test where the blast radius is small.
- Reusing a generic industry interruption-rate figure instead of measuring your own misprices the whole decision, since interruption rates vary meaningfully by instance family, region, and time.
Compare reserved instances, savings plans, and committed-use discounts across the major cloud providers. What is the mechanical difference between them in commitment scope, term, and flexibility across instance types, and how would you decide what percentage of a steady-state workload's capacity to commit?
Sample Answer
Direct answer
All three mechanisms trade a usage commitment for a lower price, but they commit to different things: AWS Reserved Instances (RIs) commit to a specific instance configuration, AWS Savings Plans commit to a dollar-per-hour spend level that flexes across instance types, Google Cloud committed-use discounts (CUDs) commit to either a resource quantity or a dollar-per-hour spend depending on which CUD type you buy, and Azure Reservations commit to a specific VM configuration similar to AWS RIs. The general pattern across every provider is the same trade-off: the more precisely you commit to a specific instance shape, the bigger the discount; the more flexibility you keep, the smaller the discount but the lower your risk if the workload changes shape.
Structured elaboration
Mechanism comparison
| Mechanism | Provider | Commits to | Term | Flexibility |
|---|---|---|---|---|
| Standard Reserved Instance | AWS | Specific instance family, size, region | 1 or 3 yr | Least flexible: can change availability zone and, within limits, instance size in the same family, but not family or OS |
| Convertible Reserved Instance | AWS | Instance family (exchangeable) | 1 or 3 yr | Can exchange for a different family, size, or OS during the term, at a lower discount than Standard |
| Compute Savings Plan | AWS | Dollar-per-hour compute spend | 1 or 3 yr | Most flexible: applies across instance family, size, OS, tenancy, and region, and across EC2, Fargate, and Lambda |
| EC2 Instance Savings Plan | AWS | Dollar-per-hour spend, locked to one instance family and region | 1 or 3 yr | Flexible on size and OS within that family and region only; typically a larger discount than Compute Savings Plans for the same term because it's narrower |
| Resource-based CUD | Google Cloud | A quantity of vCPUs, memory, GPUs, or similar, on Compute Engine | Typically 1 or 3 yr | Locked to the committed resource type and quantity; scope can be a single project or shared across a billing account |
| Flexible (spend-based) CUD | Google Cloud | Dollar-per-hour spend | 1 or 3 yr | Pools eligible spend across only three services, Compute Engine, Google Kubernetes Engine (GKE), and Cloud Run, similar in spirit to an AWS Compute Savings Plan in that the discount follows a dollar-per-hour spend level rather than a specific SKU. BigQuery and Cloud SQL are NOT part of this pool: each has its own separate, service-specific spend-based commitment, purchased and applied independently |
| Reserved VM Instance | Azure | Specific VM series, size, and region | 1 or 3 yr | Instance-size flexibility within the same VM size-flexibility group; can be rescoped after purchase to a subscription, resource group, shared billing scope, or management group without a new commercial transaction |
All four providers offer some form of upfront, partial-upfront, or no-upfront (pay monthly) payment on these commitments at the same total cost, so the payment option is a cash-flow decision, not a discount-size decision on most of these products.
Why the scope difference matters in practice
A resource-level commitment (Standard RI, resource-based CUD, Azure Reservation) only pays off if the workload keeps needing that exact shape for the whole term; if the team migrates to a different instance family six months in, the commitment sits partially wasted (though AWS and Azure both allow some exchange or resale mechanisms to recover part of that). A spend-based commitment (Compute Savings Plan, flexible CUD) survives an instance-family change automatically, because the discount is applied to dollars spent on eligible usage, not to a specific SKU, at the cost of a somewhat smaller discount than the narrowest resource-level option.
Deciding what percentage of steady-state capacity to commit
Start from the floor, not the average: pull 3 to 6 months of utilization history for the workload, and find the usage level that held true on the worst week, not the typical week. That floor, not the mean, is the safe commitment baseline, because a commitment above the actual steady floor pays for idle capacity on every low-usage day. From there, the commitment size is a risk trade-off, not a fixed rule: a stable, mature workload with a long recent history of holding above that floor supports committing close to the full floor, while a workload still changing shape (recent re-architecture, aggressive growth, planned migration) justifies leaving more of the floor on-demand or covering it with a flexible, spend-based commitment instead of a rigid resource-level one, specifically because the risk being managed is "commitment outlives the workload's actual shape," not "commitment size in the abstract."
Worked example
A team's steady-state EC2 fleet held at a minimum of 40 instances of a given family over the last 4 months, with normal weekday peaks around 55 and occasional bursts to 70. The 40-instance floor is the commitment candidate, not the 55-instance average and not the 70-instance peak: committing at 55 would mean paying the commitment rate for capacity that isn't reliably used on quieter days, and any spike above 40 (up to and including the 70-instance bursts) is served by on-demand or spot capacity regardless of the commitment size. If this workload is expected to stay on the same instance family for the full term, an EC2 Instance Savings Plan or Standard RI sized to 40 instances captures the largest discount available on that stable floor; if a re-platforming project is likely to change instance family within the year, a Compute Savings Plan sized to the equivalent dollar-per-hour spend protects the same floor's discount while surviving the family change.
Trade-offs and pitfalls
The most common mistake is committing to the peak or the average instead of the floor, which either overpays for capacity that isn't reliably used or, worse, sizes a "safe" commitment so conservatively it captures almost none of the available discount. The second is choosing the narrowest, highest-discount resource-level commitment for a workload that's still changing shape, and then discovering the commitment doesn't match the new instance family, wasting real money for the rest of the term. The third, specific to the flexible/spend-based products, is assuming "flexible" means "no attention needed": a spend-based commitment still needs the underlying usage to stay above the committed dollar level, or the unused portion is still paid for and simply not applied to any usage.
Your client's monthly cloud bill jumped about 40% versus the prior month. Walk me through your investigation from first hypothesis to root cause: what data you'd pull first, billing export, resource tags, deployment history, monitoring telemetry, how you'd narrow it down, and what you'd do to stop further unexpected spend while you're still investigating.
Sample Answer
Direct answer
I'd start by pulling the itemized billing export (AWS's Cost and Usage Report, or the equivalent from Azure or Google Cloud) rather than the headline invoice total, break the delta down by service, region, and tag, and look for whether the jump is broad (many services up a little) or concentrated (one or two line items driving most of it), because that single split determines whether I'm hunting for a config change or a runaway process. In parallel, I'd put an emergency budget alert and a hard cap or throttle in front of the likeliest culprit so I'm not still bleeding money while I investigate, since stopping the bleeding and finding the root cause are two different, simultaneously-runnable tasks, not a sequence.
Structured elaboration
Step 1: Scope the delta before hypothesizing about the cause
Pull the granular billing export for both months and diff them by service, region, account or project, and tag. A 40% jump could be one resource type up 400% or every service up a proportional amount, and those point to completely different investigations, so this diff is the first thing to run, before forming any hypothesis about root cause.
Step 2: Narrow to the resources actually driving the delta
Match the billing line items with the largest deltas back to specific resource IDs and tags. Look specifically for: new or unusually large resource counts, a spike in a service that's normally near-zero for this account, unattached storage volumes that shouldn't exist, or a jump in data-transfer line items, since egress spikes are a common and easy-to-miss cause of a sudden bill jump.
Step 3: Correlate against what changed operationally, in the same window
Cross-reference the spike window against deployment history and CI/CD logs (did a release ship right before the jump started), autoscaling events (did a scaling policy misfire and hold capacity high), and application/infrastructure logs for anything that looks like a runaway job or a misconfigured batch process. This is where "a new deploy shipped a debug flag that left instances scaled up" or "a scheduled job got triggered twice" typically surfaces.
Step 4: Form and test a specific hypothesis
By this point there's usually a short list of candidates (a specific deploy, a specific job, a specific resource type). Validate each by reproducing the cost against the actual usage: does the resource's usage-hours or data-scanned metric, for the spike window, actually account for the dollar delta observed. A hypothesis that doesn't reproduce the dollar amount isn't the (whole) root cause, and the investigation isn't done until the numbers actually add up.
Step 5: Stop further unexpected spend while still investigating
This runs in parallel with steps 1 through 4, not after them: set an emergency budget alert on the affected account or service immediately, and for the most likely culprit, apply a targeted control (scale the autoscaling group's max down to a safe ceiling, pause the suspect job, revoke a deployment credential if a bad deploy is implicated) rather than a blanket freeze that would stop legitimate traffic too. The goal is to bound the damage on the specific thing under suspicion without causing a second incident by shutting down something that turns out to be unrelated.
Worked example
A team's monthly bill jumps from a baseline of $42,000 to $58,800, a 40% increase, or $16,800 in absolute terms. The billing-export diff shows the increase isn't broad: EC2 (Amazon Elastic Compute Cloud) compute is up only 4%, but a single service, a managed data-warehouse query engine, is up $11,200, and S3 (Amazon Simple Storage Service) data-transfer/egress charges are up $4,100, together accounting for $15,300 of the $16,800 delta, with the remaining $1,500 spread thinly across normal month-over-month growth.
Matching the query-engine spike to usage shows a single ad-hoc analytics query scanned roughly 2 TB of data three separate times during the window, run by an analyst testing a new dashboard against a table that wasn't partitioned the way they assumed, at a rate where scanning that much unpartitioned data repeatedly accounts for the bulk of the $11,200. The egress increase correlates with a deploy that changed a batch export job's destination from an in-region bucket to a bucket in a different region, adding a per-GB cross-region transfer charge that hadn't existed before that release.
Root cause: two independent, unrelated changes landing in the same billing period, an expensive unpartitioned ad-hoc query pattern and a misconfigured cross-region export destination, together explaining $15,300 of the $16,800 (91%) increase, with the remainder attributable to normal growth.
Trade-offs and pitfalls
The most common mistake is jumping straight to a hypothesis (usually "it's autoscaling" or "it's a new feature") before actually running the billing diff, which wastes time chasing a plausible-sounding cause that the data doesn't support. A second is applying a blanket spend freeze while investigating, which stops the bleeding but can also take down legitimate production traffic, turning a cost incident into an availability incident. A third is declaring victory once you've found a cause that "sounds right" without checking that the dollar amount it explains actually adds up to the observed delta. As this example shows, a real spike is often more than one thing happening at once, and stopping the investigation after finding the first plausible cause can leave a second, still-active cost driver running unnoticed.
Unlock Full Question Bank
Get access to all 25 Cloud Cost Optimization and FinOps interview questions and detailed answers.
Sign in to ContinueJoin thousands of developers preparing for their dream job.