All roles

Cloud Engineer interview

You probably know
more than you can
say out loud.

Interviews are not a memory test. They are a framing test. Drag the dial and watch the same answer go from forgettable to hired, without adding a single new fact.

Free, no sign-up 336 questions inside

A real interview question

What is the difference between IaaS, PaaS, and SaaS? Give an example of each.

What most people say

drag me

IaaS is infrastructure, PaaS is platform, SaaS is software.

It just expands the acronyms without the idea behind them, the shared-responsibility spectrum, and gives no examples or trade-off. It tells the interviewer you memorized a slide, not that you understand it.

Have a cloud engineer interview coming up?

Tell us when. We will check in once afterwards and ask what they actually asked you, so the next person walks in better prepared than you did.

Browse all 336 cloud engineer questions

The complete bank, grouped by topic, with the full rubric for every question. Free, no sign-up.

Cloud fundamentals

1 question · Foundation
Foundation

What is the difference between IaaS, PaaS, and SaaS? Give an example of each.

What most people say

IaaS is infrastructure, PaaS is platform, SaaS is software.

It just expands the acronyms without the idea behind them, the shared-responsibility spectrum, and gives no examples or trade-off. It tells the interviewer you memorized a slide, not that you understand it.

The structure behind a strong answer

  1. 1

    Frame it as a spectrum of responsibility. The higher you go, the less you manage and the less you control. That single idea answers the whole question.

  2. 2

    IaaS = raw infrastructure. You get VMs/network/storage; you manage the OS up. Example: EC2, Azure VMs.

  3. 3

    PaaS = managed runtime. You deploy code; the platform runs the OS, scaling, patching. Example: App Engine, Azure App Service, RDS.

  4. 4

    SaaS = finished product. You just use it; the vendor runs everything. Example: Gmail, Salesforce.

  5. 5

    Name the trade-off. More control and flexibility lower down; less ops burden and faster delivery higher up. You pick based on how much undifferentiated heavy lifting you want to own.

What gets you hired

They're points on a shared-responsibility spectrum. With IaaS like EC2 you get raw VMs and manage everything from the OS up, maximum control, maximum ops. PaaS like RDS or App Service runs the OS, patching, and scaling for you, so you just ship code. SaaS like Gmail is a finished product you only consume. The trade-off is control versus operational burden: lower for flexibility, higher to move fast and stop maintaining undifferentiated infrastructure.

Leads with the responsibility spectrum Gives a concrete example per layer Names the control-vs-ops trade-off

Then they probe: Where do containers and Kubernetes fit on that spectrum?

Practise this one

Reliability

16 questions · Foundation, Junior, Mid, Senior
Foundation

What is a Region versus an Availability Zone, and why does the difference matter?

What most people say

A region is a bigger area and an availability zone is a smaller one inside it.

Geographically true but misses the entire point: AZs are independent failure domains, and that independence is why you spread across them. No connection to a reliability decision.

The structure behind a strong answer

  1. 1

    Region = a geographic location. A separate part of the world (e.g. eu-west-1), chosen for latency to users, data-residency law, and service availability.

  2. 2

    AZ = an isolated datacenter within a region. One or more datacenters with independent power, cooling, and networking, close enough for low-latency sync replication but isolated so one failing does not take the others down.

  3. 3

    Tie it to a failure domain. An AZ is the unit of isolated failure. Spreading across AZs survives a datacenter outage; spreading across regions survives a whole-region event or meets data-residency needs.

  4. 4

    State the default. Production runs multi-AZ by default; multi-region is added for the highest tiers (DR, global latency) because it costs real complexity.

What gets you hired

A region is a geographic location like eu-west-1, chosen for user latency, data-residency law, and which services are offered there. Inside it are Availability Zones, isolated datacenters with their own power, cooling, and network, far enough apart to fail independently but close enough for low-latency replication. The AZ is your unit of isolated failure: run multi-AZ so a datacenter outage does not take you down, and add multi-region only for DR or global latency, because it brings real complexity and cost.

Defines AZ as an independent failure domain Connects to a multi-AZ/multi-region decision Knows multi-AZ is the production default

Then they probe: Why not just run everything across multiple regions for max safety?

Practise this one
Foundation

What is the difference between vertical scaling and horizontal scaling, and when would you reach for each?

What most people say

Vertical is making the server bigger and horizontal is adding more servers, and horizontal is always better.

It parrots the definition but treats horizontal as a blanket win. A senior knows stateful systems and databases often scale up first, and that there are real trade-offs.

The structure behind a strong answer

  1. 1

    Define both plainly. Vertical scaling means giving one machine more CPU, memory, or disk. Horizontal scaling means adding more machines that share the load.

  2. 2

    Name the ceiling. Vertical scaling hits a hard limit because the biggest instance is a fixed size, and it usually needs a restart or downtime to resize.

  3. 3

    Explain the cloud bias. Cloud platforms favor horizontal scaling because you can add and remove identical instances on demand behind a load balancer.

  4. 4

    Tie to state. Horizontal scaling works cleanly when the workload is stateless, so any instance can serve any request. Stateful workloads make it harder.

  5. 5

    Give the rule of thumb. Scale up for quick wins or for things that cannot be split, like some databases. Scale out for web and app tiers that need to grow without a ceiling.

What gets you hired

Vertical scaling is making one machine more powerful, going from say 4 vCPUs and 16 gigs of memory to 16 vCPUs and 64. Horizontal scaling is adding more machines behind a load balancer so they share the work. Vertical is simple, but it has a hard ceiling at the largest instance type the provider sells, and a resize usually means a stop and start, so I am taking a couple of minutes of downtime. I treat it as a quick fix. Horizontal is what the cloud is really built for. I can run an autoscaling group with a minimum of 3 instances and a maximum of 20, scaling out when average CPU crosses about 60 percent and back in when traffic drops. The catch is that scaling out only works smoothly if the tier is stateless, so any of those 20 instances can serve any request and sessions live in a shared cache rather than on the box. So for a web tier I scale out. For a single relational database I usually scale up first, because sharding is a big commitment. Then I add read replicas, which works well when the workload is read heavy, something like 10 reads for every write, and only shard when the write path itself is the ceiling.

name the ceiling on vertical scaling connect horizontal scaling to statelessness admit databases often scale up first

Then they probe: Why does horizontal scaling need the workload to be stateless?

Practise this one
Foundation

What does "highly available" actually mean, and how do you achieve it?

What most people say

High availability means running lots of servers so the site stays up.

More servers behind a single load balancer or one database is still a single point of failure. HA is specifically about eliminating SPOFs with redundancy across failure domains plus automatic failover, not raw server count.

The structure behind a strong answer

  1. 1

    No single point of failure. High availability means no single component can take the system down. You find every SPOF, the lone load balancer, database, or zone, and remove it.

  2. 2

    Redundancy. Run redundant instances of each tier (N+1 or more) so the loss of one is absorbed by the others, spread across failure domains like availability zones.

  3. 3

    Automatic failover. Detect failures with health checks and shift traffic or promote a standby automatically, fast, without a human in the loop.

  4. 4

    Avoid shared fate. Make sure the redundant copies do not share a hidden dependency (same zone, same config push, same database) that fails them all together.

What gets you hired

High availability really means the system keeps serving despite component failures, and the core mechanism is eliminating single points of failure. So I walk the architecture and find every SPOF, a lone load balancer, a single database, everything in one availability zone, and remove it. The tool is redundancy: run multiple instances of each tier, N+1 or more, and spread them across failure domains like availability zones so one zone going down does not take the service with it. Redundancy alone is not enough though, you need automatic failover: health checks detect a failed instance and traffic shifts to the healthy ones, or a standby is promoted, fast and without waiting for a human. And the subtle part that catches people is shared fate, redundant copies that secretly share a dependency, the same zone, the same config rollout, the same primary database, so they fail together. Real HA means the copies are genuinely independent. And you measure all this in nines of availability, which tells you how much redundancy and how fast a failover you actually need.

Defines HA as removing SPOFs Redundancy across failure domains + automatic failover Mentions shared fate / measured in nines

Then they probe: What is "shared fate" and why does it undermine redundancy?

Practise this one
Foundation

Why are stateless services easier to scale and make reliable than stateful ones?

What most people say

Stateless services do not store data so they are simpler.

It is on the right track but does not explain the consequence: because no instance holds state, any instance can serve any request and instances become disposable, which is what makes scaling and recovery easy. The why is missing.

The structure behind a strong answer

  1. 1

    Stateless: no local state. A stateless service keeps no client/session state on the instance; state lives in an external store (database, cache, session store).

  2. 2

    Any instance, any request. Because no instance is special, requests can go to any of them, which makes load balancing trivial and lets you add or remove instances freely.

  3. 3

    Disposable and recoverable. An instance can die or be replaced with no data loss, so recovery is just spinning up a fresh one, and autoscaling works cleanly.

  4. 4

    Stateful is harder. A stateful instance holds data locally, so you need sticky sessions or replication, losing one can lose data, and scaling and failover get much harder. Push state out where you can.

What gets you hired

A stateless service keeps no client or session state on the instance itself. The state lives in an external store, a database, a cache like Redis, or a session store. That 1 property has big consequences. Because no instance holds anything special, any request can be served by any instance, so load balancing is trivial and I can go from 3 pods to 30 during a traffic spike without worrying about where a user's state lives. It also makes instances disposable. One can crash or be replaced and nothing is lost, so recovery is just bringing up a fresh instance, which for us was under 30 seconds, and autoscaling works cleanly because scaling down does not strand any data. A stateful service is the opposite. It holds data locally, so I either need sticky sessions to pin a user to the same instance, or replication to copy state between instances. Losing an instance can mean losing data, and both scaling and failover get much harder because instances are not interchangeable. So the design rule I follow is to push state out of the compute tier into purpose-built stores, keep the request-handling services stateless, and confine the genuinely stateful parts, the databases and the queues, to systems that are actually designed to manage state with replication and failover.

Externalized state -> any instance serves any request Instances disposable/recoverable + clean autoscaling Knows stateful needs sticky sessions/replication

Then they probe: What problems do sticky sessions introduce?

Practise this one
Junior

How should a service retry a failed call to a dependency, and what is a retry storm?

What most people say

I would retry the request a few times until it succeeds.

Immediate, uncapped retries are exactly how a brief dependency blip becomes a retry storm: every client retries at once, multiplying load on an already-struggling service and keeping it down. You need backoff, jitter, caps, and idempotency.

The structure behind a strong answer

  1. 1

    Retry only the right things. Retry transient failures (timeouts, 503s) and only idempotent operations; do not retry a clear client error (4xx) or a non-idempotent write blindly.

  2. 2

    Exponential backoff. Wait increasingly longer between attempts (for example 1s, 2s, 4s) instead of hammering immediately, to give the dependency time to recover.

  3. 3

    Add jitter. Randomize the backoff so many clients do not retry in lockstep. Without jitter, synchronized retries hit the dependency in waves.

  4. 4

    Cap and combine. Cap the number of retries and the total time, and pair retries with a circuit breaker so you stop retrying a dependency that is clearly down.

What gets you hired

Retries are necessary for transient failures but dangerous if done naively. First, retry only the right things: transient errors like timeouts or 503s, and only operations that are safe to repeat, idempotent ones, because retrying a non-idempotent write can double-charge or duplicate data. Second, use exponential backoff, wait progressively longer between attempts, say one second, then two, then four, instead of immediately hammering, so the dependency gets room to recover. Third, and this is the part people forget, add jitter: randomize the backoff so all the clients do not retry in perfect lockstep, because synchronized retries arrive in waves and re-overload the service. Fourth, cap the retries and the total time, and pair retries with a circuit breaker so that once a dependency is clearly down, you stop retrying entirely and fail fast rather than piling on. The failure mode all of this prevents is a retry storm, or thundering herd: a dependency has a brief blip, every caller retries immediately and repeatedly, that retry traffic multiplies the load on an already-struggling service and turns a momentary glitch into a sustained outage. So retries with backoff, jitter, caps, and a breaker, not retry-until-it-works.

Exponential backoff + jitter + caps Retries only idempotent/transient Names retry storm/thundering herd + circuit breaker

Then they probe: Why is jitter important in addition to exponential backoff?

Practise this one
Junior

What is idempotency, and why does it matter for reliable distributed systems?

What most people say

Idempotency means an operation can be repeated safely.

Correct definition but no reasoning about why it is essential. The point is that retries and at-least-once delivery make duplicates unavoidable, so without idempotency you get double charges, and idempotency keys are how you handle it.

The structure behind a strong answer

  1. 1

    Definition. An operation is idempotent if applying it multiple times has the same effect as applying it once. Reading is naturally idempotent; a naive "add 10 dollars" is not.

  2. 2

    Why it matters. In distributed systems retries, timeouts, and at-least-once message delivery mean the same request can arrive more than once. Without idempotency, duplicates cause double charges, duplicate orders, corrupted counts.

  3. 3

    Idempotency keys. The client sends a unique key with the request; the server records it and ignores or returns the prior result for a repeat, so a retried request applies only once.

  4. 4

    Design for it. Prefer naturally idempotent operations (set to a value rather than increment), and dedupe by key where you cannot.

What gets you hired

An operation is idempotent if doing it multiple times has the same result as doing it once. A read is naturally idempotent, and setting a field to a specific value is, but add 10 dollars to a balance is not, because applying it twice moves the balance by 20. This matters enormously in distributed systems because duplicates are not an edge case, they are guaranteed. A client hits its 30 second timeout and retries even though the first request actually succeeded, a queue like SQS delivers at least once, a load balancer re-sends. On a busy service even a 0.1 percent duplicate rate is thousands of duplicate requests a day, and if the operation is not idempotent those become double charges, duplicate orders, or corrupted counters. The standard tool is an idempotency key. The client attaches a unique key, often a UUID, the server records which keys it has processed, typically with a 24 hour retention, and if the same key arrives again it returns the original stored result instead of doing the work twice, so a retried payment charges once. Where I can, I also design operations to be naturally idempotent, for example set order status to shipped rather than increment a counter, and where I cannot, I dedupe by key. Idempotency is what makes retries and at least once delivery safe, which is why it underpins reliable systems.

Clear definition + non-idempotent example Ties it to retries/at-least-once delivery Knows idempotency keys / designing for it

Then they probe: How does an idempotency key prevent a double charge on a retried payment?

Practise this one
Junior

What is graceful degradation, and when would you fail open versus fail closed?

What most people say

Graceful degradation means the app keeps working when something fails.

It states the goal but gives no mechanism and ignores fail open vs fail closed, which is the real judgment. Failing open on an auth check would be a security hole; the answer needs the per-dependency trade-off.

The structure behind a strong answer

  1. 1

    Degrade, do not collapse. When a non-critical dependency fails, keep the core working with reduced functionality, serve cached or default data, hide a feature, rather than failing the whole request.

  2. 2

    Fail open. On dependency failure, allow the operation to proceed (favor availability). Right when the dependency is non-critical, for example skip personalization if the recommendation service is down.

  3. 3

    Fail closed. On failure, deny the operation (favor safety). Right when proceeding is unsafe, for example deny access if the authorization service is unreachable.

  4. 4

    Decide per dependency. Choose based on the cost of being wrong: never fail open on security/auth/payments; degrade gracefully (fail open) for cosmetic or enhancement features.

What gets you hired

Graceful degradation means that when something fails, the system drops to reduced functionality instead of collapsing. If a non-critical dependency is down, I keep the core experience working, serving cached or default data, hiding or stubbing the affected feature, rather than failing the whole request. In practice I put a short timeout on that call, around 200 milliseconds, and a circuit breaker that trips once the error rate crosses about 50 percent, so one sick dependency cannot eat all my threads. The key decision is whether to fail open or fail closed. Fail open means I let the operation proceed despite the failure, favoring availability. That is right when the dependency is not essential. If the recommendations service is down, I serve the page without personalized recommendations instead of returning a 500. Fail closed means I deny the operation on failure, favoring safety. That is right when proceeding would be unsafe. If the authorization service is unreachable, I must deny access rather than let everyone through. So I decide per dependency, based on the cost of being wrong. Anything touching authentication, authorization, or payments fails closed, because failing open there is a breach or a financial loss. Cosmetic or enhancement features fail open and degrade so the core stays available. The mistake to avoid is a blanket policy, because the right choice depends entirely on what that dependency guards.

Degrade to reduced function, not crash Explains fail open vs fail closed Decides per dependency (auth fails closed)

Then they probe: Give an example where fail open would be a serious bug.

Practise this one
Mid

A service is suddenly returning 500s in production. Walk me through how you would troubleshoot it.

What most people say

I would check the logs and look at the events to see what is going wrong.

No scoping, no hypothesis, no prioritization. Logs are expensive and noisy; reaching for them first signals you have read docs, not run an incident. It never mentions what changed, restoring service, or prevention.

The structure behind a strong answer

  1. 1

    Scope it before touching anything. All requests or a subset (one endpoint, one region, one user tier)? Since when? This sizes the blast radius and tells you how aggressive to be.

  2. 2

    Ask what changed. Most incidents are self-inflicted: a deploy, a config/flag flip, a traffic spike, or a dependency. "What changed in the last hour" is the highest-yield question there is.

  3. 3

    Form a hypothesis, then confirm cheaply. Check the cheapest high-signal source first, error rate by version + recent deploys + the RED/USE dashboard, before opening noisy logs.

  4. 4

    Mitigate before root-causing. If a deploy correlates, roll back or shift traffic to restore service FIRST. MTTR beats being right.

  5. 5

    Root cause, then prevent. Once stable, find the real cause from the diff/trace, then add the missing alert, test, or guardrail so it cannot recur silently.

What gets you hired

First I'd scope it, every request or one endpoint/region, and since when, so I know the blast radius. Then the highest-yield question: what changed in the last hour, a deploy, a config flip, a traffic surge, a dependency. I'd form a hypothesis and confirm it cheaply, error rate by version and recent deploy events, before drowning in logs. If a deploy lines up, I'd roll back first to stop the bleeding and confirm 500s drop, then root-cause from the release diff and a trace of the failing path. Finally I'd add whatever was missing, a symptom-based alert or a regression test, so it can't page someone silently again.

Scopes the blast radius before acting Asks "what changed" early Restores service before chasing root cause

Then they probe: The logs show nothing obvious. What now?

Practise this one
Mid

Explain RPO and RTO, and how they should drive your backup and disaster-recovery design.

What most people say

RPO and RTO are recovery metrics, and we handle DR by taking nightly backups of the database.

It does not actually distinguish the two (one is data loss, one is downtime), and nightly backups quietly assume a 24-hour RPO that may be unacceptable. It never tests the restore, never protects the backup location, and treats "we have backups" as a DR strategy.

The structure behind a strong answer

  1. 1

    Define the two numbers. RPO (Recovery Point Objective) is how much data loss is acceptable, it sets backup frequency. RTO (Recovery Time Objective) is how long you can be down, it sets recovery speed.

  2. 2

    Let the numbers drive design. A tight RPO (minutes) needs continuous replication or frequent snapshots, not nightly dumps. A tight RTO (minutes) needs warm/hot standby, not restore-from-cold-backup. Both cost more, so set them from business need, not vanity.

  3. 3

    Match a DR pattern to the budget. Backup-and-restore (cheapest, slow RTO) < pilot light < warm standby < active-active (fastest, most expensive). Pick the cheapest pattern that meets the agreed RPO/RTO.

  4. 4

    Protect the backups themselves. Store copies cross-region/cross-account, encrypted, and immutable where possible, so a regional outage or a ransomware/deletion event cannot take the backups with the primary.

  5. 5

    Test the restore. Run real restore drills and measure actual recovery time against RTO. An untested backup is a hope, not a recovery plan, and you find out at the worst possible moment.

What gets you hired

RPO is how much data loss is acceptable, so it sets how often I back up or replicate. RTO is how long I can be down, so it sets how fast I have to recover. Both numbers come from the business and then drive the design, not the other way round. An RPO of 5 minutes means continuous replication or frequent snapshots, not a nightly dump that can lose 24 hours of orders. An RTO of 15 minutes means a warm or hot standby, because restoring a 2 terabyte database from cold backup will take hours. So I map the pattern to the agreed numbers. Backup and restore is cheapest but recovery runs in hours, pilot light gets you to tens of minutes, warm standby to minutes, and active active is near zero at the highest cost, and I pick the cheapest option that genuinely meets the RPO and RTO. I also protect the backups themselves, copied cross region and cross account, encrypted, and immutable with a lock of say 35 days, so one regional outage or one deletion event cannot take primary and backups together. Then I run real restore drills, at least quarterly, and time them against the RTO, because an untested backup is a hope, not a recovery plan.

Cleanly separates data loss (RPO) from downtime (RTO) Lets business requirements drive cost, not the reverse Matches a DR pattern to the agreed numbers

Then they probe: The business says RPO and RTO of near-zero for everything. How do you respond?

Practise this one
Mid

What is a circuit breaker, and how does it prevent cascading failures? Mention bulkheads too.

What most people say

A circuit breaker stops calling a service when it is failing.

Right idea but missing the mechanism (the closed/open/half-open states and failing fast) and why it matters, freeing caller resources to stop a cascade, plus bulkheads as the complementary isolation pattern.

The structure behind a strong answer

  1. 1

    The problem it solves. Calling a failing or slow dependency ties up the caller resources (threads, connections) on doomed calls, which can exhaust the caller and cascade the failure upstream.

  2. 2

    How a breaker works. It tracks failures; once they cross a threshold it opens, and further calls fail fast immediately instead of waiting. Periodically it goes half-open to test, and closes again if the dependency recovers.

  3. 3

    Why it helps. Failing fast frees the caller resources and stops hammering a struggling dependency, giving it room to recover, which breaks the cascade.

  4. 4

    Bulkheads. Isolate resources per dependency (separate thread/connection pools), so one slow dependency exhausts only its own pool and cannot sink the whole service, like watertight compartments in a ship.

What gets you hired

A circuit breaker protects against cascading failure. When a dependency is slow, every call ties up caller resources, threads and connections, waiting on doomed requests. If your timeout is 30 seconds and you have a pool of 200 threads, a dependency that stops responding drains that pool in seconds, and then the caller itself starts failing and the problem propagates upstream. One slow dependency takes down the whole chain. The breaker counts failures, and once they cross a threshold, say 50 percent of the last 20 calls, it opens, and subsequent calls fail fast in about a millisecond instead of hanging. After a cool off, typically around 30 seconds, it goes half open and lets a single trial request through. If that succeeds it closes and normal traffic resumes, if not it stays open. Two things make this powerful. Failing fast frees the caller resources so it stays healthy, and not hammering a struggling dependency gives it room to recover, which is what actually breaks the cascade. The complementary pattern is the bulkhead. I isolate resources per dependency, so each downstream gets its own thread or connection pool, maybe 20 threads each rather than one shared pool of 200. Then one slow dependency can only exhaust its own compartment. The name comes from ship compartments, a breach floods one compartment, not the whole hull. Together, breakers and bulkheads stop a single failing dependency from sinking the entire service.

Explains failing fast to free caller resources Knows closed/open/half-open states Knows bulkhead resource isolation

Then they probe: What do the open, closed, and half-open states mean?

Practise this one
Mid

How do rate limiting, load shedding, and backpressure protect a service under overload?

What most people say

You add rate limiting so users cannot send too many requests.

It covers one mechanism for one purpose. The deeper point is overload protection: load shedding and backpressure keep the service alive by rejecting/slowing excess, because serving some requests well beats collapsing for all.

The structure behind a strong answer

  1. 1

    Rate limiting. Cap how many requests a client or tenant can make in a window (token bucket), to enforce fairness and stop one caller from overwhelming the service.

  2. 2

    Load shedding. Under overload, deliberately reject excess requests (return 429/503) to protect the throughput of the rest, rather than slowing to a crawl and failing everything.

  3. 3

    Backpressure. Signal upstream to slow down (bounded queues, blocking, flow control), so the producer matches the consumer capacity instead of piling up unbounded work.

  4. 4

    Serve some, not none. The shared principle: at capacity, gracefully reject or slow excess so the system keeps serving most requests well, instead of melting down for everyone.

What gets you hired

These are three overload-protection mechanisms with the same underlying philosophy: at capacity, it is better to reject or slow some traffic cleanly than to let everything collapse. Rate limiting caps how many requests a given client or tenant can make in a time window, usually with a token-bucket algorithm, which enforces fairness and prevents one noisy caller from consuming all the capacity. Load shedding is what you do under genuine overload: deliberately reject the excess requests, returning a clear 429 or 503, so that the requests you do accept are served well, instead of trying to handle everything and degrading until the whole service times out for everyone. Backpressure works in the other direction, it signals upstream to slow down, through bounded queues, blocking, or flow-control protocols, so the producer rate is matched to what the consumer can actually handle, rather than letting unbounded work pile up and exhaust memory. The unifying idea is serve some, not none: a service at its limit should gracefully shed or throttle the overflow to protect its core throughput, because a controlled partial rejection is vastly better than an uncontrolled total meltdown where latency explodes and everything fails. So I design these in deliberately rather than hoping load never exceeds capacity.

Distinguishes the three mechanisms Knows shed/throttle excess beats collapse Mentions token bucket / bounded queues / 429-503

Then they probe: Why is shedding load (rejecting some requests) better than trying to serve everything?

Practise this one
Mid

How does caching improve reliability, and what failure modes does it introduce?

What most people say

Caching makes things faster by storing data so you do not hit the database every time.

It captures the speed benefit but treats caching as a free win. The reliability angle (serving through outages) and the failure modes (stampede, staleness, cache as SPOF, cold-cache thundering) are exactly what an interviewer is probing.

The structure behind a strong answer

  1. 1

    Reduces load. A cache absorbs reads so the backing store is hit far less, which protects the database and improves latency and capacity.

  2. 2

    Improves availability. A cache can serve data even when the backing store is slow or down (stale-while-revalidate), keeping the service up through a dependency blip.

  3. 3

    Stampede / thundering herd. When a popular key expires, many requests miss at once and all hit the backend together. Mitigate with request coalescing/locks and jittered TTLs.

  4. 4

    Staleness and SPOF. Cached data can be stale (need an invalidation strategy), and the cache itself can become a dependency, plan for cache loss (cold cache thundering the DB) and redundancy.

What gets you hired

Caching helps reliability in two ways. It reduces load. On a read heavy service with a 10 to 1 read to write ratio, a cache at a 95 percent hit rate means the database sees roughly one twentieth of the reads, which protects it from overload and improves both latency and effective capacity. It also improves availability. With stale while revalidate, the cache keeps serving slightly old data even when the backing store is slow or briefly down, so a 30 second database blip never becomes a user facing outage. But caching is not free, and the failure modes are the interesting part. The classic one is a stampede, or thundering herd. A hot key expires and every request misses at once and slams the backend together, which can take down the very database the cache was protecting. I mitigate that with request coalescing or a lock so only one request recomputes the value, and with jittered TTLs, so a 5 minute TTL becomes 5 minutes plus or minus 30 seconds and the keys do not all expire on the same tick. There is also staleness, so I need a deliberate invalidation strategy. And the cache becomes a dependency itself. If it dies or comes back cold after a restart, 100 percent of traffic hits the database at full force. So I plan for cache redundancy and for warming or gracefully degrading with a cold cache. Caching is a powerful reliability tool that brings its own reliability concerns to design for.

Load reduction + availability (serve during outage) Knows stampede/thundering herd + mitigations Knows staleness/invalidation + cache as SPOF / cold cache

Then they probe: What is a cache stampede and how do you prevent it?

Practise this one
Mid

How does putting a queue between services improve resilience, and what are the trade-offs?

What most people say

A queue lets services talk to each other asynchronously so they are not tightly coupled.

True but generic. It misses the concrete resilience wins, load smoothing, retry, DLQ, and the honest trade-offs (eventual consistency, duplicates needing idempotency), which is what distinguishes a real answer.

The structure behind a strong answer

  1. 1

    Decouple producer and consumer. A queue lets the producer hand off work and move on; the consumer processes at its own pace, so a slow or down consumer does not fail the producer.

  2. 2

    Smooth load spikes. The queue acts as a buffer, absorbing bursts so the consumer processes at a steady rate instead of being overwhelmed, leveling the load.

  3. 3

    Built-in retry and DLQ. Failed messages can be retried, and a dead-letter queue captures messages that keep failing so they are isolated for inspection rather than lost or blocking the queue.

  4. 4

    The trade-offs. You gain resilience at the cost of eventual consistency (async, not immediate), added operational complexity, and the need to handle duplicate delivery (idempotency) and possibly ordering.

What gets you hired

Putting a queue between services decouples them in time, and that buys real resilience. The producer hands work off and moves on, so it does not block on or fail because of a slow or unavailable consumer, and the consumer drains at its own pace. The queue also smooths load. If a marketing email drives a 10x spike for 5 minutes, the queue absorbs it and the consumer keeps processing at a steady 500 messages a second instead of being overwhelmed, which is hugely valuable for spiky workloads. Queues also give reliable processing primitives. Failed messages are retried automatically, and after a few attempts, I usually set 3 to 5 with backoff, a dead letter queue captures them so a poison message is isolated for investigation instead of being lost or blocking everything behind it. The honest trade offs. You move from synchronous to eventual consistency, so the work lands seconds or minutes later, and the user experience and any read after write expectation has to accommodate that. You add operational complexity, another system to run and monitor, with queue depth and consumer lag as key signals, and I will alert if depth stays above a few thousand for more than 5 minutes. And because most queues are at least once, duplicates happen, so consumers must be idempotent, and I think about ordering if the workload needs it. A queue is a strong resilience pattern, but I add it deliberately, knowing it trades immediate consistency and simplicity for that resilience.

Decoupling + load smoothing + retry/DLQ Honest about eventual consistency Knows duplicates need idempotency (+ ordering)

Then they probe: What is a dead-letter queue for?

Practise this one
Senior

How do you limit the blast radius of a failure so one problem does not take down everything?

What most people say

I would make everything redundant so if one part fails another takes over.

Redundancy helps but does not limit blast radius if everything shares fate, one bad config push or a shared control plane fails all the redundant copies at once. Blast-radius thinking is about isolation (cells, staged rollouts), not just spares.

The structure behind a strong answer

  1. 1

    Partition into cells. Split users or tenants into independent cells/shards, each a self-contained stack, so a failure in one cell affects only that fraction of users, not everyone.

  2. 2

    Isolate across failure domains. Spread across availability zones and regions, and use bulkheads so resource exhaustion or a bad dependency is contained, not shared.

  3. 3

    Avoid shared fate. Watch for global dependencies and global changes, a single config push, one shared database, one control plane, that can fail everything at once. Reduce or stage them.

  4. 4

    Stage changes. Roll out changes progressively (canary, then waves, region by region) with automated rollback, since deploys and config changes are a top cause of correlated, everything-at-once failures.

What gets you hired

Limiting blast radius is about containing failure so 1 problem hits a fraction of the system, not all of it, and it goes beyond plain redundancy. The strongest lever is partitioning: divide users or tenants into independent cells or shards, each a self-contained stack. With 10 cells, a cell failure is a 10 percent outage rather than a 100 percent one, and you can update 1 cell at a time. Then isolate across failure domains: spread across at least 3 availability zones and, where it matters, multiple regions, and use bulkheads internally so a misbehaving dependency is confined to its own compartment, say a connection pool capped at 20 for that one caller. The thing senior engineers really watch for is shared fate, the hidden global dependencies and global actions that can fail everything at once: 1 config push applied everywhere simultaneously, 1 shared database or cache, 1 control plane. Those defeat all your redundancy if they go bad, so I try to eliminate or shard them, and where a global change is unavoidable I stage it. That is the last piece: roll changes out progressively, canary at 1 percent, then waves, then region by region, with automated rollback on error-rate regression, because deploys and config changes are the biggest single cause of correlated, everything-at-once outages. Redundancy stops a component failure being fatal. Cells, isolation, no shared fate and staged rollouts stop any single failure or change becoming global.

Cells/shards for failure isolation Avoids shared fate (global config/DB/control plane) Staged/canary rollouts with rollback

Then they probe: Why does redundancy alone not limit blast radius?

Practise this one
Senior

What is chaos engineering, and how would you introduce it responsibly?

What most people say

Chaos engineering is randomly breaking things in production to see what happens.

Calling it random breakage misses the discipline. Real chaos engineering is controlled, hypothesis-driven experiments with a defined steady state, a bounded blast radius, and an abort switch, to find weaknesses safely, not reckless destruction.

The structure behind a strong answer

  1. 1

    The idea. Deliberately inject failures, kill instances, add latency, drop a dependency, to discover how the system actually behaves under failure, before a real incident does it for you.

  2. 2

    Hypothesis-driven. Each experiment states a hypothesis (the system should stay healthy if this dependency slows) and a steady-state metric to check, so you learn something specific, not just break things.

  3. 3

    Bound the blast radius. Start small, in staging or a small slice of production, with a kill switch to abort, and expand confidence gradually. Never an uncontrolled free-for-all.

  4. 4

    Close the loop. Fix the weaknesses you find and automate the experiments so resilience does not regress. The goal is verified resilience, not chaos for its own sake.

What gets you hired

Chaos engineering is the practice of proactively injecting failure into a system to learn how it actually behaves under stress, killing instances, adding latency, making a dependency error, so you discover weaknesses in a controlled experiment instead of during a 3am incident. The crucial thing is that it is disciplined, not random breakage. Each experiment is hypothesis-driven: I define the normal steady state with a metric, then state a hypothesis like the system should stay within its error budget even if this dependency slows by 500ms, and run the experiment to confirm or refute it, so I learn something specific. I bound the blast radius carefully: start in staging or on a small slice of production traffic, have a kill switch to abort immediately if it goes wrong, and only widen the scope as confidence grows, never an uncontrolled free-for-all. And I close the loop, the point is not to break things, it is to find the weaknesses, fix them, and then automate the experiments so the resilience does not silently regress as the system changes. Done this way, chaos engineering turns assumptions about resilience into verified facts, which is exactly what you want before a real failure tests them for you. The classic origin is Netflix Chaos Monkey randomly terminating instances to force engineers to build for instance failure.

Proactive failure injection to find weaknesses Hypothesis-driven + steady-state metric Bounded blast radius + abort + fix-and-automate loop

Then they probe: How do you run a chaos experiment safely in production?

Practise this one
Senior

A stakeholder asks for "five nines". How do you reason about availability targets and their cost?

What most people say

I would set up redundancy and failover to hit five nines.

It jumps to implementation without reckoning with what five nines costs (about 5 minutes downtime a year, near-impossible with human involvement) or whether it is justified. A senior translates the target, prices it, and challenges whether it is needed.

The structure behind a strong answer

  1. 1

    Translate nines to downtime. 99.9% is about 8.8 hours of downtime a year, 99.99% about 53 minutes, 99.999% about 5 minutes. Make the abstract target concrete first.

  2. 2

    Each nine costs ~10x. Every additional nine is roughly an order of magnitude more engineering: multi-region, automated sub-minute failover, removing every SPOF, far more operational rigor.

  3. 3

    Dependencies multiply. Availability compounds: a request depending on several 99.9% services is less available than any one of them. You cannot exceed your dependencies without redundancy around them.

  4. 4

    Match target to need. Push to set the SLO from real business impact, not a round number. Over-targeting wastes money and slows delivery; under-targeting hurts users. Often 99.9 percent is plenty.

What gets you hired

First I make the target concrete, because nines are abstract: 99.9% is roughly 8.8 hours of downtime a year, 99.99% is about 53 minutes, and 99.999%, five nines, is about 5 minutes a year, which is so tight that essentially no human can be in the recovery loop, it demands fully automated detection and failover. Then I price it: each additional nine is roughly an order of magnitude more engineering and operational cost, multi-region active-active, sub-minute automated failover, eliminating every single point of failure, and a lot more rigor and on-call maturity. I also raise that availability compounds across dependencies: if a request flows through several services that are each 99.9%, the end-to-end availability is the product, lower than any single one, so you cannot promise more than your dependencies deliver unless you add redundancy around them. With all that on the table, I push back on the round number and anchor the SLO to actual business impact, what does an hour of downtime actually cost, what do users genuinely need, because over-targeting wastes large amounts of money and slows down delivery for reliability nobody needs, while under-targeting hurts the business. Very often the honest answer is that 99.9% is plenty and five nines is not worth the cost, and my job is to have that conversation with real numbers rather than just nodding and trying to build it.

Translates nines to concrete downtime Knows each nine is ~10x cost Knows dependencies multiply + pushes back on over-targeting

Then they probe: How much downtime does each nine allow per year?

Practise this one

Security

13 questions · Foundation, Junior, Mid, Senior, Principal
Foundation

Explain the shared responsibility model. Where does the cloud provider's job end and yours begin?

What most people say

The cloud provider handles security, so as long as I use AWS or Azure my data is safe.

It assumes the provider covers everything, which is exactly the mindset that leaks data. It misses that the customer owns access control and configuration.

The structure behind a strong answer

  1. 1

    State the split. The provider secures the cloud itself; the customer secures what they put in the cloud. Security of the cloud versus security in the cloud.

  2. 2

    Name the provider's side. They own the physical data centers, the hardware, the network backbone, and the virtualization layer underneath your resources.

  3. 3

    Name your side. You own your data, your access controls and identities, your network configuration, and patching anything you run on top.

  4. 4

    Show the line shifts. The boundary moves by service type. With managed services like a serverless function you own less, since the provider patches the runtime; with a raw virtual machine you own the operating system.

  5. 5

    Land the lesson. Most real breaches happen on the customer side, like a public storage bucket or an over-permissioned identity, not because the provider's hardware failed.

What gets you hired

The shared responsibility model splits security into the provider's job and mine. The provider handles security of the cloud, the physical data centres, the hardware, and the virtualization layer. I handle security in the cloud, my data, who can access it, my network rules, and patching anything I run. The important part is that the line shifts by service. On a raw virtual machine I own the operating system, so I am the one applying that kernel patch within my 30 day window. On a managed serverless function the provider owns the runtime and I own only my code and the permissions attached to it, which might be one role with 3 specific actions on 1 bucket. This matters because the breaches are almost never the provider's hardware failing. Gartner has said for years that through 2025 around 99 percent of cloud security failures would be the customer's fault, and what I see matches that, a storage bucket left public, or an identity with a wildcard policy that grants every action on every resource when it needed 2. So when I join a team I assume the hard part of the job is on my side of the line, and I go looking for the public bucket and the over-permissive role first.

say security of versus security in the cloud note the line shifts by service type name access control as your job

Then they probe: Give a concrete example of something on your side that often goes wrong.

Practise this one
Foundation

What is the CIA triad, and why is it a useful way to think about security?

What most people say

CIA is confidentiality, integrity, and availability, the three main goals of security.

It recites the acronym but does not connect it to controls or show how to use it. The value is as a thinking tool, and the answer never demonstrates applying it.

The structure behind a strong answer

  1. 1

    Confidentiality. Only authorized parties can see the data. Controls: encryption, access control, least privilege.

  2. 2

    Integrity. Data is not tampered with and changes are traceable. Controls: hashing, signatures, checksums, audit logs.

  3. 3

    Availability. The system is accessible when needed. Controls: redundancy, backups, DDoS protection, capacity.

  4. 4

    Use it as a checklist. For any system, ask how each of the three is protected. It catches gaps, for example strong encryption but no backups (availability) or no audit trail (integrity).

What gets you hired

CIA is confidentiality, integrity, and availability, and I like it because it gives me a checklist I can walk any design through. Confidentiality is making sure only authorized parties can read the data. I cover that with encryption, AES-256 at rest and TLS 1.2 or higher in transit, plus access control and least privilege, so a role gets the 3 actions it needs and nothing more. Integrity is making sure data is not tampered with and that changes are traceable. I cover that with hashing, signatures, and audit logs, typically kept for at least 90 days and often a year for compliance. Availability is making sure the system is there when people need it. I cover that with redundancy across 3 availability zones, tested backups, and DDoS protection, sized against whatever target we have promised, say 99.9 percent, which is about 43 minutes of downtime a month. The real point is that the three pull against each other and you need all of them. I have seen a system with beautiful encryption and no restore test, and when a bad deploy corrupted the data, a 4 hour outage turned into 2 days. Confidentiality was strong and availability failed anyway. So I walk the design through all three and go looking for the leg nobody funded.

Maps each leg to concrete controls Uses it as a design checklist Knows the three trade off against each other

Then they probe: Give an example where focusing only on confidentiality hurts availability.

Practise this one
Foundation

What is the difference between authentication and authorization?

What most people say

Authentication is logging in and authorization is permissions, they are basically the same thing.

"Basically the same thing" is the red flag. They are distinct steps with distinct failure modes (401 vs 403), and conflating them leads to real access-control bugs.

The structure behind a strong answer

  1. 1

    Authentication is identity. AuthN proves who you are: a password, a token, a certificate, plus MFA to strengthen it.

  2. 2

    Authorization is permission. AuthZ decides what you are allowed to do once your identity is known: roles, policies, scopes.

  3. 3

    Order matters. You authenticate first, then authorize. You cannot decide what someone may do until you know who they are.

  4. 4

    They fail differently. An authN failure is "I do not know who you are" (401); an authZ failure is "I know who you are, but you are not allowed" (403).

What gets you hired

Authentication is about identity, proving who you are, with something like a password, a token, or a certificate, often strengthened with MFA. Authorization is about permission, deciding what that identity is allowed to do, through roles, policies, or scopes. The order is fixed: you authenticate first, then authorize, because you cannot decide what someone may do until you know who they are. They also fail differently, which is a useful tell: a 401 means authentication failed, I do not know who you are; a 403 means authentication succeeded but authorization did not, I know who you are and you are not allowed. Keeping them separate matters because a lot of access-control bugs come from checking identity but forgetting to check permission.

Clean "who you are" vs "what you can do" Knows authN precedes authZ Maps 401 vs 403

Then they probe: A user is logged in but gets a 403 on an endpoint. AuthN or authZ problem?

Practise this one
Junior

What is the difference between encryption at rest and in transit, and why do you need both?

What most people say

At rest is when data is stored and in transit is when it moves; you enable both for full encryption.

It defines the terms but not the threats, and "full encryption" ignores the actual hard part: key management. It also misses that data is still plaintext in memory and to anyone with valid access.

The structure behind a strong answer

  1. 1

    In transit. Encrypts data moving over the network (TLS), so an attacker who intercepts traffic cannot read it. Defends against eavesdropping on the wire.

  2. 2

    At rest. Encrypts data stored on disk, in a database, or in object storage, so someone who gets the physical media or a storage snapshot cannot read it.

  3. 3

    Different threats. They cover different attack points: in transit protects the network path, at rest protects the stored copy. Neither covers the other.

  4. 4

    Key management is the hard part. Use a managed KMS, rotate keys, and use envelope encryption (a data key encrypted by a master key). The encryption is easy; protecting and rotating the keys is the real work.

What gets you hired

They protect against different attacks. Encryption in transit, which in practice means TLS 1.2 or 1.3, protects data while it moves across the network, so an attacker tapping the wire or sitting in the middle sees ciphertext. Encryption at rest protects stored data, on disk, in a database, in object storage, usually AES-256, so someone who steals the media or grabs a storage snapshot also gets ciphertext. You need both because they cover different attack points and neither substitutes for the other. TLS does nothing for a backup that walks out of the building, and at rest encryption does nothing for traffic sniffed on the wire. The part that actually takes thought is key management. I use a managed KMS, turn on automatic rotation, typically a new key version every 365 days, and rely on envelope encryption, where a data key encrypts the data and a master key encrypts that data key. That is what lets me rotate in 1 operation instead of re-encrypting terabytes. One caveat I would be honest about: at rest encryption does nothing against someone with valid application access, because the app decrypts transparently. That is what authorization is for.

Names the distinct threat each covers Knows both are needed Mentions KMS/rotation/envelope encryption

Then they probe: Does at-rest encryption protect against a compromised application with valid credentials?

Practise this one
Junior

How should an application running in the cloud get credentials to call other cloud services? Why avoid long-lived access keys?

What most people say

I would create an IAM user, generate access keys, and put them in the application config.

This is the exact anti-pattern. A static key in config is the classic leak vector, it does not expire and is painful to rotate. The expected answer is roles with temporary credentials.

The structure behind a strong answer

  1. 1

    The wrong way. Hard-coding a long-lived access key in code, config, or an environment variable. It leaks easily, never expires, and is hard to rotate.

  2. 2

    Use a role, not a key. Attach a role to the compute: an instance profile on a VM, IRSA or workload identity for a pod, a managed identity on Azure. The platform hands the app temporary credentials automatically.

  3. 3

    Short-lived and rotated. Those credentials are short-lived and auto-rotated, so a leak has a small blast radius and there is nothing static to steal.

  4. 4

    Scope and federate. Scope the role to least privilege, and for CI/CD or external systems use OIDC federation so even pipelines assume a role instead of holding a static key.

What gets you hired

The application should never hold a long lived access key. Those get committed to git, baked into images, or printed into logs, and because they never expire, 1 leak from 3 years ago is still a live liability, and rotating it means chasing down every place it was copied. Instead I attach a role to the workload: an instance profile for a VM, IRSA or workload identity for a Kubernetes pod, a managed identity on Azure. The platform then injects short lived credentials, typically valid for about 1 hour and auto rotated well before they expire, and the SDK picks them up from the metadata endpoint with no code change. There is nothing static to steal, and anything that does leak dies on its own within the hour. I scope that role to least privilege, only the specific actions on the specific resources it needs, so a compromised service can read its 1 bucket and nothing else. And for things outside the cloud, like a CI/CD pipeline, I use OIDC federation, so the pipeline trades its identity token for a role with a token that lasts about 15 minutes instead of storing a permanent key in the CI settings. The goal is simple: no static long lived credentials anywhere.

Rejects static keys outright Knows instance profiles/IRSA/managed identity Mentions OIDC for CI and least privilege

Then they probe: Your CI pipeline needs to deploy to the cloud. How does it authenticate without a stored key?

Practise this one
Junior

What is the difference between symmetric and asymmetric encryption, and where is each used?

What most people say

Symmetric uses one key and asymmetric uses two keys, asymmetric is more secure.

"More secure" is wrong framing, they solve different problems. It misses the speed trade-off and how the two are combined, which is the practically important part.

The structure behind a strong answer

  1. 1

    Symmetric. One shared secret key encrypts and decrypts (AES). Fast and good for bulk data, but both sides must already share the key securely.

  2. 2

    Asymmetric. A keypair: a public key encrypts (or verifies) and a private key decrypts (or signs) (RSA, ECC). Solves key distribution, but is much slower.

  3. 3

    Combine them. In practice you use asymmetric to securely exchange a symmetric session key, then switch to fast symmetric for the actual data. That is exactly what TLS does.

  4. 4

    Signatures. Asymmetric also gives you signing: a private key signs, anyone with the public key verifies, which proves authenticity and integrity.

What gets you hired

Symmetric encryption uses 1 shared key for both encryption and decryption, AES-256 being the standard. It is fast, gigabytes per second on modern hardware, so it is ideal for bulk data, but it has a key distribution problem, because both sides have to already share that secret safely. Asymmetric uses a keypair, a public key and a private key, RSA-2048 or an elliptic curve like P-256. The public key can be handed to anyone, so distribution is solved, but it is on the order of 1000 times slower, so I would never encrypt a 5 gigabyte file with it. The elegant part is that you combine them. You use asymmetric to authenticate the server and agree on a symmetric session key, then switch to fast symmetric encryption for the actual traffic. That is exactly how TLS works, the handshake is asymmetric and costs 1 round trip in TLS 1.3, and everything after it is symmetric. Asymmetric also gives you digital signatures, signing with the private key and verifying with the public key, which proves authenticity and integrity. So it is not that 1 of them is better. It is fast but distribution hard versus slow but distribution solved, and in practice they are used together.

Knows symmetric = one key/fast, asymmetric = keypair/slow Explains they are combined in TLS Mentions signatures

Then they probe: Why not just use asymmetric encryption for everything?

Practise this one
Mid

What does it mean to "shift security left", and how would you build security into a CI/CD pipeline?

What most people say

I would add a security scanning tool to the pipeline that checks the code before deploying.

Vague and single-tool. There is no one scanner; the answer should name the distinct layers (SAST, SCA, secrets, image, IaC, DAST) and address gating versus developer friction.

The structure behind a strong answer

  1. 1

    Shift left = catch it early. Move security checks earlier, into commit and build, where issues are cheap to fix, instead of finding them in production.

  2. 2

    Scan the code and deps. SAST for the source, SCA for third-party dependencies and their known CVEs, and secret scanning to block credentials from ever being committed.

  3. 3

    Scan what you ship. Container image scanning for OS/package vulnerabilities, IaC scanning (Terraform/K8s) for misconfigurations, and ideally image signing and an SBOM for provenance.

  4. 4

    Test the running app. DAST against a deployed test environment to catch runtime issues the static tools miss.

  5. 5

    Gate without blocking everything. Fail the build on critical/high findings, but tune thresholds and triage false positives, or developers route around the pipeline. Security has to be usable.

What gets you hired

Shifting left means moving security out of a late gate and into the everyday workflow, catching issues at commit and build where a fix costs minutes rather than the days it costs once it is live. In the pipeline I layer complementary checks, because each one catches different things, and I keep the whole set under about 10 minutes so people do not route around it. On the code, SAST for the source, SCA to flag vulnerable third party dependencies, and secret scanning so credentials never get committed, ideally with a pre commit hook as well. On the artifact, container image scanning for OS and package CVEs, and IaC scanning like tfsec or checkov for misconfigured Terraform or Kubernetes, plus image signing and an SBOM so I know the provenance of everything that ships. After deploy to a test environment, DAST exercises the running app for issues static tools cannot see. The judgment part is gating. I fail the build on critical and high findings and let mediums through as warnings with a 30 day fix window, and I spend real time tuning thresholds and triaging false positives. On one team the scanner opened over 200 findings on day one and most were noise. If the pipeline cries wolf, developers will find a way around it and you have made things less secure, not more.

Names SAST/SCA/secret/image/IaC/DAST and where each fits Knows shift-left = cheaper-earlier Balances gating vs developer friction

Then they probe: What is the difference between SAST, DAST, and SCA?

Practise this one
Mid

Name some of the most common web application vulnerabilities (OWASP Top 10) and how you defend against them.

What most people say

Things like SQL injection and XSS, you defend against them by validating user input.

It names two and reduces every defense to "validate input". Different vulns need different mitigations (parameterized queries, output encoding, server-side authZ, patching), and broken access control, the most common class, is missing.

The structure behind a strong answer

  1. 1

    Injection. SQL or command injection from untrusted input. Defend with parameterized queries/prepared statements and never building queries by string concatenation.

  2. 2

    Broken access control. Users reaching data or actions they should not (the most common class). Defend by enforcing authorization server-side on every request, default deny.

  3. 3

    Cross-site scripting (XSS). Injecting scripts that run in other users browsers. Defend by output-encoding/escaping and a Content Security Policy.

  4. 4

    Misconfiguration and stale components. Default credentials, open buckets, unpatched libraries. Defend with hardened defaults, patching, and dependency scanning (SCA).

  5. 5

    SSRF and secrets exposure. Server-side request forgery tricking the app into calling internal endpoints; and leaked secrets. Defend with egress controls/metadata protection and a secrets manager.

What gets you hired

A few of the big ones. Injection, like SQL injection, where untrusted input becomes part of a query. The defense is parameterized queries and prepared statements, never string concatenating SQL. Broken access control, which sits at number 1 in the current OWASP Top 10, where a user reaches data or actions they should not, and the defense is enforcing authorization server side on every request with default deny, not hiding the button in the UI. Changing an id in a URL from 41 to 42 should return a 403, not somebody else's invoice. Cross site scripting, where attacker script runs in another user's browser, defended with output encoding and a Content Security Policy. Security misconfiguration and vulnerable components, so default credentials, public buckets, unpatched libraries, defended with hardened defaults, patching, and dependency scanning. And SSRF, where you trick the server into calling internal endpoints, defended with egress restrictions and protecting the instance metadata endpoint, which is why IMDSv2 exists. The theme is that input validation alone is not enough. Each class has its own specific mitigation, and you need all of them.

Names multiple distinct classes incl. broken access control Gives the right mitigation per class Knows input validation alone is insufficient

Then they probe: Why is "validate input" not a sufficient defense against SQL injection?

Practise this one
Mid

How do you secure a container image and its supply chain?

What most people say

I would scan the image for vulnerabilities before pushing it to the registry.

Scanning is necessary but only one layer. It ignores base image size, running as non-root, image signing, SBOMs, and admission control, which is where supply-chain security actually lives.

The structure behind a strong answer

  1. 1

    Start minimal. Use a small or distroless base image: fewer packages means a smaller attack surface and fewer CVEs to patch.

  2. 2

    Run as non-root. Set a non-root USER, drop Linux capabilities, use a read-only root filesystem, and never bake secrets into layers.

  3. 3

    Scan and pin. Scan images for known vulnerabilities in CI, pin base images by digest (not just :latest), and rebuild to pick up patches.

  4. 4

    Prove provenance. Sign images (for example with cosign) and generate an SBOM so you can verify what is in an image and that it came from your pipeline, not a tampered source.

  5. 5

    Enforce at admission. In Kubernetes, use an admission policy to reject unsigned images, images from untrusted registries, or those running as root.

What gets you hired

I work it in layers. Start with a minimal or distroless base image, because every extra package is attack surface and another CVE to patch. Swapping a full OS base for distroless has taken images I have worked on from around 900MB and 150 plus known CVEs down to about 80MB and a handful. Then harden the runtime, a non root USER, dropped capabilities, and a read only root filesystem, and absolutely no secrets baked into image layers, because they persist in the history even if a later layer deletes them. Scan images in CI for known vulnerabilities, pin base images by digest rather than a floating tag so builds are reproducible, and rebuild on a schedule, weekly is a reasonable default, to absorb upstream patches. Then prove provenance, which is the supply chain part, sign images with something like cosign and publish an SBOM, so I can verify an image really came from my pipeline and know exactly what is inside it. Finally enforce it at the cluster boundary with an admission policy that rejects unsigned images, untrusted registries, or any container asking to run as root. So scanning is one layer. The real protection is minimal base, runtime hardening, signed provenance, and admission enforcement working together.

Minimal/distroless base + non-root runtime Pins by digest, scans in CI Signing/SBOM/admission control for supply chain

Then they probe: Why pin a base image by digest instead of using :latest?

Practise this one
Mid

What is defense in depth, and how does it relate to zero trust?

What most people say

Defense in depth means having multiple firewalls so attackers cannot get through.

It reduces layering to "more firewalls", all at the network layer. Real defense in depth spans network, identity, app, and data, and the answer misses zero trust and assume-breach entirely.

The structure behind a strong answer

  1. 1

    Defense in depth. Multiple independent layers of control, so if one fails the next still holds: network, identity, application, and data protections stacked.

  2. 2

    Why one layer is not enough. Any single control can fail or be bypassed. Layering means a breach of one (say the network perimeter) does not hand over everything.

  3. 3

    Zero trust. Drops the assumption that being inside the network means trusted. Every request is authenticated and authorized on its own, regardless of origin.

  4. 4

    How they fit. Zero trust is a modern expression of defense in depth focused on identity: verify explicitly, least privilege, and assume breach, so internal traffic is not automatically trusted.

What gets you hired

Defense in depth means layering independent controls so no single failure is catastrophic. Network controls, identity and access controls, application hardening, and data protections like encryption, stacked so an attacker who gets through 1 layer still meets the next. The reason is simple, any single control can fail or be bypassed, so you never want the whole system resting on the perimeter. Zero trust is the modern evolution of that idea, centered on identity. The old model trusted anything inside the network, which is how one phished laptop turned into a full breach. Zero trust drops that assumption and treats every request as untrusted until proven otherwise, authenticating and authorizing it on its own merits regardless of where it comes from. The 3 principles are verify explicitly, enforce least privilege, and assume breach. So these are not competing ideas. Zero trust is defense in depth applied on the assumption that the attacker may already be inside, which is why internal service to service calls get mTLS with certificates that rotate on the order of every 24 hours, and why each service gets a policy scoped to the specific calls it makes, rather than implicit trust because it happens to be in the same VPC.

Layers span network/identity/app/data Knows zero trust drops perimeter trust Says verify explicitly / least privilege / assume breach

Then they probe: What does "assume breach" change about how you design a system?

Practise this one
Senior

A developer accidentally committed an AWS access key to a public GitHub repo. What do you do?

What most people say

I would remove the key from the repository and force-push to delete it from the history.

This is the dangerous wrong instinct. The key is already harvested by bots within seconds; deleting the commit does nothing to stop misuse. Without immediate rotation, the account stays exposed.

The structure behind a strong answer

  1. 1

    Revoke immediately. Treat the key as compromised the moment it was public. Disable/delete it right away, the seconds-to-bots reality means it may already be in use.

  2. 2

    Deleting the commit is not enough. The key is already scraped and cached. Removing it from git history does not un-leak it; rotation is the only real fix.

  3. 3

    Assess the damage. Check CloudTrail/audit logs for any use of that key: unexpected API calls, new resources (crypto mining is common), IAM changes. Scope the blast radius.

  4. 4

    Contain and recover. If it was used, follow incident response: revoke derived sessions, roll affected resources/credentials, and check for persistence (new users, keys, backdoors).

  5. 5

    Prevent recurrence. Add secret scanning and pre-commit hooks, move to short-lived role-based credentials so there is no static key to leak, and consider why a long-lived key existed at all.

What gets you hired

First, rotate, immediately. The instant that key hit a public repo I treat it as compromised, because bots scrape new GitHub commits and exposed keys get used in under 60 seconds in the worst cases, so I disable and delete it right away. The common mistake is to delete the commit, force-push, and think it is handled. It is not. The key was scraped and cached within seconds, so removing it from history does not un-leak it, only revoking the credential does. With that done I assess blast radius in CloudTrail, and I look at the whole window from the commit timestamp forward, not just the last hour: was the key used, are there unexpected API calls, new resources like 20 large GPU instances spun up for crypto mining in a region we never touch, or IAM changes. If it was used, I run it as a real incident, revoke derived sessions, roll affected resources, and hunt for persistence like new users or backdoor access keys. Then prevention: secret scanning and pre-commit hooks so this is blocked at the source, and the deeper fix, move to short-lived role-based credentials, tokens that expire in an hour, so there is no long-lived key to leak in the first place. I would also ask why a static key existed at all.

Rotates/revokes first, treats as compromised Knows deleting the commit is insufficient Checks audit logs for misuse + adds prevention

Then they probe: Why is deleting the commit and rotating different in priority?

Practise this one
Senior

How would you secure a growing multi-account AWS organization to limit blast radius?

What most people say

I would set up IAM roles and policies carefully in the account and enable MFA.

Per-account IAM is necessary but not the answer to multi-account governance. It misses the structural controls, account separation, SCP guardrails, centralized logging, landing zone, that actually contain blast radius at scale.

The structure behind a strong answer

  1. 1

    Separate accounts for isolation. Use multiple accounts (per team, per environment) as hard blast-radius boundaries, so a compromise or mistake in one does not reach others.

  2. 2

    Guardrails with SCPs. Use AWS Organizations Service Control Policies to set org-wide guardrails: deny risky actions, restrict regions, prevent disabling logging, regardless of account-level IAM.

  3. 3

    Centralize logging and identity. Aggregate CloudTrail and config into a dedicated logging account, and use single sign-on with short-lived roles instead of per-account IAM users.

  4. 4

    Baseline and protect root. A landing zone applies a secure baseline to every new account; lock down and MFA the root user, and never use it for daily work.

  5. 5

    Detect and respond. Turn on org-wide threat detection and config compliance (GuardDuty, Security Hub, Config) so drift and threats are visible centrally.

What gets you hired

At scale the unit of isolation is the account, so I use a lot of them, often one per team per environment, which for 10 teams across dev, staging and prod is already 30 plus accounts. That sounds like a lot until you remember an account is a hard blast-radius boundary: a mistake or compromise in one cannot reach another. Over the top I put guardrails with Organizations SCPs, org-wide denies that no account-level IAM can override, like blocking anyone from disabling CloudTrail, restricting to the 2 or 3 regions we actually approve, or denying risky actions outright. I centralize logging into a dedicated, locked-down account so CloudTrail and Config data cannot be tampered with by a compromised workload account, and I use single sign-on with short-lived assumed roles, typically 1 hour sessions, instead of long-lived IAM users scattered everywhere. Every new account comes from a landing zone that applies the secure baseline automatically, so provisioning takes minutes rather than a week of manual setup, and the root user of each account is MFA-protected and never used for daily work. Finally I make it observable: GuardDuty for threat detection, Security Hub and Config for posture and drift, aggregated org-wide so I can see and respond centrally. The theme is structure and guardrails, not fixing things one resource at a time.

Account separation as blast-radius boundary SCP guardrails above IAM Centralized logging + SSO + landing zone + detection

Then they probe: What can an SCP do that account-level IAM cannot?

Practise this one
Principal

How do you threat model a new system? Walk me through how you think about what could go wrong.

What most people say

I would think about how a hacker might attack the system and add protections for those cases.

Ad hoc and unstructured, "think about how a hacker might attack" misses things. Principal-level threat modeling is a repeatable method: map trust boundaries, apply STRIDE, prioritize by risk, which this answer lacks.

The structure behind a strong answer

  1. 1

    Map the system. Diagram the components, data flows, and trust boundaries: where does data enter, where does trust change (internet to app, app to database, third parties).

  2. 2

    Identify what is valuable. Name the assets worth protecting (PII, credentials, money, availability) so you can focus effort where the impact is highest.

  3. 3

    Enumerate threats systematically. Walk a framework like STRIDE at each trust boundary: Spoofing, Tampering, Repudiation, Information disclosure, Denial of service, Elevation of privilege. It forces breadth instead of guessing.

  4. 4

    Prioritize by risk. Rank threats by likelihood times impact, you cannot fix everything, so focus on high-risk realistic attacks, not exotic ones.

  5. 5

    Mitigate and revisit. Design controls for the top risks, accept or transfer the rest explicitly, and treat the model as living, revisit it as the system changes.

What gets you hired

I treat it as a structured exercise, not a brainstorm, because ad hoc thinking misses whole categories. First I map the system, components, data flows, and above all the trust boundaries, the points where data crosses from less trusted to more trusted, internet to app, app to database, calls to third parties. In a typical service that is 4 or 5 boundaries, which is a tractable number to work through in a 90 minute session with the team. Then I identify the assets actually worth protecting, PII, credentials, money, availability, so effort lands where impact is highest. Then I enumerate threats systematically rather than guessing, walking all 6 STRIDE categories at each boundary, spoofing, tampering, repudiation, information disclosure, denial of service, and elevation of privilege, which forces breadth. I prioritize by likelihood times impact, because you cannot mitigate everything, and I focus on realistic high risk attacks over exotic ones. For the top few, usually the top 5, I design specific controls, and for the rest I explicitly accept or transfer the risk rather than quietly ignore it. And I treat the model as living, revisiting it whenever the architecture changes, because one new integration can open a new boundary. The goal is to reason about risk before we build, so security is designed in rather than bolted on.

Maps data flows and trust boundaries Uses a framework like STRIDE for breadth Prioritizes by risk and treats it as living

Then they probe: What is a trust boundary and why focus threat modeling there?

Practise this one

Networking

26 questions · Foundation, Junior, Mid, Senior
Foundation

What is a VPC, and what is a subnet within it? Why split a network into subnets at all?

What most people say

A VPC is your network in the cloud and a subnet is a smaller network inside it.

It is technically not wrong but stops at definitions. It never explains the public versus private split, which is the whole point of subnetting in practice.

The structure behind a strong answer

  1. 1

    Define the VPC. A VPC is your own isolated private network inside the cloud, with a range of IP addresses you control, where your resources live.

  2. 2

    Define the subnet. A subnet is a slice of that IP range. You place resources into subnets to group and control them.

  3. 3

    Explain public versus private. A public subnet can reach the internet through a gateway; a private subnet cannot be reached directly from the internet.

  4. 4

    Map the tiers. A common pattern is web servers in a public subnet and databases in a private subnet, so the database is never directly exposed.

  5. 5

    Add the controls. Traffic between and into subnets is filtered by firewall rules, so isolation is enforced, not just assumed by layout.

What gets you hired

A VPC is my own isolated private network in the cloud. I pick an address block, typically something like 10.0.0.0/16, which gives me about 65,000 addresses, and every resource I create lives inside that boundary, separate from other customers. A subnet is a slice of that range, say a /24 with 256 addresses, and subnets are how I control where things sit. In a normal layout I would carve out 6 subnets, a public and a private one in each of 3 availability zones. The key distinction is public versus private. A public subnet has a route to an internet gateway, so that is where internet-facing things like load balancers and web servers go. A private subnet has no direct route from the internet, so databases and backend services go there. The database is reachable from the web tier but never directly exposed. On top of the layout, firewall rules enforce the isolation. My database security group only allows port 5432 from the web tier's security group, nothing else, so even if something lands in the wrong subnet it still cannot reach the data. Layout plus rules together is what makes the isolation real rather than just tidy.

frame the VPC as an isolated network explain public versus private subnets place databases in private subnets

Then they probe: How does a resource in a private subnet reach the internet for updates if it needs to?

Practise this one
Foundation

What is the difference between TCP and UDP, and when would you choose each?

What most people say

TCP is reliable and UDP is fast, so you usually want TCP.

It memorizes the adjectives without the why, and "usually want TCP" misses that UDP is the right call for real-time and DNS. No connection to actual workloads.

The structure behind a strong answer

  1. 1

    TCP is a conversation. Connection-oriented: a handshake first, then reliable, ordered delivery with retransmission, flow control, and congestion control.

  2. 2

    UDP is a postcard. Connectionless: fire and forget. No handshake, no ordering, no retransmission. Lower overhead and latency, but the app handles loss.

  3. 3

    Map TCP to correctness. Use TCP when every byte must arrive in order: HTTP, SSH, database connections, file transfer.

  4. 4

    Map UDP to timeliness. Use UDP when fresh-but-lossy beats late-but-complete: live video/voice, gaming, DNS queries, DHCP. QUIC/HTTP3 even rebuild reliability on top of UDP.

What gets you hired

TCP is connection-oriented. It does a three-way handshake, which costs a full round trip before any data moves, and then guarantees reliable, ordered delivery with acknowledgements, retransmission, and congestion control. You are paying some latency and about 20 bytes of header per segment for correctness. UDP is connectionless, no handshake and no guarantees, with an 8 byte header, so it is lighter and lower latency, but the application has to tolerate loss and reordering itself. I choose TCP when every byte matters and the order matters, so HTTP, SSH, database connections. I choose UDP when timeliness beats completeness, so live voice and video, gaming, and DNS, where a single query and response fit in one small packet on port 53 and retrying is cheaper than setting up a connection. In a voice call, losing about 1 percent of packets is barely audible, but waiting 200 milliseconds for a retransmission is a jarring gap, so dropping is the right call. QUIC is the interesting middle ground. It runs over UDP to get that lightness, then rebuilds reliability and congestion control in user space, and it folds the transport and TLS handshakes together so a connection can be up in 1 round trip.

Ties each protocol to real workloads Knows DNS and real-time use UDP Mentions ordering/retransmission/congestion control

Then they probe: Why does DNS mostly use UDP?

Practise this one
Foundation

What happens, step by step, when you type a URL into a browser and press enter?

What most people say

The browser sends a request to the server and the server sends back the web page.

It collapses the whole stack into one step. It shows no awareness of DNS, the TCP handshake, or TLS, which is exactly what the question is probing.

The structure behind a strong answer

  1. 1

    Resolve the name. The browser checks its cache, then asks a DNS resolver to turn the hostname into an IP (recursive lookup if not cached).

  2. 2

    Open a connection. It opens a TCP connection to that IP on port 443 (or 80) via the three-way handshake: SYN, SYN-ACK, ACK.

  3. 3

    Secure it (HTTPS). For https, a TLS handshake negotiates a cipher, validates the server certificate, and derives a shared session key.

  4. 4

    Send the request. The browser sends an HTTP request (GET /path with headers). The server, often behind a load balancer or CDN, processes it and returns a response.

  5. 5

    Render. The browser parses the HTML, then fetches CSS, JS, and images (often over the same connection), and paints the page.

What gets you hired

First the browser turns the hostname into an IP address: it checks its own cache, then asks a DNS resolver, which walks root, TLD, and authoritative servers if needed. With the IP, it opens a TCP connection on port 443 using the three-way handshake. For https it then runs a TLS handshake, validating the certificate against a trusted CA and agreeing on a session key. Now it sends the HTTP request, GET with headers, which often hits a CDN or load balancer before the origin. The server returns a response, and the browser parses the HTML and pulls in CSS, JS, and images to render the page. I can go deeper at any layer, ARP and routing under TCP, or caching at the DNS and CDN level.

Names DNS, TCP, TLS, HTTP in order Mentions caching/CDN Can go deeper on any layer when pushed

Then they probe: Where can caching short-circuit these steps?

Practise this one
Foundation

How does DNS resolution actually work, and what is the difference between an A record and a CNAME?

What most people say

DNS maps a domain name to an IP address so you do not have to remember numbers.

True but shallow. It never explains the recursive chain, record types, or TTL, so it cannot help when DNS is the cause of an incident, which is common.

The structure behind a strong answer

  1. 1

    The resolver does the walking. Your machine asks a recursive resolver. If it has no cached answer, it queries a root server, then the TLD server (.com), then the domain authoritative server.

  2. 2

    A record. An A record maps a name directly to an IPv4 address (AAAA for IPv6). It is the leaf of the lookup.

  3. 3

    CNAME. A CNAME is an alias: it points one name at another name, which must then be resolved. Useful for pointing many names at one canonical host.

  4. 4

    TTL and caching. Every record has a TTL; resolvers cache it for that long. Low TTL means faster propagation but more queries; high TTL means stale answers linger after a change.

What gets you hired

When I look up a name, my machine asks a recursive resolver. If nothing is cached, the resolver walks the hierarchy: a root server points it to the TLD server for .com, which points to the domain authoritative nameserver, which returns the record. An A record is the leaf, it maps the name to an IPv4 address (AAAA for IPv6). A CNAME is an alias, it points one name to another name that then has to be resolved, which is handy for aliasing many subdomains to one canonical host. Every record carries a TTL, so resolvers cache the answer for that long. That TTL is the practical gotcha: set it low before a migration so changes propagate fast, because a high TTL means the old IP lingers in caches well after you have changed it.

Describes the recursive root/TLD/authoritative walk A vs CNAME (address vs alias) Knows TTL drives caching/propagation

Then they probe: You changed a DNS record but some users still hit the old server. Why?

Practise this one
Foundation

What is the difference between IPv4 and IPv6, and why does IPv6 exist?

What most people say

IPv6 is the newer version of IP with more addresses than IPv4.

Technically true but stops at the headline. It misses the exhaustion driver, why NAT existed, and that IPv6 removes the need for NAT, which is the part that affects real network design.

The structure behind a strong answer

  1. 1

    The size difference. IPv4 is 32-bit, about 4.3 billion addresses, written as four dotted decimals. IPv6 is 128-bit, an effectively unlimited space, written as eight hex groups.

  2. 2

    Why it exists. IPv4 addresses ran out. NAT was the stopgap that let many private hosts share one public IPv4; IPv6 removes the scarcity entirely.

  3. 3

    No NAT needed. With IPv6 every device can have a globally unique address, so NAT is not required for address conservation; isolation comes from firewalls instead.

  4. 4

    Migration is gradual. They are not interoperable, so the world runs dual-stack (both at once) during the long transition, with mechanisms to bridge.

What gets you hired

IPv4 is a 32-bit address space, about 4.3 billion addresses written as four dotted decimals, and we ran out of them. NAT was the workaround: many private hosts share a single public IPv4 for outbound traffic. IPv6 is 128-bit, written as hex groups, with an effectively unlimited supply, so it removes the scarcity that NAT was patching. The practical consequence is that with IPv6 every device can have its own globally unique address and you do not need NAT for conservation, so isolation has to come deliberately from firewalls rather than as a NAT side effect. The two are not interoperable, so in practice we run dual-stack during the transition, serving both until IPv6 is universal.

Knows 32-bit vs 128-bit and the exhaustion driver Connects IPv4 scarcity to NAT Knows IPv6 removes the need for NAT

Then they probe: If IPv6 has enough addresses for everything, where does network isolation come from?

Practise this one
Junior

An EC2 instance in a private subnet needs to download OS updates from the internet. Walk me through how you make that work, and why.

What most people say

I would give it a public IP / put it in a public subnet so it can reach the internet.

That defeats the purpose, it makes the instance internet-reachable, the opposite of "private". It shows the candidate conflates outbound access with inbound exposure, the exact thing the question tests.

The structure behind a strong answer

  1. 1

    Define what makes a subnet "private". A subnet is private because its route table has no route to an Internet Gateway. So instances there cannot be reached from, or initiate to, the internet directly.

  2. 2

    Separate inbound from outbound. The instance does not need to be reachable from the internet, it needs to initiate outbound to fetch updates. Those are different problems.

  3. 3

    Add a NAT gateway in a public subnet. Put a NAT gateway in a public subnet (which does route to the IGW). Point the private subnet’s route table at the NAT for 0.0.0.0/0.

  4. 4

    Trace the path. Outbound traffic goes instance -> NAT -> IGW -> internet; replies return the same way. The instance stays unreachable from outside because nothing can initiate inbound through a NAT.

  5. 5

    Mention the cheaper/safer alternatives. For AWS-service traffic (S3, ECR), a VPC/gateway endpoint avoids the NAT entirely, which removes that data-transfer cost and keeps traffic off the internet.

What gets you hired

The subnet is private because its route table has no route to an Internet Gateway, so nothing inbound or outbound flows directly. But the instance only needs to initiate outbound, not be reachable. So I add a NAT gateway in a public subnet and point the private route table’s default route at it. Traffic flows instance to NAT to IGW out, and replies come back the same path, while the instance stays unreachable from the internet because you cannot initiate inbound through a NAT. If it is just pulling from S3 or ECR, I would use a VPC endpoint instead to skip the NAT cost and keep it off the public internet.

Defines private subnet by its route table Separates inbound vs outbound reachability Reaches for a VPC endpoint for AWS-service traffic

Then they probe: The instance still cannot reach the internet after you add the NAT. Where do you look?

Practise this one
Junior

In AWS, what is the difference between a security group and a network ACL, and when would you reach for each?

What most people say

They are both firewalls, you can use either one, they do the same thing.

It misses the two distinctions that matter operationally: stateful versus stateless, and instance versus subnet. Treating a NACL like a security group leads to allowing inbound but forgetting the return rule, then a confusing one-way connectivity outage.

The structure behind a strong answer

  1. 1

    State where each one sits. A security group attaches to an instance or ENI (the resource level). A network ACL attaches to a subnet, so it filters everything entering or leaving that subnet.

  2. 2

    Stateful vs stateless. Security groups are stateful: allow an inbound request and the response is allowed back automatically. NACLs are stateless: you must explicitly allow the return traffic, usually on ephemeral ports.

  3. 3

    Allow vs deny. Security groups only have allow rules (deny by default). NACLs support both allow and deny rules and evaluate them in numbered order, so you can block a specific IP.

  4. 4

    Pick the tool. Use security groups as the everyday primary control for what a resource accepts. Reach for a NACL for a coarse subnet-wide rule, like denying a known-bad CIDR across an entire subnet.

What gets you hired

A security group sits on the instance or ENI and is stateful, so if I allow inbound TCP 443 the reply is allowed back out automatically and I never write a return rule. A network ACL sits on the subnet and is stateless, so I have to explicitly allow the return traffic too, which in practice means opening the ephemeral port range, 1024 to 65535, outbound. Forget that and connections just hang in one direction, which is a miserable bug to chase. Security groups are allow only and deny everything by default, so there is no deny rule to write. NACLs do both allow and deny, and they are evaluated in numbered order, lowest number first, so a deny at rule 90 wins over an allow at rule 100. That ordering is exactly what makes them useful for an explicit block. Day to day I rely on security groups for what each resource accepts, and I only reach for a NACL when I need a subnet wide rule, like denying a single bad CIDR that is scanning us.

Calls security groups stateful and NACLs stateless Knows the instance vs subnet layer Knows NACLs support deny and are evaluated in order

Then they probe: You added an inbound allow rule on a NACL and traffic still fails. What is the classic mistake?

Practise this one
Junior

At a high level, what does DNS do, and what is Route 53's role in AWS?

What most people say

DNS is the internet's phone book and Route 53 is just where AWS stores your domain.

The phone book line is fine but it stops there. It misses that Route 53 is authoritative DNS with routing policies and health checks, and never mentions TTL, which is what bites people during a DNS-based failover or migration.

The structure behind a strong answer

  1. 1

    Define DNS. DNS translates a human name like app.example.com into the address a client actually connects to, through records (A, AAAA, CNAME, and so on) served by authoritative name servers.

  2. 2

    Place Route 53. Route 53 is AWS's managed, authoritative DNS service. You create a hosted zone for your domain and define the records there, and AWS runs the name servers.

  3. 3

    Note routing and health checks. Route 53 does more than plain records: routing policies (latency, weighted, failover, geolocation) and health checks let it steer traffic, for example failing over to a backup endpoint when the primary is unhealthy.

  4. 4

    Respect TTL. Records cache for their TTL, so a change is not instant everywhere. Before a cutover, lower the TTL ahead of time so clients pick up the new record quickly.

What gets you hired

DNS is the lookup layer that turns a name like app.example.com into the address a client connects to, using records served by authoritative name servers. Route 53 is AWS's managed authoritative DNS: I create a hosted zone for the domain, define the records, and AWS runs the name servers. Beyond plain records, it offers routing policies, latency, weighted, failover, geolocation, plus health checks, so it can steer traffic or fail over to a backup endpoint when the primary is unhealthy. The thing to respect is TTL: records cache for their TTL, so changes are not instant. Before a cutover I lower the TTL in advance so clients pick up the new record fast.

Defines DNS as name-to-address resolution Knows Route 53 is authoritative DNS plus routing policies Brings up TTL and caching for cutovers

Then they probe: You changed a record to point at a new server but half your users still hit the old one. Why?

Practise this one
Junior

In Azure, how would you control which traffic can reach a VM, and how do network security groups work?

What most people say

You open the ports you need in the Azure firewall and that is it.

It vaguely points at the right idea but never names NSGs, never mentions rule priority or direction, and ignores that the first matching rule wins. Without the priority model you cannot reason about why an allow rule is being overridden by a deny.

The structure behind a strong answer

  1. 1

    Name the primitive. In Azure you use a network security group (NSG): a set of inbound and outbound rules that allow or deny traffic based on source, destination, port, and protocol.

  2. 2

    Explain where it attaches. An NSG can attach to a subnet (covers every resource in it) or to a VM's network interface (just that VM), or both, in which case both sets of rules are evaluated.

  3. 3

    Explain rule evaluation. Each rule has a priority number; lower numbers are evaluated first, and the first matching rule wins. So a high-priority deny can override a lower-priority allow.

  4. 4

    Note the defaults. Azure adds default rules, including allowing traffic within the virtual network and denying all other inbound from the internet. You layer your own higher-priority rules on top, like allowing 443 from a specific source.

What gets you hired

In Azure I would use a network security group, which is a list of inbound and outbound rules that allow or deny traffic by source, destination, port, and protocol. I can attach it to the subnet to cover everything in it, or to the VM's NIC for just that machine, or both, and then both rule sets apply. Each rule has a priority number: lower numbers are evaluated first and the first match wins, so a high-priority deny beats a lower-priority allow. Azure ships default rules that allow intra-VNet traffic and deny inbound from the internet, so I layer my own higher-priority rules on top, for example allowing 443 from a specific source range and leaving everything else denied.

Names NSGs as the Azure control Knows rules have priority and first match wins Knows an NSG attaches at subnet or NIC level

Then they probe: You added an allow rule for port 22 but SSH is still blocked. What would you check first?

Practise this one
Junior

Explain CIDR notation. What does a /24 mean, and how many hosts does it hold?

What most people say

A /24 is a subnet with 256 addresses, the standard size you use for most things.

It gets the count right but stops there: no host-bit reasoning, no usable-versus-total distinction, and "use it for everything" is how you run out of space or waste it.

The structure behind a strong answer

  1. 1

    The slash is network bits. In 10.0.0.0/24, the /24 means the first 24 bits are the network prefix; the remaining 8 bits are for hosts.

  2. 2

    Count the addresses. 32 minus 24 is 8 host bits, so 2^8 = 256 addresses (10.0.0.0 to 10.0.0.255).

  3. 3

    Usable is fewer. Normally 2 are reserved (network and broadcast), so 254 usable. Cloud providers reserve a few more per subnet (AWS reserves 5), so a /24 gives 251 in AWS.

  4. 4

    Smaller number, bigger block. A /16 is 65,536 addresses, a /28 is 16. Lower prefix = larger network. Plan ranges so subnets do not overlap and leave room to grow.

What gets you hired

CIDR notation says how many leading bits are the network prefix. A /24 means 24 network bits and 8 host bits, so 2^8 = 256 total addresses, for example 10.0.0.0 through 10.0.0.255. Usable is fewer: the network and broadcast addresses are reserved, leaving 254, and cloud providers reserve extra per subnet, AWS reserves 5, so you actually get 251. The key intuition is that a smaller number after the slash is a bigger block: a /16 is 65,536 addresses, a /28 is only 16. When I design, I size subnets to the workload with headroom, keep the ranges non-overlapping, and avoid one giant subnet because that hurts blast-radius isolation.

Reasons from host bits (2^(32-prefix)) Distinguishes total vs usable Knows smaller prefix = bigger network

Then they probe: How many usable hosts in a /28?

Practise this one
Junior

What is the difference between a public and a private IP address, and what does NAT do?

What most people say

Private IPs are internal and public IPs are external; NAT connects them.

Directionally right but vague. It misses that private ranges are non-routable and reusable, and it does not explain that outbound NAT keeps hosts unreachable from outside, which is the security point.

The structure behind a strong answer

  1. 1

    Private IPs are not routable. RFC 1918 ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16) are reusable inside any network and never routed on the public internet.

  2. 2

    Public IPs are globally unique. Public addresses are routable on the internet and globally unique, which is why they are a scarce, allocated resource.

  3. 3

    NAT bridges the two. Network Address Translation rewrites the private source IP to a public one on the way out (and back), so many private hosts share one public IP for outbound traffic.

  4. 4

    Direction matters. Outbound NAT (a NAT gateway) lets private hosts reach the internet without being reachable from it. Inbound exposure needs an explicit public IP, load balancer, or port forward.

What gets you hired

Private IPs come from the RFC 1918 ranges, 10.x, 172.16-31.x, and 192.168.x. They are reusable inside any private network and are never routed on the public internet, which is why every home and VPC can use 10.0.0.0/8. Public IPs are globally unique and routable, so they are scarce and allocated. NAT bridges them: when a private host makes an outbound connection, the NAT device rewrites the source to a public IP and tracks the mapping so replies come back. The important property is direction. A NAT gateway gives private instances outbound internet access, for OS updates say, without making them reachable from the internet. To accept inbound traffic you need something public on purpose, a public IP, a load balancer, or a port forward.

Names the RFC 1918 ranges Knows private = non-routable/reusable Explains outbound NAT keeps hosts unreachable inbound

Then they probe: Why can a private instance download updates but not receive inbound connections through a NAT gateway?

Practise this one
Junior

At a high level, what happens during a TLS/HTTPS handshake, and what makes the connection secure?

What most people say

The data gets encrypted with the SSL certificate so nobody can read it.

It conflates the certificate with the encryption and ignores identity. The cert proves who the server is; the encryption uses a negotiated session key. Missing that is a real gap for anyone managing TLS.

The structure behind a strong answer

  1. 1

    Agree on terms. Client and server exchange hellos and pick a TLS version and cipher suite they both support.

  2. 2

    Prove identity. The server presents its certificate; the client validates it against a trusted Certificate Authority and checks the hostname and expiry. This stops impersonation.

  3. 3

    Exchange a key. Using asymmetric crypto (or an ephemeral key exchange like ECDHE), both sides derive a shared symmetric session key without sending it in the clear.

  4. 4

    Switch to fast crypto. The rest of the session uses that symmetric key, which is much faster than asymmetric. TLS 1.3 streamlines this to a single round trip.

What gets you hired

The handshake does two jobs: prove identity and set up encryption. Client and server first agree on a TLS version and cipher. The server then presents its certificate, and the client validates it against a trusted CA and checks the hostname and expiry, that is what stops a man in the middle from impersonating the site. Then they do a key exchange, typically ephemeral ECDHE, so both sides derive the same symmetric session key without ever sending it in the clear. From there the actual data uses that symmetric key, which is far faster than asymmetric crypto. So security comes from two things together: the certificate proves who you are talking to, and the negotiated session key keeps the traffic confidential and tamper-evident. TLS 1.3 does this in one round trip.

Separates identity (cert) from encryption (session key) Mentions CA validation/hostname check Knows asymmetric sets up a symmetric key

Then they probe: What does a Certificate Authority actually vouch for?

Practise this one
Junior

What is the difference between a forward proxy and a reverse proxy?

What most people say

A proxy sits in the middle and forwards traffic between the client and server.

It describes "a proxy" generically and never distinguishes the two. The whole question is which side it fronts, and the answer does not pick a side.

The structure behind a strong answer

  1. 1

    It is about which side it fronts. A forward proxy sits in front of clients and makes outbound requests on their behalf. A reverse proxy sits in front of servers and receives inbound requests on their behalf.

  2. 2

    Forward proxy use. Corporate egress control, content filtering, caching, or anonymizing the client. The destination server sees the proxy, not the client.

  3. 3

    Reverse proxy use. TLS termination, load balancing, caching, compression, routing by path/host, and hiding the backend topology. The client thinks the proxy is the server.

  4. 4

    Cloud reality. Most things you run, an ALB, Nginx ingress, an API gateway, a CDN, are reverse proxies.

What gets you hired

The difference is which side it represents. A forward proxy sits in front of clients and makes requests outward on their behalf, so the destination only ever sees the proxy's IP, not the 500 laptops behind it. That is a corporate egress proxy, a content filter, an anonymizer. A reverse proxy sits in front of servers and accepts inbound requests on their behalf, so the client believes the proxy is the server. It is the thing that terminates TLS on port 443 and forwards to backends on plain 8080, load balances, caches, compresses, and routes by host or path while hiding the backend entirely. In cloud work almost everything I touch is a reverse proxy: an application load balancer, an Nginx ingress, an API gateway, a CDN holding static assets at the edge with something like a 60 second TTL. In one setup I worked on, 3 services sat behind a single ingress and clients only ever knew 1 hostname. So the short version is that a forward proxy protects and controls clients, and a reverse proxy protects and scales servers.

Frames it as which side it fronts Gives correct use cases for each Knows cloud LBs/ingress/CDN are reverse proxies

Then they probe: Is an application load balancer a forward or reverse proxy?

Practise this one
Junior

What is the difference between a stateful and a stateless firewall? How does that relate to AWS security groups and NACLs?

What most people say

Stateful firewalls are smarter and stateless ones are simpler, so security groups are the better choice.

It frames a behavioral difference as good versus bad. The real point is connection tracking and the ephemeral-port return-traffic gotcha on stateless NACLs, which is missed entirely.

The structure behind a strong answer

  1. 1

    Stateful tracks connections. A stateful firewall remembers established connections, so if it allows an outbound request, the matching reply is automatically allowed back. You only write the rule for the initiating direction.

  2. 2

    Stateless evaluates each packet. A stateless firewall judges every packet on its own with no memory, so you must explicitly allow both directions, including the return traffic.

  3. 3

    Security groups are stateful. AWS security groups track state: allow inbound 443 and the response goes out automatically. You do not write a return rule.

  4. 4

    NACLs are stateless. AWS NACLs are stateless, so you must allow the return traffic too, typically on the ephemeral port range (1024-65535), which people forget.

What gets you hired

A stateful firewall tracks the state of connections, so when it permits an outbound or inbound flow, the matching return traffic is allowed automatically, you only write the rule for the direction that initiates. A stateless firewall has no memory and evaluates each packet independently, so you have to allow both directions explicitly. In AWS this maps directly: security groups are stateful, so if I allow inbound on 443 the response leaves without a separate rule. NACLs are stateless, so I have to allow the return traffic as well, and since responses come back on ephemeral ports I need to open 1024-65535 outbound, which is the classic mistake that makes NACLs seem broken. So they are not better or worse, they operate at different layers with different rules, and the stateless one needs explicit return-path rules.

Explains connection tracking Maps SG=stateful, NACL=stateless Knows the ephemeral-port return-traffic rule

Then they probe: You allowed inbound 443 on a NACL but responses fail. Why?

Practise this one
Junior

How does a host decide where to send a packet? Explain routing tables, the default gateway, and longest-prefix match.

What most people say

The packet goes to the router and the router sends it to the internet.

It skips the decision logic entirely: the routing-table lookup, local-versus-remote, the default route, and longest-prefix match. That logic is exactly what you need when an instance cannot reach the internet.

The structure behind a strong answer

  1. 1

    Consult the routing table. Every host and router has a routing table mapping destination prefixes to a next hop or interface. For each packet it looks up the destination IP.

  2. 2

    Local versus remote. If the destination is on the same subnet, the host delivers directly (resolving the MAC via ARP). If not, it sends to a router.

  3. 3

    Default gateway. The default route, 0.0.0.0/0, points to the default gateway and catches anything with no more specific match, typically the path to the internet.

  4. 4

    Longest-prefix match. When multiple routes match, the most specific one wins. A /24 route beats the /0 default, so specific routes override the catch-all.

What gets you hired

For every packet the host looks up the destination IP in its routing table, which maps destination prefixes to a next hop. If the destination is on the same subnet, it delivers directly, using ARP to find the MAC address. If it is somewhere else, it hands the packet to a router. The catch-all is the default route, 0.0.0.0/0, which points at the default gateway and matches anything without a more specific entry, normally the way out to the internet. When several routes could match, longest-prefix match decides: the most specific prefix wins, so a /24 entry takes precedence over the /0 default. This is exactly the mental model I use when a cloud instance has no internet: I check whether the subnet route table has a 0.0.0.0/0 route to an internet or NAT gateway, because a missing default route is the usual cause.

Describes the routing-table lookup Knows 0.0.0.0/0 is the default route Knows longest-prefix match picks the most specific

Then they probe: An instance in a subnet cannot reach the internet though the security group is open. What route do you check?

Practise this one
Mid

An Azure VM cannot connect to an Azure SQL Database. Walk me through how you debug the networking.

What most people say

I would restart the VM and the database and see if it connects.

Networking failures are deterministic, not flaky; restarting changes nothing and shows no mental model of the path. It skips the SQL firewall and NSGs, the actual usual causes.

The structure behind a strong answer

  1. 1

    Confirm what is failing. Is it DNS resolution, a TCP connect on 1433, or an auth error? "Cannot connect" needs narrowing first, the failure point tells you which layer to inspect.

  2. 2

    Check the SQL side firewall. Azure SQL has its own server-level firewall. By default it denies; the VM’s outbound IP (or "allow Azure services", or a VNet rule) must be permitted.

  3. 3

    Check the network path / NSGs. Network Security Groups on the VM’s subnet/NIC must allow outbound 1433. Confirm the VM has a route to the destination.

  4. 4

    Prefer a private endpoint. For production, put Azure SQL behind a Private Endpoint so it resolves to a private VNet IP, traffic never traverses the public internet, and you debug VNet/DNS, not public firewall rules.

  5. 5

    Then rule out auth. Once the TCP path is open, a remaining failure is credentials/Azure AD/permissions, not networking.

What gets you hired

First I’d narrow the failure: DNS, a TCP connect on 1433, or auth, because each points to a different layer. Azure SQL has its own server firewall that denies by default, so I’d confirm the VM’s outbound IP or a VNet rule is allowed. Then the network path: the NSG on the VM’s subnet/NIC must allow outbound 1433 and there must be a route. For production I’d actually put SQL behind a Private Endpoint so it resolves to a private IP and never touches the public internet, which removes the public-firewall guesswork. Once the TCP path is open, any remaining failure is auth, credentials or Azure AD, not networking.

Narrows the failure layer first Knows Azure SQL has its own firewall Reaches for a Private Endpoint + private DNS

Then they probe: You added a Private Endpoint but the VM still resolves the DB to a public IP. Why?

Practise this one
Mid

What is the difference between a layer 4 and a layer 7 load balancer, and when would you use each?

What most people say

Layer 7 is newer and better, so you should use a layer 7 load balancer.

It treats it as a version upgrade rather than a trade-off. L4 is the right choice for non-HTTP traffic and ultra-low-latency paths; "L7 is better" misses what each layer can actually see.

The structure behind a strong answer

  1. 1

    L4 works on connections. A layer 4 load balancer routes by IP and port (TCP/UDP). It does not read the payload, so it is fast and protocol-agnostic.

  2. 2

    L7 understands the request. A layer 7 load balancer reads the application protocol (HTTP), so it can route by host, path, header, or cookie, and terminate TLS.

  3. 3

    Pick L7 for HTTP smarts. Use L7 when you need path-based routing (/api vs /web), TLS termination, sticky sessions, or WAF integration. It is the default for web apps.

  4. 4

    Pick L4 for raw speed or non-HTTP. Use L4 for very high throughput, low latency, non-HTTP protocols, or when you must preserve the client IP and pass TLS straight through.

What gets you hired

It comes down to what the load balancer can see. A layer 4 load balancer works at the transport layer, routing by IP and port without inspecting the payload, so it is fast, cheap, and works for any protocol. A layer 7 load balancer understands the application protocol, usually HTTP, so it can route by hostname, URL path, header, or cookie, terminate TLS, do sticky sessions, and plug into a WAF. I reach for L7 for normal web apps where I want to send /api to one target group and /static to another, or centralize TLS. I reach for L4 when I need extreme throughput and low latency, when the protocol is not HTTP, or when I need to preserve the original client IP and pass TLS through untouched. So it is a deliberate trade-off, not newer-is-better.

Frames it as "what can it see" Knows L7 enables path/host routing + TLS termination Knows L4 for non-HTTP/low-latency/client-IP

Then they probe: You need to route /api and /app to different services on one domain. Which layer?

Practise this one
Mid

Explain the difference between latency, bandwidth, and throughput. Why might a high-bandwidth link still feel slow?

What most people say

Bandwidth is speed and latency is delay; if it is slow you need more bandwidth.

The reflex to add bandwidth is the trap the question sets. Many slow experiences are latency-bound or loss-bound, where more bandwidth changes nothing. It also blurs throughput into "speed".

The structure behind a strong answer

  1. 1

    Latency is delay. How long one packet takes to get there, often measured as round-trip time. Bounded by distance (speed of light), hops, and queuing.

  2. 2

    Bandwidth is capacity. The maximum data rate the link can carry, the width of the pipe, not the speed of any one packet.

  3. 3

    Throughput is what you actually get. The real achieved rate, which can be well below bandwidth due to latency, packet loss, and protocol limits.

  4. 4

    Why fat pipes feel slow. A chatty app doing many sequential round trips is latency-bound: each request waits a full RTT, so more bandwidth does not help. TCP window size and loss also cap throughput over high-latency links.

What gets you hired

Latency is the delay for a packet to travel, usually measured as round trip time, and it is bounded by distance and hops, London to Sydney sits around 250ms no matter what you pay. Bandwidth is the capacity of the link, say 1 gigabit per second. Throughput is what you actually achieve, which is often well below bandwidth because of latency, packet loss, and protocol overhead. So a high bandwidth link feels slow when the workload is latency bound. If a page makes 50 small sequential requests and each pays a 100ms round trip, that is 5 seconds of waiting, and widening the pipe changes nothing. The fix is cutting round trips, batching, caching, or moving the content closer with a CDN. Loss matters too. TCP backs off on loss, and the congestion window caps throughput on long fat links, which is why a transfer across the world can crawl at 20 megabits on a gigabit line while showing under 1 percent loss. So before anyone buys more bandwidth I measure which dimension is the bottleneck, because for most slow apps it is round trips, not capacity.

Cleanly separates the three terms Knows latency-bound workloads ignore bandwidth Mentions RTT/round trips, loss, or window size

Then they probe: An API call is slow only for users far from the region. What is going on and what helps?

Practise this one
Mid

A user reports your service is unreachable or slow. How do you systematically debug the network path?

What most people say

I would ping the server, and if it does not respond I would restart the service.

Ping alone tests only ICMP, which is often blocked, and restarting skips diagnosis entirely. It cannot distinguish a DNS problem from a firewall drop from an app error.

The structure behind a strong answer

  1. 1

    Check name resolution. dig or nslookup the hostname: does it resolve, and to the IP you expect? A stale or wrong DNS answer is a common culprit.

  2. 2

    Check reachability and path. ping for basic ICMP reachability and latency, then traceroute or mtr to see where along the path packets stop or slow down.

  3. 3

    Check the port and service. curl -v or nc -vz host port: is the port open and the app responding? Connection refused means nothing is listening; a timeout means a firewall/security group is dropping packets.

  4. 4

    Localize the layer. Separate DNS vs network vs firewall vs app. If DNS and routing are fine but connect times out, suspect a security group or NACL; if it connects but errors, it is the application.

  5. 5

    Check both ends. Confirm the service is healthy and listening on 0.0.0.0, the load balancer target is healthy, and the security rules allow the port end to end.

What gets you hired

I work the layers in order. First DNS: dig the hostname to confirm it resolves to the IP I expect, since a stale record is a frequent cause. Then reachability: ping for basic latency, and traceroute or mtr to see where on the path packets stall. Then the port: curl -v or nc -vz to the specific port, where connection refused means nothing is listening and a timeout usually means a firewall or security group is silently dropping packets. That refused-versus-timeout distinction localizes fast. Then I separate the layers: if routing is fine but connect times out, I check security groups and NACLs; if it connects but returns errors, the problem is the app, not the network. I also check both ends, that the service is listening on 0.0.0.0 not just localhost, the load balancer targets are healthy, and the rules permit the port the whole way through.

Layered DNS -> route -> port -> app routine Interprets refused vs timeout Checks both ends and the bind address

Then they probe: curl gives "connection refused" versus a hang/timeout. What does each tell you?

Practise this one
Mid

How does a load balancer know a backend is unhealthy, and how do you avoid dropping requests during a deploy or scale-in?

What most people say

The load balancer pings the servers and removes the ones that do not respond, then sends traffic to the rest.

It gets the basic idea but ignores the operational half: threshold tuning, a real readiness check, and draining. Without draining, every deploy drops in-flight requests, which is the actual question.

The structure behind a strong answer

  1. 1

    Health checks. The load balancer probes each target on an interval (an HTTP path or a TCP connect) and marks it healthy or unhealthy after a threshold of consecutive successes or failures.

  2. 2

    Tune the thresholds. Interval, timeout, and healthy/unhealthy counts trade speed against flapping: too aggressive and a slow GC pause ejects a good node; too slow and users hit a dead one for a while.

  3. 3

    Use a real readiness endpoint. The check should hit an endpoint that reflects real readiness (dependencies up, warm), not a trivial 200 that is healthy before the app can serve.

  4. 4

    Drain on removal. On deploy or scale-in, connection draining (deregistration delay) lets in-flight requests finish before the target is removed, so you do not cut active connections.

  5. 5

    Graceful shutdown. The app should stop accepting new work, finish in-flight requests, then exit, coordinated with the drain window, so nothing is killed mid-request.

What gets you hired

The load balancer runs periodic health checks against each target, an HTTP path or a TCP connect, and flips a target to unhealthy after a configured number of consecutive failures (and back after consecutive successes). I tune the interval, timeout, and thresholds to balance fast detection against flapping, because too aggressive a check will eject a healthy node during a brief GC pause. I also point the check at a real readiness endpoint that verifies dependencies, not a trivial handler that returns 200 before the app can actually serve. For deploys and scale-in, the key is connection draining, the deregistration delay: when a target is removed the LB stops sending new requests but lets in-flight ones finish before it disappears. I pair that with graceful shutdown in the app, stop accepting new work, drain, then exit, so a rollout never cuts an active request.

Explains thresholds/interval tuning Wants a real readiness endpoint Knows connection draining + graceful shutdown for deploys

Then they probe: Users get a burst of 5xx every time you deploy. What is likely missing?

Practise this one
Mid

What changed between HTTP/1.1, HTTP/2, and HTTP/3, and what problem does each solve?

What most people say

Each version is faster than the last; HTTP/3 is the newest and quickest.

It is a ranking, not an explanation. It misses multiplexing, head-of-line blocking, and the reason HTTP/3 abandoned TCP for QUIC/UDP, which is the actual content of the question.

The structure behind a strong answer

  1. 1

    HTTP/1.1. One request at a time per connection (pipelining never really worked), so browsers open many parallel TCP connections. Keep-alive reuses a connection across requests.

  2. 2

    HTTP/2. Multiplexes many concurrent streams over a single TCP connection, with header compression. Big win, but one lost TCP packet stalls all streams: TCP-level head-of-line blocking remains.

  3. 3

    HTTP/3. Runs over QUIC, which is built on UDP with independent streams, so a lost packet only stalls its own stream, removing TCP head-of-line blocking.

  4. 4

    QUIC extras. QUIC folds in the TLS handshake for a faster (often 1-RTT) setup and supports connection migration, so a connection survives a network change (wifi to cellular).

What gets you hired

HTTP/1.1 handles one request at a time per connection, so browsers worked around it by opening many parallel TCP connections, with keep-alive to reuse them. HTTP/2 multiplexes many streams over a single connection and compresses headers, which is a real efficiency gain, but because it still rides one TCP connection, a single lost packet stalls every stream, that is TCP-level head-of-line blocking. HTTP/3 fixes that by running over QUIC, which is built on UDP with independent streams, so a lost packet only delays its own stream, not all of them. QUIC also folds the TLS handshake into the connection setup for a faster start, often one round trip, and supports connection migration so a session can survive switching networks, like wifi to cellular. So each step targets a specific bottleneck: parallelism, then per-connection efficiency, then transport-level head-of-line blocking.

Knows HTTP/2 multiplexing + header compression Explains TCP head-of-line blocking Knows HTTP/3 = QUIC over UDP and why

Then they probe: If HTTP/2 multiplexes over one connection, why is there still head-of-line blocking?

Practise this one
Mid

How does a VPN work, and what is the difference between a full tunnel and a split tunnel?

What most people say

A VPN encrypts your internet so nobody can see what you are doing.

It reduces a VPN to "encryption" and ignores the tunnel-to-a-network model and the full-versus-split routing decision, which is the operational heart of the question.

The structure behind a strong answer

  1. 1

    Encapsulate and encrypt. A VPN wraps your traffic in an encrypted tunnel between two endpoints, so it traverses an untrusted network (the internet) privately, as if you were on the remote network.

  2. 2

    Common protocols. IPsec is the long-standing standard for site-to-site; WireGuard is a modern, simpler, faster option with a small codebase; both are widely used.

  3. 3

    Full tunnel. All of the client traffic goes through the VPN, so everything is inspected and protected, but it adds latency and load even for traffic that did not need the VPN.

  4. 4

    Split tunnel. Only traffic destined for the corporate/cloud network goes through the VPN; the rest goes direct. Faster and lighter, but that direct traffic bypasses corporate inspection.

What gets you hired

A VPN builds an encrypted tunnel between two endpoints and encapsulates your traffic inside it, so it can cross an untrusted network like the internet privately and you appear to be sitting on the remote network. IPsec is the established site to site standard, and it is what most cloud VPN gateways speak. WireGuard is the newer option, roughly 4,000 lines of code against tens of thousands for IPsec, simpler and faster, and it is gaining ground quickly. The routing choice is full versus split tunnel. Full tunnel pushes 100 percent of client traffic through the VPN, so everything is protected and subject to corporate inspection, but you pay the extra hop, often 20 to 40ms added, and you concentrate every user's video calls and software updates through one gateway. Split tunnel sends only traffic bound for the corporate or cloud ranges, say 10.0.0.0/8, through the tunnel and lets the rest go straight out. That is faster and far lighter on the gateway, but the direct traffic skips corporate security controls entirely. So it is a deliberate trade off, full tunnel when I need maximum control and inspection, split tunnel for performance when only specific networks need the private path.

Explains encapsulation + encrypted tunnel to a network Names IPsec/WireGuard Articulates the split vs full trade-off

Then they probe: A remote employee says everything is slow after connecting to the VPN. What setting likely helps?

Practise this one
Senior

How would you connect a cloud network to an on-prem datacenter, and what are the trade-offs between the options?

What most people say

I would set up a VPN between the cloud and the datacenter.

It picks a product without asking the requirements. A VPN may be right, but a senior answer weighs bandwidth, latency consistency, cost, and time-to-provision, and knows when a dedicated circuit or a hub topology is the better call.

The structure behind a strong answer

  1. 1

    Start from requirements. Ask first: how much bandwidth, how latency-sensitive, what consistency of performance, what security/compliance, and how fast must it be live? The answer picks the option.

  2. 2

    VPN over the internet. An IPsec site-to-site VPN is encrypted, cheap, and fast to stand up, but it rides the public internet so latency and bandwidth are variable.

  3. 3

    Dedicated private link. AWS Direct Connect or Azure ExpressRoute is a private circuit: consistent latency, high bandwidth, often cheaper egress, but costly and slow to provision (weeks).

  4. 4

    Topology at scale. For many networks, a hub like AWS Transit Gateway (or Azure Virtual WAN) centralizes routing instead of a mesh of peerings. Remember VPC peering is non-transitive.

  5. 5

    Layer security and routing. Plan non-overlapping CIDRs, route propagation, and encryption (encrypt even over Direct Connect if compliance requires). Often a VPN is the encrypted backup for a Direct Connect.

What gets you hired

I start from requirements: bandwidth, latency sensitivity, how consistent the performance must be, security and compliance, and how soon it must be live. With that, the options sort themselves. A site-to-site IPsec VPN is encrypted, cheap, and live in a few hours, but it rides the public internet, so a single tunnel caps around 1.25 gigabits per second and latency wanders, fine for modest or bursty traffic and as a quick start. A dedicated circuit, Direct Connect on AWS or ExpressRoute on Azure, gives consistent low latency, links from 1 up to 100 gigabits, and cheaper egress, around 2 cents a gigabyte instead of 9, at the cost of real money and typically 4 to 12 weeks to provision, which suits steady high-volume or latency-sensitive workloads. A common pattern is the dedicated circuit as primary with a VPN as encrypted failover, and BGP flipping over in well under a minute. At scale I avoid a mesh of peerings, which are non-transitive and grow as n squared, so 10 VPCs would need 45 links. I use a hub, Transit Gateway or Azure Virtual WAN, for centralized routing instead. Across all of it I make sure CIDRs do not overlap, routes propagate correctly, and traffic is encrypted where compliance demands it, even on a private circuit.

Starts from requirements not a product Weighs cost/latency/bandwidth/provisioning Knows peering is non-transitive / hub at scale

Then they probe: When is the cost and weeks-long setup of Direct Connect actually justified over a VPN?

Practise this one
Senior

How does a CDN get a user to the nearest edge location? Explain anycast and the role of DNS in this.

What most people say

A CDN stores copies of your content in different countries so users get it from a nearby server.

It describes caching but not the steering mechanism. The question is how the user is actually routed to the nearest edge, which is anycast and DNS, and that is missing.

The structure behind a strong answer

  1. 1

    The edge caches content. A CDN runs many edge points of presence worldwide that cache content close to users, so requests do not travel to a distant origin.

  2. 2

    DNS-based steering. One way to route: the CDN authoritative DNS returns a different edge IP based on the resolver location, sending each user toward a nearby PoP.

  3. 3

    Anycast routing. The other way: the CDN announces the same IP address from many locations via BGP, and the internet routing naturally delivers packets to the topologically nearest announcement.

  4. 4

    Why anycast also helps resilience. If a PoP goes down, its route is withdrawn and traffic shifts to the next-nearest automatically, and the spread absorbs DDoS by distributing load.

What gets you hired

A CDN runs edge points of presence around the world, often 300 or more, that cache content near users, but the interesting part is how a user is sent to the right one. There are two mechanisms, usually combined. DNS-based steering: the CDN authoritative nameserver returns a different edge IP depending on where the resolver is, so a user in Amsterdam and one in Sydney resolve the same hostname to different PoPs, and a short TTL, often 20 to 60 seconds, lets the CDN re-steer quickly. Anycast: the CDN announces the same IP address from many locations over BGP, and internet routing carries each packet to the topologically nearest announcement, with no per-user DNS decision needed. That is typically what takes a first-byte time from 200 milliseconds down to 20. Anycast is also why CDNs are resilient. If a PoP fails, its BGP route is withdrawn and traffic reroutes to the next nearest within seconds, and spreading 1 IP across hundreds of sites means a DDoS is absorbed across the whole fleet instead of hitting one target. So nearest-edge is not magic caching, it is DNS geo-resolution and anycast routing doing the steering.

Explains anycast (one IP, many BGP announcements) Knows DNS-based geo steering Connects anycast to resilience/DDoS absorption

Then they probe: What are the trade-offs of anycast versus DNS-based geo-routing for steering?

Practise this one
Senior

Small requests work but large payloads or some HTTPS sites hang over a VPN or tunnel. What is going on?

What most people say

The connection is probably unstable or the server is slow with big requests.

It guesses at instability and misses the tell: small works, large hangs. That signature is almost always an MTU / PMTUD black hole, not flakiness or a slow server.

The structure behind a strong answer

  1. 1

    Recognize the signature. Small packets (a ping, the start of a handshake) work, but large packets (a big POST, a TLS certificate) hang. That pattern points at MTU, not the app.

  2. 2

    What MTU is. MTU is the largest packet a link carries, usually 1500 bytes on Ethernet. Tunnels (VPN, encapsulation) add overhead, lowering the effective MTU below 1500.

  3. 3

    Path MTU Discovery and the black hole. A host sends a large packet with do-not-fragment set; a router that cannot forward it should reply with an ICMP "fragmentation needed". If a firewall blocks that ICMP, the sender never learns to shrink, and large packets vanish silently: a PMTUD black hole.

  4. 4

    The fixes. Allow the ICMP type 3 code 4 messages through, lower the MTU on the tunnel interface, or clamp the TCP MSS so segments fit the path. MSS clamping is the common practical fix.

What gets you hired

The signature gives it away: small packets succeed, large ones hang. That is almost always an MTU problem, not the application. MTU is the largest packet a link will carry, typically 1500 bytes, and a tunnel like a VPN adds header overhead that lowers the effective MTU. Normally Path MTU Discovery handles this: the sender marks large packets do-not-fragment, and any router that cannot forward one returns an ICMP "fragmentation needed" so the sender shrinks. The failure mode is when a firewall blocks those ICMP messages, the sender never gets told, so large packets are silently dropped while small ones sail through, a PMTUD black hole. That is exactly why a TLS handshake can stall (the certificate is large) or a big upload hangs. The fixes are to allow ICMP type 3 code 4 through, reduce the MTU on the tunnel interface, or clamp the TCP MSS so segments always fit the path. MSS clamping is the usual go-to in practice.

Reads the small-works/large-hangs signature Names MTU and PMTUD black hole Knows MSS clamping / ICMP allow / lower MTU as fixes

Then they probe: Why does an HTTPS site specifically hang, when plain pings work?

Practise this one
Senior

What is mutual TLS (mTLS), and how does a service mesh use it for service-to-service security?

What most people say

mTLS is just TLS but more secure, and a service mesh turns it on for you.

It never states the actual difference, that both sides authenticate, and treats the mesh as a magic switch. The interesting part is identity-based auth via per-service certs and sidecars, which is missed.

The structure behind a strong answer

  1. 1

    Normal TLS is one-way. In standard TLS only the server proves its identity with a certificate; the client is anonymous at the transport layer.

  2. 2

    mTLS is two-way. In mutual TLS both sides present and validate certificates, so each service cryptographically proves who it is. That is the basis of identity-based, zero-trust service-to-service auth.

  3. 3

    The mesh automates it. A service mesh (Istio, Linkerd) runs a sidecar proxy next to each service. The sidecars terminate and originate mTLS transparently, so the app code is unchanged.

  4. 4

    Plus identity and control. The mesh issues and rotates short-lived per-service certificates from its own CA, and adds policy (who may call whom), traffic management, and observability on top.

What gets you hired

Normal TLS authenticates only the server. It presents a certificate and the client stays anonymous at the transport layer. Mutual TLS makes it two-way, so both sides present and validate certificates and each service cryptographically proves its identity to the other. That is the foundation of zero-trust service-to-service security: a service is trusted because of its verified identity, not because it happens to sit inside the 10.0.0.0/8 network. A service mesh operationalizes this by running a sidecar proxy next to every service, so across a fleet of 200 pods you get mutual authentication and encryption on every hop without changing a single line of application code. The mesh runs its own CA and issues short-lived per-service certificates, often with a 24 hour lifetime and automatic rotation well before expiry, which means a stolen certificate is useless almost immediately. On top of that identity it layers authorization policy, which service may call which, so the payments service can call the ledger and nothing else, plus traffic management and consistent observability. The honest cost is a sidecar per pod, roughly 50 to 100 megabytes of memory and a millisecond or so of added latency per hop. For most estates that is a fair price. So mTLS is the identity primitive, and the mesh is what makes it practical at scale.

States both sides authenticate Knows sidecars do mTLS without app changes Mentions per-service identity / short-lived certs / policy

Then they probe: What does mTLS give you that network-level controls like security groups do not?

Practise this one

Cost

13 questions · Foundation, Junior, Mid, Senior
Foundation

What does elasticity mean in the cloud, and how does pay-as-you-go change the cost model compared to running your own servers?

What most people say

The cloud is cheaper because you only pay for what you use instead of buying servers.

It states the slogan without the mechanism. It misses that idle resources still cost money and that elasticity, not just renting, is what creates the savings.

The structure behind a strong answer

  1. 1

    Define elasticity. Elasticity is the ability to add capacity when demand rises and remove it when demand falls, automatically and quickly.

  2. 2

    Contrast the old model. On your own hardware you buy servers up front for peak demand, a large capital expense, and that capacity sits idle most of the time.

  3. 3

    Name the new model. Pay-as-you-go turns that into an operating expense. You pay for what you use by the hour or second and stop paying when you turn things off.

  4. 4

    Connect elasticity to cost. Elasticity is what makes pay-as-you-go pay off, since you scale down in quiet periods instead of paying for peak capacity all the time.

  5. 5

    Add the caution. On demand is not automatically cheaper. Resources left running idle still bill, so cost control means turning things off and right-sizing.

What gets you hired

Elasticity is capacity that grows and shrinks automatically with demand, adding instances when traffic spikes and removing them when it drops. On the sites I have worked with, peak traffic is often 4 or 5 times the overnight baseline, so that difference is real money. Pay-as-you-go is the billing model that goes with it. Instead of buying servers up front for peak load, a big capital expense that sits idle most of the day, I rent capacity by the hour or the second, which is an operating expense. The two only pay off together. Elasticity is what makes pay-as-you-go actually save anything, because if I scale from 10 instances down to 2 during the quiet 12 hours, I stop paying for peak capacity around the clock. The honest caveat is that the cloud is not automatically cheaper. A forgotten instance still bills, maybe 70 dollars a month, and an oversized database running at 8 percent CPU bills at full price all year. The savings come from right-sizing and from actually turning things off. Putting non-production environments on a nightly shutdown alone took one team's dev account from roughly 4,000 dollars a month to about 1,600.

frame elasticity as scaling both up and down name the CapEx to OpEx shift link elasticity to where the savings come from

Then they probe: What is the difference between elasticity and scalability?

Practise this one
Foundation

What are the main things that drive a cloud bill?

What most people say

The cost is mostly from the servers you run.

Compute is one driver, but storage, data transfer (egress especially), and managed services are large and often surprising. Thinking the bill is just servers means you miss where a lot of real spend hides.

The structure behind a strong answer

  1. 1

    Compute. Instances, containers, and functions, billed by size and time (vCPU and memory hours) or per invocation. Often the largest line item.

  2. 2

    Storage. Data at rest (GB-month) plus performance (IOPS/throughput), across object, block, and database storage, and snapshots/backups add up.

  3. 3

    Data transfer. Network egress, data leaving the cloud to the internet, and traffic across regions or availability zones, is charged and is the most commonly overlooked driver.

  4. 4

    Managed services. Databases, queues, load balancers, and other managed services bill per request, per GB, or per hour, and convenience has a price.

What gets you hired

I think of a cloud bill in four categories. Compute is usually the biggest, often 50 to 60 percent of the total: instances, containers, and serverless functions, billed as size times time, so vCPU and memory hours, or per invocation. Storage is next: data at rest charged per GB-month, roughly 2 cents a GB for standard object storage, plus performance tiers for IOPS and throughput. That spans object storage, block volumes, and databases, and the easy-to-forget parts, snapshots and backups, quietly accumulate. Data transfer is the one people most often overlook. Egress to the internet runs about 9 cents a GB, and traffic crossing regions or even availability zones is metered too, usually a cent or two per GB each way, while ingress is free. So a chatty cross-AZ architecture, or serving large files without a CDN, can run up a surprising bill. And managed services, databases, queues, load balancers, each bill on their own per-request, per-GB, or per-hour model, so the convenience of managed infrastructure carries an ongoing cost. Having this map matters because optimization starts with knowing which category dominates. I optimize compute very differently from egress or storage, so my first move is always to open the cost breakdown and find the top 3 line items rather than assume it is all servers.

Names compute/storage/transfer/managed services Flags egress as overlooked Knows you optimize from the breakdown

Then they probe: Which cost driver surprises teams most often?

Practise this one
Foundation

Why is data egress (data transfer) often a hidden cost, and how do you reduce it?

What most people say

Egress is the cost of bandwidth; you reduce it by using less bandwidth.

Circular. The real insight is that egress (out) is charged while ingress (in) is free, so it hides, and the fixes are architectural, CDN, locality, caching, not vaguely "use less bandwidth".

The structure behind a strong answer

  1. 1

    Egress is charged, ingress is not. Getting data into the cloud is usually free, but data leaving, to the internet, and across regions or AZs, is billed, so transfer costs are invisible until the invoice.

  2. 2

    Where it comes from. Serving large files or media to users without a CDN, cross-region replication, chatty cross-AZ traffic between services, and pulling big datasets out of the cloud.

  3. 3

    Use a CDN. A CDN caches content at the edge, so most user traffic is served from the CDN (cheaper, and closer) instead of repeatedly egressing from the origin.

  4. 4

    Keep traffic local. Keep chatty services in the same AZ/region, avoid unnecessary cross-region hops, compress data, and cache to cut repeated transfers.

What gets you hired

Egress is sneaky because of an asymmetry. Getting data into the cloud is almost always free, but data leaving is billed, both internet egress to your users, around 9 cents a GB, and traffic crossing regions or even availability zones, which is typically a cent or two per GB in each direction. Because ingress is free, people stop thinking about transfer costs, and then egress shows up as a surprising line on the invoice. It comes from a few places: serving large files, images, video, downloads, straight to users without a CDN, so every single download egresses from the origin; cross-region replication; chatty microservices talking across AZs, which is billed cross-AZ traffic; and bulk exports out of the cloud. The fixes are architectural. A CDN is the big one for user-facing content. It caches at the edge, and with a healthy cache hit ratio of 90 to 95 percent only a small fraction of requests ever reach the origin, which is both cheaper per GB and faster. Beyond that, keep chatty services in the same AZ and region so their traffic is local, avoid needless cross-region hops, compress payloads, gzip alone often cuts JSON by 70 percent, and cache aggressively so I am not re-sending the same bytes. So I treat egress as a first-class design consideration, where data lives and how it flows, rather than something to discover on the bill.

Knows egress charged / ingress free Names CDN + locality + caching fixes Treats egress as a design consideration

Then they probe: Why does a CDN reduce egress cost?

Practise this one
Junior

What is right-sizing, and why is over-provisioning the most common source of cloud waste?

What most people say

Right-sizing means picking the correct instance size for your workload.

Correct definition but no method or why. The key insight is that most waste is over-provisioned, low-utilization resources, and you fix it by looking at actual utilization metrics, continuously, not by gut feel.

The structure behind a strong answer

  1. 1

    What it is. Right-sizing is matching the resource size (instance type, CPU/memory, capacity) to the actual workload demand, rather than guessing big.

  2. 2

    Why over-provisioning happens. Teams provision for peak or pad for safety and never revisit it, so resources run at low utilization, paying for capacity that is never used.

  3. 3

    Use real utilization. Look at actual CPU, memory, and throughput metrics over time, then downsize or change instance family to fit, with headroom for spikes, not for imagination.

  4. 4

    It is continuous. Workloads change, so right-sizing is an ongoing practice, plus removing zombies (idle instances, unattached volumes, old snapshots), not a one-time cleanup.

What gets you hired

Right-sizing is matching the resources you provision, instance type, CPU, memory, capacity, to what the workload actually uses, instead of guessing on the high side. The reason it is the biggest source of waste is human: teams provision for peak load or pad generously for safety, deploy it, and then never revisit it, so a huge amount of capacity sits at low utilization, getting billed every hour for headroom that is never touched. It is not exotic services that blow the budget, it is fleets of oversized instances running at 10 percent CPU. The fix is to be data-driven: look at the actual CPU, memory, and throughput metrics over a representative period, and then downsize or switch to a more appropriate instance family so the resource fits the real demand, keeping enough headroom for genuine spikes but not for imagination. And critically it is continuous, not a one-off, because workloads grow and shrink, so right-sizing is a recurring practice, and I pair it with cleaning up zombies, idle instances nobody owns, unattached volumes, old snapshots, which are pure waste. So the message is: most cloud savings come from cutting over-provisioned, underutilized capacity, found through utilization data, not from clever tricks.

Match resources to real utilization Knows over-provisioning is the main waste Uses metrics, continuous, kills zombies

Then they probe: How do you decide a resource is over-provisioned?

Practise this one
Junior

Compare on-demand, committed/reserved, and spot/preemptible pricing. How do you mix them?

What most people say

Reserved instances are cheaper, so you should use reserved instances.

Reserved is cheaper only for steady baseline load, committing to capacity you do not consistently use wastes money, and it ignores spot for fault-tolerant work. The answer is a mix matched to workload shape, not one model.

The structure behind a strong answer

  1. 1

    On-demand. Pay full price per hour/second with no commitment. Maximum flexibility, highest unit cost. Good for unpredictable or short-lived workloads.

  2. 2

    Committed / reserved. Commit to a level of usage for 1 to 3 years for a large discount (often 30 to 70 percent). Good for steady, predictable baseline load.

  3. 3

    Spot / preemptible. Deeply discounted spare capacity (up to ~90 percent off) that the provider can reclaim with little notice. Good for fault-tolerant, interruptible, or batch work.

  4. 4

    Mix to fit the shape. Cover the always-on baseline with commitments, handle variable load above it with on-demand, and run fault-tolerant or batch jobs on spot. Match the pricing model to the workload.

What gets you hired

These three models trade flexibility against price. On-demand is full price with no commitment, you pay per hour or second and can start and stop freely, which is maximum flexibility but the highest unit cost, so it suits unpredictable or short-lived workloads. Committed or reserved pricing means you commit to a certain usage level for one to three years in exchange for a substantial discount, often 30 to 70 percent, which is ideal for steady, predictable baseline load that you know will run continuously. Spot or preemptible instances are spare capacity sold at a deep discount, up to around 90 percent off, with the catch that the provider can reclaim them on short notice, so they are perfect for fault-tolerant, interruptible, or batch workloads that can tolerate being killed and restarted, and a bad fit for a stateful database. The real answer is not to pick one but to mix them to the shape of your usage: cover the always-on baseline with commitments to lock in the discount, absorb the variable demand above that baseline with on-demand for flexibility, and run anything fault-tolerant or batch on spot for the deep savings. So I look at the workload profile, what is steady, what is bursty, what is interruptible, and assign each part to the cheapest model that fits its risk and predictability.

Maps each model to a workload type Knows spot can be reclaimed / for fault-tolerant Advocates a mix to usage shape, not one model

Then they probe: What kind of workload is a bad fit for spot instances?

Practise this one
Junior

Why is resource tagging important for cost management?

What most people say

Tagging helps you organize your resources.

Organization is incidental. The cost point is that tags make spend attributable, so you know who spends what (showback/chargeback) and can hold teams accountable. Without tags the bill is unallocatable.

The structure behind a strong answer

  1. 1

    Untagged = opaque. Without tags, the bill is one big number, you cannot tell which team, service, or environment is responsible for what, so you cannot target optimization.

  2. 2

    Tags allocate cost. Consistent tags (team, environment, project, cost-center) let you slice the bill by owner and purpose, turning the invoice into actionable, attributable data.

  3. 3

    Showback and chargeback. Allocation enables showback (showing each team its spend for visibility) and chargeback (billing the cost back to the team), which creates accountability.

  4. 4

    Enforce it. Tagging only works if it is consistent, so enforce a tagging policy (required tags, automation, or policy-as-code) because a half-tagged estate gives a half-useful breakdown.

What gets you hired

Tagging is the foundation of cost accountability. Without tags the cloud bill is one opaque number. You can see you spent 80,000 dollars last month, but not which team, service, or environment drove it, so you cannot target optimization or hold anyone responsible. Consistent tags, typically 4 or 5 required ones like team, environment, project, and cost-center, let you slice the bill along those dimensions, and the invoice turns into attributable data: this team spent 12,000 dollars, dev is somehow 30 percent of prod, this one project costs 9,000 a month. That allocation unlocks the two practices that actually change behavior. Showback, where each team sees its own spend and starts caring about it, and chargeback, where the cost lands on the team's budget so it becomes a real constraint they own. The catch is that this only works if tagging is consistent and complete. I have seen estates where 40 percent of spend lands in an unallocated bucket, and a half tagged estate gives you a half useful breakdown. So I enforce it: a documented required tag set, automation that applies tags at provision time, tags inherited from the Terraform provider default_tags, and policy as code that flags or rejects untagged resources, with a target of 95 percent or better allocation coverage. Tagging is not about tidiness. It is the prerequisite for seeing where the money goes, which is why every FinOps effort starts here.

Knows untagged bill is unallocatable Tags enable allocation + showback/chargeback Wants enforced/consistent tagging

Then they probe: What is the difference between showback and chargeback?

Practise this one
Mid

How do autoscaling and scheduling reduce cost, and what is an easy win for non-production?

What most people say

Autoscaling adds more servers when you need them so the app does not go down.

That frames autoscaling as availability only and misses the cost half, scaling DOWN to stop paying for idle peak capacity, and the easy non-prod scheduling win, which is what the question is really after.

The structure behind a strong answer

  1. 1

    Pay for demand, not peak. Autoscaling adds capacity when demand is high and removes it when low, so you stop paying for peak-sized capacity around the clock.

  2. 2

    Scale on the right signal. Scale on a metric that tracks real load (CPU, request rate, queue depth) so capacity follows demand, with headroom for spikes and scale-up lag.

  3. 3

    Schedule non-production. Dev, staging, and test environments rarely need to run nights and weekends. Shutting them down outside working hours is a large, easy saving (often roughly 65 to 70 percent of their cost).

  4. 4

    Serverless for spiky/idle. For very spiky or mostly-idle workloads, serverless or scale-to-zero means you pay only when it runs, instead of a server sitting idle.

What gets you hired

Both are about matching what you pay for to what you actually need, instead of paying for peak capacity continuously. Autoscaling adds instances when demand rises and, just as importantly for cost, removes them when demand falls, so you are not running and paying for peak-sized capacity twenty-four hours a day when you only need it at busy times. The key is to scale on a signal that reflects real load, CPU, request rate, or queue depth, with enough headroom to absorb spikes and the lag of spinning up new capacity. The easiest concrete win, though, is scheduling non-production environments: dev, staging, and test environments almost never need to run overnight or on weekends, yet they often run 24/7 by default, so simply shutting them down outside working hours cuts their cost by roughly two-thirds, and it is low-risk because nobody is using them then. And for workloads that are very spiky or mostly idle, serverless or scale-to-zero is a cost lever in its own right, because you pay only when code actually runs rather than for an always-on server sitting idle. So autoscaling for production demand, scheduling for non-prod, and scale-to-zero for spiky workloads, all the same principle: only pay for capacity when it is doing useful work.

Knows scaling DOWN is the cost lever Cites non-prod scheduling as the easy win Mentions scale-to-zero/serverless for idle

Then they probe: Why is shutting down non-prod at night such a common easy win?

Practise this one
Mid

How do you control storage costs as data grows over time?

What most people say

I would delete old data that is not needed anymore.

Deleting helps but is manual and crude. The systematic answer is tiering by access frequency with automated lifecycle policies, plus cleaning orphaned storage, and being aware of retrieval costs on cold tiers. Just "delete old data" misses the mechanism.

The structure behind a strong answer

  1. 1

    Tier by access frequency. Match the storage class to how often data is accessed: hot for frequent, infrequent-access tiers for occasional, and archive/cold for rarely-touched data, each cheaper per GB.

  2. 2

    Automate with lifecycle policies. Lifecycle rules automatically transition data to cheaper tiers as it ages, and expire or delete it after a retention period, so cost management is hands-off.

  3. 3

    Clean up orphans. Delete unattached volumes, old snapshots and backups beyond retention, incomplete multipart uploads, and forgotten data, these accumulate silently.

  4. 4

    Mind the trade-offs. Colder tiers are cheaper to store but have retrieval cost and latency, so tier by genuine access patterns, and watch that retrieval/transfer fees do not erase the savings.

What gets you hired

Storage grows forever if you let it, so I manage it by access pattern rather than by deleting things in a panic. The core idea is tiering. I match the storage class to how often the data is really read. Hot for frequently accessed data, an infrequent access tier for data touched maybe once a month, and an archive or cold tier for data that is rarely read but must be kept for compliance, each step roughly 3 to 5 times cheaper per GB. Then I automate it with lifecycle policies, rules that transition objects as they age and expire them at the end of retention. On a logs bucket that usually looks like standard for 30 days, infrequent access at 30, archive at 90, delete at 365, so cost management runs itself instead of depending on someone remembering. Alongside that I hunt orphaned storage, which is pure waste. Unattached volumes left behind when instances were deleted, snapshots kept years past their retention, incomplete multipart uploads, forgotten datasets. On one account that cleanup alone took the storage bill from about 4,000 dollars a month to 1,600. The trade off to respect is that cold tiers are cheap to store but charge for retrieval and add latency, minutes to hours for archive, so I tier on genuine access data and check that retrieval charges would not erase the savings if the data gets read more than expected. So, tier by access frequency, automate transitions and expiry, and continuously clean up orphans.

Tiers by access frequency Automates with lifecycle policies Cleans orphans + knows retrieval-cost trade-off

Then they probe: What is the trade-off of moving data to a cold/archive tier?

Practise this one
Mid

A managed service costs more per hour than self-hosting the same thing. How do you reason about that?

What most people say

Self-hosting is cheaper, so I would self-host to save money.

It optimizes the sticker price while ignoring the engineering time and operational risk that managed services absorb. That hidden labor cost usually makes self-hosting more expensive overall, not less. TCO is the missing lens.

The structure behind a strong answer

  1. 1

    Sticker price is not the full cost. A managed database costs more per hour than running it yourself, but the price includes operations: patching, backups, high availability, scaling, and monitoring.

  2. 2

    Count the engineering time. Self-hosting shifts that work to your engineers, setup, patching, upgrades, failover, on-call when it breaks, which is real, expensive, and often the larger cost.

  3. 3

    Count the risk. Self-managed infrastructure carries operational risk: a botched upgrade or a missed backup can cause an outage or data loss that dwarfs any hourly savings.

  4. 4

    Decide on TCO. Compare total cost of ownership, infrastructure plus labor plus risk. Managed usually wins until you reach a scale or have a special need where self-hosting genuinely pays off.

What gets you hired

The mistake is comparing only the hourly sticker price. Yes, a managed service, say a managed database, costs more per hour than the same engine running on a raw instance, but that price is buying operations: the provider handles patching, backups, high availability, failover, scaling, and a lot of the monitoring. When you self-host, that work does not disappear, it moves to your engineers, who now own setup, OS and engine patching, version upgrades, configuring and testing failover, capacity management, and getting paged at 3am when it breaks. That engineering time is real money and often the larger cost, especially because skilled engineer hours are expensive and finite. On top of labor there is operational risk: a self-managed system is one botched upgrade or one missed backup away from an outage or data loss whose cost can dwarf years of the hourly savings. So the right lens is total cost of ownership, infrastructure plus engineering labor plus the expected cost of operational risk, not just the bill line. When I do that comparison, managed services usually win, because the per-hour premium is less than the loaded cost of doing it well yourself, and the honest exception is at large scale or with a specialized requirement, where self-hosting can genuinely pay off and you have or can justify the dedicated expertise to run it reliably.

Reasons in total cost of ownership Counts engineering time + operational risk Knows self-host pays off only at scale/special need

Then they probe: When does self-hosting actually become the cheaper choice?

Practise this one
Mid

How do you avoid a surprise on the monthly cloud bill?

What most people say

I would check the bill at the end of the month to see if it went up.

Checking the invoice is reactive, by then the runaway has already cost you weeks of overspend. The proactive answer is budgets, threshold alerts, and anomaly detection that catch a spike within hours, plus guardrails.

The structure behind a strong answer

  1. 1

    Set budgets and alerts. Define budgets per team/project/account and alert as you approach or exceed thresholds, so overspend is caught mid-month, not on the invoice.

  2. 2

    Anomaly detection. Use cost anomaly detection to flag sudden, unusual spikes automatically, a forgotten GPU instance, a runaway loop, a misconfigured job, before they run for weeks.

  3. 3

    Make spend visible. Dashboards and regular cost reviews keep spend in front of the teams who own it, so cost is monitored continuously like any other metric, not once a month.

  4. 4

    Guardrails for the worst cases. Quotas/limits and policy-as-code (for example blocking very large instance types without approval) prevent the catastrophic runaway, not just alert on it.

What gets you hired

The whole point is to be proactive, because if you only look at the invoice you find out weeks late, and one forgotten p3 GPU instance at roughly 3 dollars an hour is about 2,200 dollars a month burned quietly. So first I set budgets per team, project or account, with alerts at 50, 80 and 100 percent of the expected spend, plus a forecast alert, so someone gets notified mid-month while there is still time to act. On top of that I turn on cost anomaly detection, which flags a sudden spike against the normal pattern, say a day that jumps 40 percent above the trailing 14 day baseline. That is what catches the classic disasters, a developer who left a big GPU box running, a misconfigured loop spinning up resources, an export accidentally egressing 5 terabytes at 9 cents a gigabyte, within hours instead of on the monthly bill. I also keep spend continuously visible with a dashboard and a 30 minute cost review each month with the teams that own the spend, so cost is a monitored metric, not a once-a-month surprise. And for the genuinely catastrophic cases I add guardrails, service quotas and limits, and policy as code that blocks launching the largest instance types without approval, so the worst runaways are prevented outright rather than merely alerted on. Budgets and alerts, anomaly detection, continuous visibility, and preventive guardrails, all aimed at catching a cost problem long before the invoice arrives.

Proactive budgets + threshold alerts Anomaly detection for spikes Continuous visibility + preventive guardrails

Then they probe: What does cost anomaly detection catch that a fixed budget alert might miss?

Practise this one
Senior

How do you approach cloud cost optimization without hurting reliability?

What most people say

I would buy reserved instances and turn off things we are not using.

Not wrong, but it is tactics with no method, no measurement, no right-sizing, no architectural lever, and no guard for reliability. It is the answer of someone who has read tips, not owned a bill.

The structure behind a strong answer

  1. 1

    Measure and attribute first. You cannot optimize what you cannot see. Tag resources, use cost-and-usage tooling to find the top spenders, never cut blind.

  2. 2

    Kill waste before cutting capacity. Idle/oversized instances, unattached disks, orphaned IPs, dev environments running overnight. This is free, it removes spend with zero reliability risk.

  3. 3

    Right-size and commit. Right-size from real utilization, then cover the steady-state baseline with Savings Plans/Reserved Instances and burst with on-demand/spot. Spot only for fault-tolerant workloads.

  4. 4

    Architect for cost. The big wins are design: storage lifecycle tiering, a CDN/cache to cut egress and compute, autoscaling to zero off-peak, serverless for spiky workloads. Egress and idle capacity are the usual silent killers.

  5. 5

    Protect reliability explicitly. Tie cuts to SLOs, keep the error budget intact, and never remove redundancy that the availability target depends on.

What gets you hired

I start with visibility, tagging and cost tooling to attribute spend, because usually the top 5 line items are 70 or 80 percent of the bill and blind cuts are how you cause an outage. First I remove pure waste, idle and oversized instances, unattached volumes, dev environments running 168 hours a week when they are needed for 45, which is risk free and often 15 to 20 percent straight off. Then I right-size from real utilization, if a box sits at 8 percent CPU for 30 days it does not need that shape, and I cover the steady-state baseline with Savings Plans, roughly 1 year commitments for 30 to 40 percent off, leaving burst on demand and spot only for fault-tolerant work. But the biggest wins are architectural. Lifecycle-tiering cold objects to infrequent access and archive after 30 and 90 days, a CDN and caching to cut egress and compute, autoscaling down off-peak, serverless for spiky loads. Egress at 9 cents a gigabyte and idle capacity are the silent killers. On the last estate I worked, that took the bill from about 40,000 dollars a month to 26,000 without touching redundancy. Throughout, I tie every cut to SLOs. If the availability target needs 3 availability zones, I do not collapse to 2 to save money.

Measures/attributes before cutting Removes waste before capacity Names architectural levers (egress, caching, lifecycle)

Then they probe: Finance says cut 30% this quarter. How do you respond?

Practise this one
Senior

What is FinOps, and how do you make engineers care about cloud cost?

What most people say

FinOps is the finance team tracking and controlling cloud costs.

Framing it as finance policing the bill misses the whole point. FinOps is cross-functional and puts cost ownership on the engineers who drive it through visibility and incentives, not after-the-fact control by a central team.

The structure behind a strong answer

  1. 1

    What FinOps is. FinOps brings financial accountability to the variable spend of the cloud, a cross-functional practice across engineering, finance, and product, not just a finance function.

  2. 2

    The phases. Inform (visibility and cost allocation so everyone can see spend), Optimize (right-sizing, commitments, eliminating waste), and Operate (continuous governance and improvement).

  3. 3

    Give engineers visibility and ownership. Engineers control the spend through their architecture choices, so show them the cost of what they run (tagging, dashboards, cost-per-team) and make them own their budget.

  4. 4

    Incentives over policing. A central team policing the bill after the fact does not scale or change behavior. Embed cost as a metric engineers see and own, alongside latency and reliability, so it is considered at design time.

  5. 5

    Balance, not minimize. The goal is value for money, spending efficiently to move fast, not blindly cutting cost at the expense of velocity or reliability.

What gets you hired

FinOps is the practice of bringing financial accountability to cloud spend, and the key is that it is cross functional, engineering, finance, and product together, not finance unilaterally controlling the bill. The model has three phases. Inform, which is visibility and cost allocation through tagging and dashboards, and my bar there is that at least 95 percent of spend is attributable to an owning team, because unallocated spend is spend nobody will fix. Optimize, the levers like right sizing, commitment discounts, and eliminating waste. And Operate, making it continuous governance and culture rather than a one time cleanup. It has to involve engineers because engineers drive the spend through their architecture and resource choices, so cost is really decided at design time, and a finance review 30 days after the invoice cannot undo those decisions. So the way you make engineers care is visibility and ownership, show each team the cost of what they run, attributed to them, give them a budget with an alert at 80 percent of it, and cost becomes a metric they watch beside latency and reliability. That beats policing, because a central team chasing overspend after the fact does not scale across dozens of teams and does not change the decisions that caused it, whereas engineers who see the cost of their own choices start optimizing naturally. And I frame the goal as value for money, not minimizing the bill, because cost is one dimension of good engineering, not the only one.

FinOps = cross-functional cost accountability Knows inform/optimize/operate Engineer ownership + visibility over policing, value not minimize

Then they probe: Why does central cost policing not work as well as engineer ownership?

Practise this one
Senior

Why is cost per customer (unit economics) often a better metric than total cloud spend?

What most people say

You should track total spend and try to reduce it each month.

Minimizing total spend can be exactly wrong, if the business is growing, the bill should grow. The meaningful metric is cost per customer/transaction; a rising total with falling unit cost is healthy. Pure cost-cutting can starve growth.

The structure behind a strong answer

  1. 1

    Total spend lacks context. Total cloud cost going up is meaningless on its own, it might mean waste, or it might mean the business grew. The number alone cannot tell you.

  2. 2

    Unit economics adds the denominator. Cost per unit of value, per customer, per transaction, per request, relates spend to what the business gets for it, which is the metric that actually means something.

  3. 3

    Healthy growth vs waste. A rising total bill with a falling cost-per-customer is efficient, profitable growth. A flat or rising cost-per-customer signals real inefficiency to fix.

  4. 4

    Drives the right decisions. Optimizing unit cost focuses effort on efficiency and scalability, and it is the language leadership and finance understand, rather than blindly cutting the total and starving growth.

What gets you hired

Total cloud spend on its own is almost meaningless, because it has no context. If the bill went from 100,000 dollars a month to 130,000, that could be waste, or it could simply mean the business grew and you are serving far more customers. You cannot tell which from the total, and that is exactly why reduce the bill is a bad goal. Unit economics fixes it by adding a denominator, cost per unit of business value, per customer, per transaction, per active user, per request. That ratio actually means something, because it relates what you spend to what the business gets. With it you can separate healthy growth from waste. If spend rises 30 percent while customers double and cost per customer drops from 4 dollars to 2.60, that is efficient scaling and it is good news. A cost per customer that is flat or climbing while you grow is the real warning sign, it says the architecture is not scaling efficiently and there is genuine inefficiency to find. This reframing matters because it points optimization at efficiency and scalability rather than blindly slashing the total, which can starve the business by cutting capacity that supports growth. It is also the language leadership and finance speak, they care about gross margin and cost to serve, not raw infrastructure dollars. So I measure and optimize unit cost, treating cloud spend as an investment that should scale sub linearly with the value it produces, rather than a bill to minimize.

Knows total spend lacks context Uses cost per customer/transaction Distinguishes healthy growth from waste, avoids blind cutting

Then they probe: When is a rising total cloud bill actually a good sign?

Practise this one

Linux

9 questions · Foundation, Junior, Mid
Foundation

Explain Linux file permissions. What does chmod 644 mean, and what would 755 be for?

What most people say

644 gives read and write, 755 gives full permissions.

It does not break the number into owner/group/other or the 4/2/1 bits, and "full permissions" is wrong (755 is not 777). It shows pattern-memory, not the model.

The structure behind a strong answer

  1. 1

    Three identities. Every file has permissions for owner (user), group, and other.

  2. 2

    Three bits each. read (4), write (2), execute (1). You add them: 6 = read+write, 7 = read+write+execute, 5 = read+execute.

  3. 3

    Decode 644. Owner 6 (rw-), group 4 (r--), other 4 (r--): a normal file the owner edits and everyone reads.

  4. 4

    Decode 755. Owner 7 (rwx), group 5 (r-x), other 5 (r-x): a script or directory that everyone can execute/enter but only the owner can change.

  5. 5

    Note execute on directories. On a directory, execute means "can enter/traverse it", not "run it", a common gotcha.

What gets you hired

Permissions are set for three identities, owner, group, and other, each with read (4), write (2), execute (1) that you sum. So 644 is owner read-write (6), group read (4), other read (4): a typical data file. 755 is owner read-write-execute (7), group and other read-execute (5): what you want for a script or a directory, everyone can run or enter it but only the owner can modify it. One gotcha: on a directory, the execute bit means you can traverse into it, not that it runs.

Breaks numbers into owner/group/other Knows the 4/2/1 values Knows execute means traverse on a directory

Then they probe: What is the difference between chmod and chown?

Practise this one
Foundation

How do you find a running process and stop it? What is the difference between SIGTERM and SIGKILL?

What most people say

I would run kill -9 on the process id to stop it.

Jumping straight to -9 is the junior tell: it denies the process any cleanup and risks corruption. It also skips SIGTERM entirely, which is what you should try first.

The structure behind a strong answer

  1. 1

    Find it. ps aux | grep name, or pgrep, or top/htop for a live view. Get the PID.

  2. 2

    Ask politely first. kill <pid> sends SIGTERM (15): asks the process to shut down cleanly so it can flush state, close connections, and exit.

  3. 3

    Force only if it ignores you. kill -9 <pid> sends SIGKILL: the kernel kills it immediately, no cleanup. Use it only when SIGTERM fails.

  4. 4

    Know the cost of -9. SIGKILL can leave corrupt files, stale locks, or orphaned children because the process never got to clean up.

What gets you hired

I find the PID with ps aux | grep or pgrep, or watch it live in top. Then I send SIGTERM first with plain kill, which asks the process to shut down cleanly, flush buffers, close sockets, and exit on its own terms. Only if it ignores that do I escalate to kill -9, SIGKILL, which the kernel enforces immediately with no chance to clean up, so it can leave corrupt files or stale locks. So -9 is the last resort, not the first move.

Tries SIGTERM before SIGKILL Explains why -9 risks corruption Knows ps/pgrep/top to find PIDs

Then they probe: A process is stuck and even kill -9 does not remove it. Why?

Practise this one
Foundation

Explain PATH, environment variables, and how pipes and redirection work in the shell.

What most people say

PATH is where programs are, env vars are settings, and pipes connect commands.

Directionally right but vague: it misses that PATH is an ordered search list, that exported vars are what children inherit, and the stdout/stderr distinction (2>&1) that trips people up constantly.

The structure behind a strong answer

  1. 1

    Environment variables. Key-value settings the shell and child processes inherit (e.g. HOME, AWS_REGION). Set with export VAR=value; child processes see exported vars.

  2. 2

    PATH. A colon-separated list of directories the shell searches, in order, to find a command you type. "command not found" usually means it is not on PATH.

  3. 3

    Pipes. cmd1 | cmd2 sends cmd1 stdout into cmd2 stdin, composing small tools (e.g. ps aux | grep nginx).

  4. 4

    Redirection. > writes stdout to a file (overwrite), >> appends, < reads from a file, and 2> redirects stderr; 2>&1 merges stderr into stdout.

What gets you hired

Environment variables are inherited key-value settings, you export VAR=value and child processes see it, which is how config like AWS_REGION reaches a program. PATH is a colon-separated, ordered list of directories the shell searches to resolve a command; "command not found" usually means it is not on PATH or not executable. Pipes (|) feed one command stdout into the next command stdin, which is how you compose small tools. Redirection sends streams to files: > overwrites, >> appends, < feeds input, and 2> handles stderr separately, with 2>&1 merging stderr into stdout, which matters when you want to capture errors too.

PATH as an ordered search list Knows exported vars are inherited Distinguishes stdout vs stderr (2>&1)

Then they probe: You added a binary but the shell still says "command not found". Why?

Practise this one
Junior

A server reports the disk is full. Walk me through finding what is using the space.

What most people say

I would delete old log files to free up space.

It might work, but it skips diagnosis: which filesystem, what is actually large, and the two classic traps (deleted-open files and inode exhaustion) where deleting logs does nothing. No prevention either.

The structure behind a strong answer

  1. 1

    Confirm which filesystem. df -h shows usage per mount. Find the one at 100%, do not assume it is /.

  2. 2

    Find the big directories. du -sh /* then drill down (du -sh /var/* ...), or du -ahx | sort -rh | head to find the largest files.

  3. 3

    Check for deleted-but-open files. If df says full but du does not add up, a process is holding a deleted file open. lsof | grep deleted finds it; restart that process to release the space.

  4. 4

    Check inodes too. df -i, you can be out of inodes (millions of tiny files) with disk space free; the symptom is the same "no space left".

  5. 5

    Fix the cause, not just the symptom. Rotate/cap logs, clean package caches, and add monitoring/alerting so it does not silently refill.

What gets you hired

First df -h to see which mount is actually full, because it is often not /, it is /var sitting at 100 percent from logs. Then du to localize it, du -ahx /var, sorted with sort -rh and piped to head -20, ranks the 20 heaviest directories and files and usually one of them is obviously the problem. If df says the disk is 100 percent full but du only accounts for, say, 20 gigabytes of it, that gap is the classic trap. A process is holding a deleted file open, so the space is not returned until that handle closes. lsof piped through grep deleted finds it, and restarting that process, often a logger, frees the space instantly. I would also run df -i for inode exhaustion, because millions of tiny cache or session files can hit 100 percent of inodes while df -h still shows free space, and the error message looks identical. Then I fix the cause rather than just buying time: logrotate with a size cap and 7 days of retention, clean the package and build caches, and an alert at 80 percent so it warns me before it silently refills.

Uses df then du to localize Knows the deleted-open-file trap Checks inodes (df -i)

Then they probe: du and df disagree on usage. What is happening?

Practise this one
Junior

A systemd service will not start. How do you investigate?

What most people say

I would restart the service a few times and reboot the server if it keeps failing.

Restart-and-reboot ignores the error message systemd already gives you. A failing unit fails deterministically; rebooting wastes time and hides the cause.

The structure behind a strong answer

  1. 1

    Check status. systemctl status <svc> shows active/failed state, the exit code, and the last few log lines, usually the first clue.

  2. 2

    Read its logs. journalctl -u <svc> --no-pager (add -e for the end, -f to follow) shows why it exited, the actual error, not a guess.

  3. 3

    Inspect the unit. systemctl cat <svc> shows the unit file: the ExecStart command, user, working dir, and dependencies. Many failures are a bad path, missing env, or wrong permissions.

  4. 4

    Reproduce manually. Run the ExecStart command by hand as the service user to see the raw failure outside systemd.

  5. 5

    Reload after edits. systemctl daemon-reload after changing a unit file, then restart, a common forgotten step.

What gets you hired

I start with systemctl status, which gives me the failed state, the exit code, and the last 10 log lines. The exit code alone often tells the story, a 203 means the ExecStart binary was not found or is not executable, while a plain 1 usually means the app itself started and then died. Then journalctl -u for the service, with -n 100 and --no-pager, gives me the full error, and that normally says exactly why it stopped. Next I read the unit itself with systemctl cat, checking the ExecStart path, the User it runs as, the WorkingDirectory, and any After or Requires dependencies, because the cause is very often a wrong absolute path, a missing environment variable, or a permissions problem, like the service user not being able to read a file or bind to a port below 1024. If it is still unclear, I run the ExecStart command by hand as that service user, which strips systemd out of the picture and shows me the raw error. And after any edit to the unit file I run daemon-reload before restarting, because otherwise systemd is still running the old definition, and that is the step people forget most.

Goes to status + journalctl first Reads the actual error Knows daemon-reload after edits

Then they probe: The service starts then immediately exits and systemd keeps restarting it. What do you check?

Practise this one
Junior

How do you check what is listening on a port, and test whether you can reach a remote service?

What most people say

I would ping the server to see if it is up.

Ping only tests ICMP reachability, not the port or the service, and many hosts block ICMP entirely. It cannot tell you if the actual service is listening or reachable.

The structure behind a strong answer

  1. 1

    See local listeners. ss -tlnp (or the older netstat -tlnp) lists TCP listening sockets, the port, and the owning process. Confirms the service is actually bound.

  2. 2

    Check what it bound to. Bound to 127.0.0.1 means localhost-only; it must bind 0.0.0.0 (or the right interface) to accept remote connections, a very common bug.

  3. 3

    Test reachability outward. curl http://host:port for HTTP, or nc -vz host port / telnet host port for a raw TCP connect to see if the port is open from here.

  4. 4

    Separate the layers. If DNS fails, dig/nslookup; if connect hangs, it is firewall/security-group or routing; if it refuses, nothing is listening; if it connects but the app errors, it is the app.

What gets you hired

Locally, ss -tlnp shows what is listening, the port, and the owning process, and crucially what address it bound to, 127.0.0.1 is localhost-only and a frequent cause of "works locally, not remotely". To test a remote service I use curl for HTTP or nc -vz host port for a raw TCP check. Then I layer the diagnosis: DNS resolves (dig)? Connection refused means nothing is listening; a hang usually means a firewall or security group dropping packets; if it connects but errors, the problem is the app, not the network. Ping alone is not enough since it only tests ICMP, which is often blocked.

Uses ss/netstat to confirm listening Checks the bind address (127.0.0.1 vs 0.0.0.0) Distinguishes refused vs timeout

Then they probe: curl hangs with no response. Refused vs timeout, what does each tell you?

Practise this one
Mid

A Linux box is sluggish and load is high. Walk me through finding the cause.

What most people say

I would open top and kill whatever is using the most CPU.

It assumes the problem is CPU, but high load is often I/O wait or memory pressure where the top-CPU process is innocent. Killing blindly can take down the wrong thing and lose data.

The structure behind a strong answer

  1. 1

    Read load in context. uptime/top show load average; compare it to core count (load 8 on 8 cores is full, on 2 cores it is overloaded). High load can be CPU OR I/O wait.

  2. 2

    Split CPU vs I/O vs memory. top: high %us = CPU-bound app; high %wa (I/O wait) = disk/network blocking; check free -h and swap, heavy swapping looks like slowness but is memory.

  3. 3

    Find the offender. top/htop sorted by CPU or memory; for I/O, iostat or iotop to see which process and device.

  4. 4

    Understand before acting. Is it legitimate load (needs scaling) or a runaway/leak (needs a fix)? renice can de-prioritize, but killing blindly can lose data.

  5. 5

    Confirm and prevent. After mitigating, check the metric returns to normal and add an alert/limit (cgroups, ulimits) so it cannot recur silently.

What gets you hired

First I read load average against core count, load 8 means different things on 2 cores versus 16. Then I split the cause in top: high user CPU points to a busy app, but high I/O wait (%wa) means disk or network is the bottleneck and the CPU is idle waiting, and heavy swap (free -h) makes everything slow for a memory reason. I find the offender with htop for CPU/memory or iotop/iostat for I/O. Before acting I decide if it is legitimate load that needs scaling or a runaway that needs a fix; renice to de-prioritize, or fix the process, rather than blind-killing. Then I confirm recovery and add a limit or alert so it cannot silently recur.

Compares load to core count Separates CPU vs I/O wait vs memory Uses iostat/iotop for I/O

Then they probe: Load is high but CPU usage is low. What does that mean?

Practise this one
Mid

You SSH into a misbehaving Linux server. What are the first things you check?

What most people say

I would check the application logs to see what is wrong.

App logs are one input, but if the box is out of disk, memory, or CPU, the app logs may be a symptom, not the cause. A senior checks the host vitals first to know which layer is failing.

The structure behind a strong answer

  1. 1

    Load and CPU. uptime / top, is it saturated, and for how long (load average over 1/5/15 min shows trend).

  2. 2

    Memory. free -h, is it out of RAM or swapping? OOM kills show up in dmesg / journalctl -k.

  3. 3

    Disk. df -h (space) and df -i (inodes); a full disk breaks almost everything quietly.

  4. 4

    Logs. journalctl -e / -f and the relevant /var/log files, plus dmesg for kernel/hardware/OOM messages. Read the most recent errors.

  5. 5

    The service itself. systemctl status of the app, is it running, listening (ss -tlnp), and reaching its dependencies?

What gets you hired

I run a quick vitals sweep before diving in. Load and CPU with uptime/top, including the 1/5/15-minute trend. Memory with free -h, and dmesg or journalctl -k for OOM kills. Disk with df -h and df -i, a full disk or exhausted inodes breaks things silently. Then logs, journalctl -e and the relevant /var/log, plus dmesg for kernel and hardware messages. Finally the service itself, systemctl status, is it listening on its port (ss -tlnp), and can it reach its dependencies. That order tells me which layer is actually failing instead of assuming it is the app.

Has a CPU/mem/disk/logs routine Checks df -i not just df -h Knows where OOM kills appear

Then they probe: Where do you look for an OOM (out-of-memory) kill?

Practise this one
Mid

How do you schedule a recurring job, and how do you debug one that did not run?

What most people say

I would add it to crontab; if it does not run I would check that the cron syntax is right.

Syntax is rarely the real problem. The usual culprit, cron running with a stripped-down environment and PATH, is exactly what this question is probing, and the weak answer misses it entirely.

The structure behind a strong answer

  1. 1

    Schedule it. crontab -e adds a line: five time fields (min hour day month weekday) then the command, e.g. "0 2 * * * /path/script.sh" runs at 02:00 daily.

  2. 2

    Know cron runs a minimal environment. Cron does not load your shell profile, so PATH is tiny and your env vars are missing. The number-one cause of "works in my shell, not in cron".

  3. 3

    Make it robust. Use absolute paths for binaries and files, set needed env vars in the crontab or script, and do not assume the working directory.

  4. 4

    Capture output. Cron mails output by default (often unread); redirect explicitly: ">> /var/log/myjob.log 2>&1" so you can see what happened.

  5. 5

    Verify it fired. Check the cron/syslog (journalctl -u cron or /var/log/syslog) to confirm cron even triggered the line, then read your job log for its own errors.

What gets you hired

I add it with crontab -e, five time fields plus the command, like "0 2 * * * /opt/app/backup.sh" for 2am daily. The thing that bites people: cron runs with a minimal environment, it does not source your profile, so PATH is bare and your env vars are gone. So I use absolute paths everywhere, set required env in the crontab or the script, and never assume a working directory. I redirect output explicitly, ">> /var/log/job.log 2>&1", because cron silently mails output you will never read. To debug a no-run, I first confirm cron actually triggered it in the cron/syslog, then read the job log for the real error, which is usually a missing binary on PATH or a relative path.

Knows the 5 cron fields Calls out the minimal cron env/PATH Redirects output to a log (2>&1)

Then they probe: The script runs fine when you run it manually but fails under cron. Why?

Practise this one

AWS

12 questions · Foundation, Junior, Mid, Senior, Principal
Foundation

What are the EC2 purchasing options (On-Demand, Reserved, Savings Plans, Spot), and how do you mix them?

What most people say

On-Demand is the normal way to pay, and Reserved Instances are cheaper if you commit.

It only names two and treats On-Demand as the default to use everywhere. It misses Spot for batch and the actual strategy: baseline on commitments, burst on demand, batch on Spot.

The structure behind a strong answer

  1. 1

    On-Demand. Pay per second, no commitment, highest unit price. Use for spiky, unpredictable, or short-lived workloads.

  2. 2

    Reserved / Savings Plans. Commit to 1 or 3 years for up to ~72% off. Savings Plans commit to a dollars-per-hour spend and are more flexible (Compute Savings Plans even cover Fargate and Lambda); RIs are tied more to instance attributes.

  3. 3

    Spot. Spare capacity up to ~90% off, but AWS can reclaim it with a 2-minute warning. Only for fault-tolerant, stateless, or batch work.

  4. 4

    Mix deliberately. Cover your steady baseline with Savings Plans or RIs, handle bursts with On-Demand, and run interruptible or batch workloads on Spot. That blend is where the real savings come from.

What gets you hired

There are four levers. On-Demand is pay-as-you-go with no commitment but the highest unit price, right for spiky or short-lived workloads. Reserved Instances and Savings Plans give up to about 72% off for a 1 or 3 year commitment; I lean toward Compute Savings Plans because they commit to a dollar-per-hour spend rather than specific instances and even cover Fargate and Lambda, so they survive instance-family changes. Spot is spare capacity at up to 90% off, but AWS can reclaim it on a 2-minute notice, so I only put fault-tolerant, stateless, or batch work there. The real skill is the mix: cover the steady baseline with Savings Plans, absorb bursts with On-Demand, and run interruptible workloads, CI runners, batch processing, stateless web tiers behind an ASG, on Spot. That layered approach typically cuts compute cost dramatically without risking the workloads that cannot tolerate interruption.

Names all four and the commitment trade-off Knows Savings Plans flexibility vs RIs Describes the baseline/burst/Spot mix

Then they probe: Why might you choose a Savings Plan over a Reserved Instance?

Practise this one
Foundation

Walk me through the main S3 storage classes and how you would use lifecycle policies to control cost.

What most people say

There is S3 Standard for normal storage and Glacier for cheap long-term storage, and you can move data to Glacier to save money.

It collapses many classes into two and ignores the practical traps: IA retrieval fees, minimum storage durations, and Glacier restore times. Moving frequently-read data to Glacier can cost more, not less.

The structure behind a strong answer

  1. 1

    Standard vs infrequent access. S3 Standard for hot data; Standard-IA for data accessed rarely but needed fast, cheaper storage but with a per-GB retrieval fee and a 30-day minimum.

  2. 2

    Intelligent-Tiering. When access patterns are unknown or changing, it auto-moves objects between tiers for a small monitoring fee, so you do not guess.

  3. 3

    Glacier tiers for archive. Glacier Instant Retrieval (ms access, archive price), Flexible Retrieval (minutes to hours), and Deep Archive (cheapest, up to ~12 hours to restore). Cost drops as retrieval gets slower.

  4. 4

    Lifecycle policies. Automate transitions: keep new objects in Standard, move to IA after 30 days, to Glacier after 90, expire after a year. This is where ongoing savings come from.

What gets you hired

I match the class to the access pattern. S3 Standard for hot, frequently accessed data. Standard-IA for data I rarely touch but must get instantly, cheaper storage but with a per-GB retrieval cost and a 30-day minimum, so it backfires for data you actually read often. If access patterns are unknown or shifting, Intelligent-Tiering auto-moves objects between tiers for a small monitoring fee, which removes the guesswork. For archives there is a Glacier family: Instant Retrieval for millisecond access at archive prices, Flexible Retrieval for minutes-to-hours, and Deep Archive, the cheapest, where restore can take around 12 hours. The pattern is that price falls as retrieval gets slower and as you commit to leaving data put. Then I automate it with lifecycle policies: new objects in Standard, transition to IA after 30 days, to Glacier after 90, and expire after a year. The gotchas I watch are minimum storage durations and retrieval fees, because misjudging access frequency can make a cheaper class more expensive overall.

Matches class to access pattern Knows IA retrieval fee + 30-day minimum Automates with lifecycle policies

Then they probe: When would moving data to Glacier actually cost more?

Practise this one
Junior

How does AWS decide whether an IAM request is allowed? Explain the evaluation logic.

What most people say

IAM checks the policies attached to the user and allows the action if there is a policy that allows it.

It misses default deny, that explicit deny overrides any allow, and resource policies / SCPs / boundaries. That incomplete model is exactly how people create both accidental lockouts and over-broad access.

The structure behind a strong answer

  1. 1

    Default deny. Every request starts denied. Access must be explicitly granted by some policy, or it is refused.

  2. 2

    Explicit deny wins. If any applicable policy explicitly denies the action, it is denied, full stop, no allow can override an explicit deny.

  3. 3

    Then look for an allow. With no explicit deny, AWS checks for an explicit allow across identity-based and resource-based policies. If none, the implicit default deny stands.

  4. 4

    Boundaries and SCPs cap it. Organizations SCPs and permission boundaries set a ceiling: an action must be allowed by the identity policy AND permitted by the SCP/boundary. They grant nothing, they only limit.

What gets you hired

Every request starts from an implicit deny, so nothing is allowed unless something grants it. AWS then evaluates all applicable policies with a key rule: an explicit deny always wins. If any policy, identity-based, resource-based, an SCP, or a permission boundary, explicitly denies the action, it is denied no matter what else allows it. If there is no explicit deny, AWS looks for an explicit allow across identity-based policies (on the user or role) and resource-based policies (on the resource, like an S3 bucket policy); if it finds one, allow, otherwise the default deny holds. On top of that, Organizations SCPs and permission boundaries act as ceilings, not grants: an action has to be allowed by the principal policy and also be within the SCP and boundary. So the mental model is default deny, explicit deny overrides everything, then an allow within the ceiling. That framing is what keeps me from both locking people out and handing out more than intended.

States default deny + explicit-deny-wins Knows identity vs resource policies Knows SCPs/boundaries cap, not grant

Then they probe: A user has an IAM policy allowing s3:GetObject but still gets AccessDenied. What could cause it?

Practise this one
Junior

How does an EC2 Auto Scaling Group work, and how would you configure it to scale on load?

What most people say

You set a minimum and maximum number of instances and it adds more when CPU goes high.

It captures the gist but skips the launch template, multi-AZ, health-check self-healing, and the choice of scaling policy. CPU is also not always the right signal, and there is no mention of warmup/cooldown.

The structure behind a strong answer

  1. 1

    What it manages. An ASG keeps a fleet between a min and max around a desired count, launching instances from a launch template across multiple AZs.

  2. 2

    Scaling policies. Target tracking is the default choice, keep a metric (say average CPU at 50%) on target and AWS adjusts capacity. Step scaling and scheduled scaling exist for specific needs.

  3. 3

    Health checks and replacement. The ASG replaces instances that fail EC2 or ELB health checks, so it also provides self-healing, not just scaling.

  4. 4

    Tune the dynamics. Set warmup/cooldown so new instances are counted only once ready, and scale on a metric that reflects real load (requests per target or latency, not just CPU).

What gets you hired

An Auto Scaling Group keeps a fleet between a minimum and maximum around a desired count, launching instances from a launch template and spreading them across AZs for resilience. For scaling I default to target tracking: I pick a metric and a target, like keep average CPU at 50% or requests-per-target at some number, and AWS adds or removes capacity to hold that target, which is far less fiddly than hand-tuned step thresholds. Step and scheduled scaling are there for predictable spikes or known traffic patterns. The ASG also does health-based self-healing: it replaces instances failing EC2 or ELB health checks, so it improves availability, not just throughput. The tuning that matters is warmup and cooldown, so a new instance is only counted once it is actually serving, avoiding flapping, and choosing a scaling metric that reflects real load. For a request-driven web tier I prefer requests-per-target or latency over raw CPU.

Knows launch template + min/max/desired + multi-AZ Prefers target tracking Mentions health-check self-healing and warmup/cooldown

Then they probe: Why might CPU be a poor scaling metric for a web app?

Practise this one
Mid

How do you choose between SQS, SNS, EventBridge, and Kinesis?

What most people say

They are all for sending messages between services; I would use SQS since it is the standard one.

It treats them as interchangeable and defaults to SQS. They solve different problems, fan-out, event routing, ordered streaming with replay, and picking wrong means rebuilding later.

The structure behind a strong answer

  1. 1

    SQS, a queue. Decouples a producer from a consumer that pulls and processes at its own pace. At-least-once delivery (FIFO option for ordering/exactly-once). Use for work queues and load leveling.

  2. 2

    SNS, pub/sub fan-out. Pushes one message to many subscribers at once (other services, queues, HTTP, email). Use when multiple consumers each need a copy.

  3. 3

    EventBridge, an event bus. Routes events by content-based rules to many targets, with schemas, scheduling, and SaaS/AWS-service integration. Use for event-driven architectures and decoupled routing.

  4. 4

    Kinesis, a stream. Ordered, replayable, high-throughput streaming where many consumers read the same data and you may reprocess. Use for real-time analytics, clickstreams, log/metric pipelines.

What gets you hired

I match the primitive to the pattern. SQS is a queue, it decouples a producer from a consumer that pulls work at its own pace, which is great for load leveling and work queues. It is at least once delivery, messages can sit up to 14 days, and there is a FIFO flavour when I need strict ordering and dedup, at roughly 3,000 messages a second with batching. SNS is pub sub fan out, one message pushed to many subscribers at once, which I use when several consumers each need their own copy. The classic combo is SNS into 3 or 4 SQS queues so each consumer gets a durable copy it can retry. EventBridge is an event bus that routes events by content based rules to many targets, with schema support, scheduling, and native AWS and SaaS integrations, so it is my choice for event driven architectures where producers and consumers should not know about each other. Kinesis is for streaming, ordered and replayable and high throughput, 1MB per second per shard on write, with a retention window I can set to 7 days or more so multiple consumers read the same stream and I can reprocess history. Think clickstreams, real time analytics, log pipelines. So the question I ask is, queue of work, broadcast, event routing, or an ordered replayable stream.

Distinguishes queue / pub-sub / event bus / stream Knows SNS-to-SQS fan-out Knows Kinesis for ordered replayable streaming

Then they probe: How do you fan out one event to several independent consumers durably?

Practise this one
Mid

What are VPC endpoints, and when would you use a gateway endpoint versus an interface endpoint?

What most people say

VPC endpoints let you connect to AWS services privately instead of over the internet.

Correct at a high level but stops there. It does not distinguish gateway (S3/DynamoDB, free, route-based) from interface (PrivateLink, ENI, paid), which is exactly the architect-level decision being asked.

The structure behind a strong answer

  1. 1

    The problem they solve. By default, reaching S3 or other AWS services from a private subnet goes out via a NAT gateway over the internet. VPC endpoints keep that traffic on the AWS private network instead.

  2. 2

    Gateway endpoints. For S3 and DynamoDB only. They add a route-table entry and are free. The easy win for private S3/DynamoDB access.

  3. 3

    Interface endpoints (PrivateLink). For most other AWS services and your own services. They create an ENI with a private IP in your subnet, billed hourly plus per-GB.

  4. 4

    Why bother. Security (traffic never traverses the internet), and cost (avoid NAT gateway data-processing charges for service traffic). Lock access down further with endpoint policies.

What gets you hired

A VPC endpoint lets resources in your VPC reach AWS services over the AWS private network instead of going out through a NAT gateway to the public internet. There are two kinds. A gateway endpoint is for S3 and DynamoDB only; it works by adding an entry to your route table and it is free, so it is the easy default for private S3 or DynamoDB access. An interface endpoint uses PrivateLink: it puts an elastic network interface with a private IP into your subnet and works for most other AWS services and even your own services published behind a Network Load Balancer; it is billed per hour and per GB. The reasons to use them are security and cost: traffic to the service never leaves the AWS network, and you avoid NAT gateway data-processing charges for that traffic, which adds up for something like heavy S3 access. I also attach endpoint policies to restrict exactly which buckets or actions are reachable through the endpoint.

Knows gateway = S3/DynamoDB, free, route-based Knows interface = PrivateLink ENI, paid, most services Cites security + NAT-cost savings

Then they probe: Your private instances pull large amounts of data from S3 through a NAT gateway and the bill is high. What do you change?

Practise this one
Mid

How does CloudFront work, and how would you use it to serve a web app efficiently and securely?

What most people say

CloudFront is a CDN that caches your content in different locations to make your site faster.

True but shallow. It misses origin security (OAC), path-based cache behaviors, and the invalidation strategy, which is where people actually get into trouble with stale content and surprise bills.

The structure behind a strong answer

  1. 1

    Edge caching. CloudFront caches content at edge locations close to users, so requests are served near the user instead of hitting the origin every time, cutting latency and origin load.

  2. 2

    Origins and behaviors. Origins can be S3, an ALB, or any custom origin. Cache behaviors route by path (for example /static cached hard, /api passed through) with per-path TTLs and cache keys.

  3. 3

    Lock down the origin. For S3, use Origin Access Control so the bucket only accepts requests from CloudFront, not the public internet. Add WAF and signed URLs/cookies for protected content.

  4. 4

    Invalidation strategy. Cache invalidations are slow and can cost money, so prefer versioned/fingerprinted object names (app.abc123.js) and let old versions age out, rather than invalidating on every deploy.

What gets you hired

CloudFront is the AWS CDN: it caches content at edge locations near users, so most requests are served at the edge instead of traveling to the origin, which cuts latency and offloads the origin. I point it at origins, an S3 bucket for static assets, an ALB for the dynamic app, or a custom origin, and configure cache behaviors per path: cache /static aggressively with long TTLs, and pass /api through with minimal caching and the right cache key. Security matters: for an S3 origin I use Origin Access Control so the bucket only accepts requests coming through CloudFront and is not publicly reachable, and I add WAF for filtering and signed URLs or cookies for private content. The operational gotcha is invalidation: invalidations propagate slowly and can cost money, so instead of invalidating on every deploy I use versioned asset names like app.abc123.js, which lets the browser and edge cache forever and just fetch the new filename on release, while old ones age out. That combination, edge caching, locked-down origin, and fingerprinted assets, gives fast and safe delivery without cache-staleness pain.

Explains edge caching + origin offload Uses OAC to lock the origin Has a fingerprinted-asset invalidation strategy

Then they probe: How do you avoid stale assets after a deploy without constant invalidations?

Practise this one
Mid

When would you choose DynamoDB over a relational database, and how does partition key design affect performance?

What most people say

DynamoDB is a NoSQL database that scales automatically, so use it when you need a database that handles a lot of traffic.

It is a vague "it scales" pitch. It misses the access-pattern-first modeling and the partition-key/hot-partition reality, which is where DynamoDB projects succeed or fail.

The structure behind a strong answer

  1. 1

    When to pick it. DynamoDB shines for known, high-volume access patterns needing single-digit-millisecond latency at any scale, key-value and document workloads, not ad-hoc queries or complex joins.

  2. 2

    Model access patterns first. Unlike relational, you design the table around how you will read it. Often a single-table design with composite keys, and Global Secondary Indexes for alternate access patterns.

  3. 3

    Partition key drives distribution. Data is sharded by the partition key hash. A high-cardinality, evenly-accessed key spreads load; a low-cardinality or skewed key creates a hot partition that throttles even when overall capacity is fine.

  4. 4

    Capacity and consistency. On-demand vs provisioned (with auto scaling) capacity; reads are eventually consistent by default, with a strongly-consistent option. Adaptive capacity helps with skew but good key design is the real fix.

What gets you hired

I reach for DynamoDB when the access patterns are well understood and high volume and I need single digit millisecond reads, typically p99 under 10ms, on key value or document shaped data, and I do not need ad hoc queries, joins, or complex transactions, which is where a relational database is still the right tool. The mental shift is that you model the table around your access patterns up front rather than around normalized entities. In practice that often means a single table design with composite partition and sort keys, plus a few Global Secondary Indexes, usually 2 or 3, for the alternate ways you need to query. The performance lever is the partition key, because DynamoDB shards by its hash and each physical partition caps out around 3,000 read units and 1,000 write units per second. A high cardinality key spreads load evenly. A skewed one, say partitioning by a status value or by a single large tenant, concentrates traffic on one partition and you get throttled while your table level capacity looks half idle. That is the classic hot partition problem. Adaptive capacity softens it, but the real fix is composing a key that distributes, for example suffixing with a shard number 1 through 10. For capacity I use on demand when traffic is unpredictable and provisioned with auto scaling when it is steady, and I default to eventually consistent reads, which cost half as much, opting into strong consistency only where I truly need it.

Access-pattern-first modeling / single-table Explains partition key distribution + hot partitions Knows when relational is the better choice

Then they probe: Your DynamoDB table throttles under load even though provisioned capacity looks adequate. What is the likely cause?

Practise this one
Senior

Leadership says the AWS bill is too high. How do you systematically bring it down?

What most people say

I would look for large instances and switch them to smaller ones to reduce the bill.

It jumps straight to shrinking instances with no measurement or attribution, and ignores the biggest levers: commitments, Spot, storage tiering, and especially data-transfer costs that often dominate and that rightsizing never touches.

The structure behind a strong answer

  1. 1

    Get visibility first. You cannot cut what you cannot see. Use Cost Explorer and the Cost and Usage Report, and enforce tagging so spend is attributed to teams/services. Find the biggest line items.

  2. 2

    Rightsize and remove waste. Use Compute Optimizer to rightsize over-provisioned instances, and delete idle waste: unattached EBS volumes, old snapshots, unused elastic IPs, idle load balancers, forgotten dev environments.

  3. 3

    Commit and use Spot. Cover steady baseline usage with Savings Plans or RIs, and move interruptible/batch workloads to Spot. Consider Graviton (ARM) for better price-performance.

  4. 4

    Storage and data transfer. Apply S3 lifecycle policies and the right storage classes. Then the trap: data transfer, cross-AZ and internet egress charges. VPC endpoints and CloudFront cut a lot of it.

  5. 5

    Make it ongoing. Set budgets and anomaly alerts, and build cost review into the team rhythm so it does not creep back up.

What gets you hired

I start with visibility, because optimizing blind just breaks things. Cost Explorer and the Cost and Usage Report show where the money actually goes, and I enforce tagging so I can attribute spend to teams and services and target the biggest line items first. Then waste: Compute Optimizer to rightsize over-provisioned compute, and a sweep for idle resources, unattached EBS volumes, stale snapshots, unused elastic IPs, idle load balancers, dev environments left running overnight. Next the structural levers: cover steady baseline usage with Savings Plans or RIs, move batch and interruptible workloads to Spot, and look at Graviton for cheaper price-performance. On storage, apply S3 lifecycle policies and appropriate classes. Then the one people miss: data transfer. Cross-AZ chatter and internet egress can quietly dominate a bill, and VPC endpoints and CloudFront cut a lot of it, plus keeping chatty components in the same AZ. Finally I make it durable with budgets, anomaly detection, and a regular cost review, so the savings stick instead of creeping back. The order matters: measure and attribute, kill waste, commit, then tackle storage and transfer.

Measure + tag/attribute before cutting Names commitments, Spot, rightsizing, lifecycle Calls out data-transfer/cross-AZ costs

Then they probe: What cost driver do engineers most often overlook?

Practise this one
Senior

What are the real pitfalls of running a Lambda-based serverless architecture at scale?

What most people say

The main downside is cold starts adding some latency to the first request.

Cold starts are real but the least of it at scale. The senior concerns, concurrency throttling, hammering a database, idempotency under at-least-once delivery, are missing entirely.

The structure behind a strong answer

  1. 1

    Cold starts. A new execution environment adds startup latency, worse for large packages or VPC-attached functions. Mitigate with provisioned concurrency for latency-sensitive paths and slim packages.

  2. 2

    Concurrency limits and throttling. There is an account concurrency limit (1,000 by default). Bursts beyond it throttle (429s). Use reserved concurrency to protect critical functions and to cap others.

  3. 3

    Overwhelming downstreams. Lambda scales out fast and can open thousands of connections to a database, exhausting it. Put RDS Proxy in front, or buffer with SQS so the consumer drains at a safe rate.

  4. 4

    Idempotency and retries. Async and event-source invocations are at-least-once, so handlers must be idempotent. Use a dedup key, and configure DLQs or on-failure destinations so failures are not lost.

  5. 5

    Limits and orchestration. Mind the timeout (15 min max), payload sizes, and /tmp limits. Use Step Functions to orchestrate multi-step workflows instead of chaining Lambdas by hand.

What gets you hired

Cold starts are the famous one, a new environment adds startup latency, worse with big packages or VPC attachment, and I handle it with provisioned concurrency on latency-sensitive paths and lean packages. But at scale the bigger issues are elsewhere. Concurrency: there is an account limit, 1,000 by default, and bursts past it get throttled with 429s, so I use reserved concurrency to guarantee headroom for critical functions and to cap noisy ones. Downstream pressure is the classic trap: Lambda scales out so aggressively that it can open thousands of database connections and take RDS down, so I put RDS Proxy in front to pool connections, or buffer through SQS so a consumer drains at a controlled rate. Idempotency is non-negotiable because async and event-source invocations are at-least-once, so every handler needs a dedup key and safe retries, with DLQs or on-failure destinations so nothing is silently lost. And I respect the limits, the 15-minute max timeout, payload and /tmp sizes, and I orchestrate multi-step flows with Step Functions rather than chaining functions and reinventing retries and state. Serverless removes server management, but these operational edges are what you actually design around.

Goes beyond cold starts to concurrency/throttling Knows Lambda can overwhelm RDS (Proxy/SQS buffer) Insists on idempotency + DLQs

Then they probe: Your Lambda functions are taking down your RDS database under load. Why, and what fixes it?

Practise this one
Senior

Users report intermittent 5xx errors and slow responses on a production app behind an ALB, on ECS, talking to RDS. How do you diagnose it?

What most people say

I would restart the ECS tasks and scale up the service to handle the load.

It acts before diagnosing. Restarting and scaling might mask a flapping health check or a database bottleneck temporarily, but without finding whether it is the LB, the app, or RDS, it will recur.

The structure behind a strong answer

  1. 1

    Split ALB 5xx from target 5xx. CloudWatch distinguishes HTTPCode_ELB_5XX (the LB could not get a healthy response: no healthy targets, timeouts) from HTTPCode_Target_5XX (the app returned the error). That immediately points at infra vs app.

  2. 2

    Check target health and scaling. Are targets failing health checks and flapping in/out? Is the ECS service hitting CPU/memory limits or task throttling, and is it scaled for the load?

  3. 3

    Walk the latency per hop. ALB target response time, then ECS task metrics, then RDS. Use X-Ray traces to see which segment is slow, the app, a downstream call, or the database.

  4. 4

    Inspect RDS. CPU, freeable memory, and especially connection count and slow queries. A connection pool exhausted or a missing index often shows up as intermittent slowness and 5xx under load.

  5. 5

    Correlate with change. Line it up against deploys, traffic spikes, or scaling events. Intermittent issues that started at a deploy or a traffic peak narrow the cause fast.

What gets you hired

I localize the layer before touching anything. First I split the 5xx in CloudWatch: ELB 5xx means the load balancer could not get a healthy response, no healthy targets or upstream timeouts, while Target 5xx means the app itself returned the error. That one split tells me whether I am chasing infrastructure or application. Then target health: are ECS tasks failing health checks and flapping in and out of the target group, and is the service hitting CPU or memory limits or being throttled? Then latency per hop, ALB target response time, ECS task metrics, and RDS, using X-Ray traces to pinpoint which segment is slow, the app, a downstream dependency, or the database. RDS is a prime suspect for this signature: I check CPU and freeable memory, but especially connection count and slow queries, because an exhausted connection pool or a missing index shows up exactly as intermittent slowness and 5xx under load. Throughout, I correlate with change, a recent deploy, a traffic spike, a scaling event, since intermittent problems that began at a known moment narrow the cause quickly. Only once I know the layer do I act, which might be fixing a query, adding RDS Proxy, tuning health checks, or scaling, rather than blindly restarting.

Splits ELB vs Target 5xx first Checks target health/flapping + task limits Uses X-Ray + RDS connection/slow-query metrics

Then they probe: What does the difference between ELB 5xx and Target 5xx tell you?

Practise this one
Principal

Design an active-active multi-region architecture on AWS. What are the hard parts?

What most people say

I would deploy the app in two regions and use Route 53 to send traffic to both, with database replication between them.

It names the topology but waves away the actual hard problem: how writes are kept consistent across regions and what happens on conflict. "Database replication between them" hides the multi-master conflict question that defines the design.

The structure behind a strong answer

  1. 1

    Challenge the requirement. Active-active is the most expensive and complex option. First ask why: latency, regional resilience, or data residency? Many cases are better served by active-passive DR, so I confirm the driver.

  2. 2

    Route users. Route 53 with latency-based or geolocation routing plus health checks sends users to the nearest healthy region and fails traffic over automatically. AWS Global Accelerator is an alternative for TCP/UDP.

  3. 3

    Stateless compute is the easy part. Run the full stack in each region (ALB, ECS/EKS or Lambda, auto scaling). Replicate artifacts and config. This part scales cleanly.

  4. 4

    Data is where it lives or dies. Pick per data class. DynamoDB Global Tables give multi-region multi-master with last-writer-wins (eventual). Aurora Global Database gives fast cross-region read replicas and quick promotion, but one write region. S3 with Cross-Region Replication for objects.

  5. 5

    Own the consistency trade-off. For true multi-master, decide conflict handling explicitly (last-writer-wins vs partition writes by home region vs app-level merge), keep operations idempotent, and state the CAP choice out loud. Test regional failover regularly.

What gets you hired

First I would challenge the requirement, because active-active is the priciest, most complex option, so is this for latency, regional resilience, or data residency? Often active-passive DR is the better answer, so I confirm the driver. Assuming active-active is justified: routing is straightforward with Route 53 latency-based or geolocation routing plus health checks, which sends each user to the nearest healthy region and fails over automatically, with Global Accelerator as an option for non-HTTP. Stateless compute is the easy part, run the whole stack in each region behind its own ALB with auto scaling, and replicate artifacts and config. The hard part, always, is data. I decide per data class. DynamoDB Global Tables give genuine multi-region multi-master, but resolution is last-writer-wins and reads are eventually consistent, so it fits data that tolerates that. Aurora Global Database gives low-latency cross-region read replicas and fast promotion, but there is a single write region, so it is closer to active-passive for writes. S3 Cross-Region Replication handles objects. The thing a principal has to own is the consistency trade-off: for true multi-master I decide conflict handling deliberately, last-writer-wins, or partition writes so each record has a home region, or application-level merge for things like profiles, keep every operation idempotent, and I say the CAP choice out loud, during a partition, strongly-consistent data favors consistency over availability and I accept that. And I rehearse regional failover, because a DR path you have never tested does not exist.

Challenges the requirement first Names Route 53, DynamoDB Global Tables, Aurora Global, S3 CRR correctly Centers data consistency / conflict resolution

Then they probe: What is the single hardest part of active-active, and why?

Practise this one

Kubernetes

16 questions · Foundation, Junior, Mid, Senior, Principal
Foundation

What problem does Kubernetes solve, and what does it actually do for you?

What most people say

Kubernetes is a tool for running and managing containers.

True but empty. It does not mention desired-state reconciliation or self-healing, which are the whole point, and it gives no sense of when the complexity is worth it.

The structure behind a strong answer

  1. 1

    The problem. Running many containers across many machines by hand is unmanageable: placement, restarts, scaling, networking, and rollouts all become manual toil.

  2. 2

    Desired state. You declare what you want (this image, 5 replicas, this config) and Kubernetes continuously reconciles reality to match it.

  3. 3

    What you get. Self-healing (restart or reschedule failed pods), scaling, rolling updates and rollbacks, service discovery, and load balancing, all automated.

  4. 4

    The cost. That power comes with real operational complexity, so it is worth it when you have enough scale or enough services to justify it.

What gets you hired

Kubernetes is a container orchestrator, and the core idea is desired-state reconciliation. Doing this by hand, running containers across a fleet of machines means manually handling placement, restarts, scaling, networking, and deploys, and that stops being practical fast. With Kubernetes I declare the desired state, this image, 3 replicas, this config, and controllers loop continuously, comparing actual to desired and closing the gap within seconds. Everything good falls out of that one idea. Self-healing, so a crashed pod is restarted and a node that goes unready has its pods rescheduled elsewhere. Horizontal scaling, where I can tell the autoscaler to hold pods around 70 percent CPU and it moves the replica count for me. Rolling updates, where by default it brings up 25 percent extra pods and takes at most 25 percent down at a time, so the app stays serving while the new version rolls in, and rollbacks when it does not. Plus service discovery and load balancing across whatever replicas exist right now. The honest caveat is that all of this is real operational complexity, a control plane to run, YAML to own, networking to understand. I would reach for it when the number of services or the scale genuinely justifies it, not for 1 small app that a managed container service would run happily.

Explains desired-state reconciliation Names self-healing/scaling/rollouts Acknowledges the complexity trade-off

Then they probe: What does "desired state reconciliation" mean in practice?

Practise this one
Foundation

What are the main components of a Kubernetes cluster (control plane and nodes)?

What most people say

There is a master node and worker nodes, and the workers run the containers.

Outdated "master" framing and no detail. It cannot name the API server, etcd, scheduler, kubelet, or kube-proxy, so it gives no foothold for diagnosing where something broke.

The structure behind a strong answer

  1. 1

    API server. The front door: every component and user talks to the cluster through the API server. It validates and persists changes.

  2. 2

    etcd. The cluster database: a consistent key-value store holding all cluster state. If you lose etcd, you lose the cluster state, so it is backed up.

  3. 3

    Scheduler and controllers. The scheduler assigns pending pods to nodes; the controller-manager runs the reconciliation loops that drive actual state toward desired.

  4. 4

    Node components. On each worker node: the kubelet runs and reports on pods, kube-proxy handles service networking, and a container runtime (containerd) actually runs the containers.

What gets you hired

A cluster splits into the control plane and the worker nodes. On the control plane, the API server is the front door, listening on 6443, and everything talks through it, kubectl, the controllers, and every kubelet. It validates changes and persists them. etcd is the backing store, a consistent key-value database holding all cluster state, and it uses Raft, so production runs 3 or 5 members to keep quorum. That is also why etcd backups are non-negotiable, because losing etcd is losing the cluster. The scheduler decides which node each pending pod lands on based on resource requests and constraints. The controller-manager runs the reconciliation loops that keep actual state matching desired. On every worker node, the kubelet runs the pods assigned to it and reports status back, kube-proxy programs the networking that makes Services work, and a container runtime like containerd actually launches the containers. Knowing this split is what makes debugging tractable. Pods stuck Pending points at the scheduler and node capacity. A node going NotReady after roughly 40 seconds of missed heartbeats, with its pods evicted about 5 minutes later, points at that node's kubelet or network. Pods failing on exactly 1 node points at that node's runtime.

Names API server, etcd, scheduler, controller-manager Knows kubelet/kube-proxy/runtime on nodes Connects components to debugging

Then they probe: Why is etcd so important, and what follows from that?

Practise this one
Foundation

What is a Pod, and why do you use a Deployment instead of creating Pods directly?

What most people say

A pod is a container, and a Deployment is just a way to create pods.

A pod is not exactly a container (it can hold several and adds shared networking), and "just a way to create pods" misses the whole value: replica management, self-healing, and rollouts.

The structure behind a strong answer

  1. 1

    Pod basics. A pod is the smallest deployable unit: one or more containers that share a network namespace (same IP) and can share storage. It is ephemeral.

  2. 2

    Bare pods are fragile. A pod created directly is not recreated if its node dies, has no scaling, and no rollout mechanism. When it is gone, it is gone.

  3. 3

    Deployment adds a controller. A Deployment manages a ReplicaSet that keeps the desired number of pod replicas running, recreating them on failure.

  4. 4

    Rollouts for free. Deployments give declarative rolling updates and rollbacks: change the image and it replaces pods gradually, with revision history to roll back.

What gets you hired

A pod is the smallest thing Kubernetes schedules. It is 1 or more containers that share a network namespace, so they share an IP and can talk to each other over localhost, and they can share volumes. That is how sidecar patterns work. Pods are ephemeral by design. I almost never create them directly, because a bare pod has no safety net. If its node dies, nothing brings it back, there is no scaling, and there is no controlled way to update it. A Deployment fixes that by putting a controller in front. It manages a ReplicaSet that holds the desired replica count, so a pod that dies is recreated in seconds, and scaling from 3 replicas to 10 is one number changing in one field. It also gives me declarative rollouts. I update the image tag and the Deployment replaces pods gradually, by default surging up to 25 percent extra and keeping at least 75 percent of capacity serving, so the app stays available through the change. And it keeps revision history, 10 revisions by default, so if the new version starts throwing errors I can roll back with a single command and be on the old image in under a minute. So the Deployment is the controller that turns a fragile pod into a resilient, updatable workload.

Knows a pod can hold multiple containers sharing the network Explains Deployment = replicas + self-healing + rollouts Knows Deployment manages ReplicaSets

Then they probe: What is the relationship between a Deployment, a ReplicaSet, and Pods?

Practise this one
Foundation

What is a Service in Kubernetes, and what are the main types?

What most people say

A Service is how you expose your pods so other things can reach them.

Directionally right but vague. It never explains why (ephemeral pod IPs), how (label selection), or the type distinctions (ClusterIP vs NodePort vs LoadBalancer), which is what the question asks.

The structure behind a strong answer

  1. 1

    Why it exists. Pods are ephemeral and their IPs change as they restart or reschedule. A Service gives a stable virtual IP and DNS name in front of a set of pods.

  2. 2

    Selection and balancing. A Service selects pods by labels and load-balances traffic across the healthy ones (those that pass readiness).

  3. 3

    ClusterIP and NodePort. ClusterIP is the default, reachable only inside the cluster. NodePort opens a port on every node, exposing the service externally in a basic way.

  4. 4

    LoadBalancer and Ingress. LoadBalancer provisions a cloud load balancer for external traffic. For HTTP routing across many services, an Ingress (with an ingress controller) is the L7 layer above Services.

What gets you hired

Pods are ephemeral and their IPs change every time they restart or reschedule, so you cannot point clients at a pod IP. A Service solves that: it provides a stable virtual IP and a DNS name that fronts a set of pods, selected by labels, and load-balances across the ones that are ready. The types differ by reach. ClusterIP, the default, is only reachable inside the cluster, which is right for internal service-to-service traffic. NodePort opens a fixed port on every node so the service is reachable from outside in a basic way, but it is rarely used directly in production. LoadBalancer provisions an actual cloud load balancer pointing at the service, which is the usual way to expose something externally on a managed cluster. And above all of these, for HTTP, an Ingress plus an ingress controller gives L7 routing, host and path based, TLS termination, so many services can share one entry point instead of one load balancer each.

Explains the ephemeral-IP problem Knows label selection + readiness-based balancing Distinguishes ClusterIP/NodePort/LoadBalancer and Ingress

Then they probe: What is the difference between a Service and an Ingress?

Practise this one
Junior

When would you use a Deployment versus a StatefulSet versus a DaemonSet?

What most people say

You use a Deployment for most things; StatefulSets and DaemonSets are for special cases.

It does not say what makes those cases special. The distinction (stable identity/storage for StatefulSet, one-per-node for DaemonSet) is exactly what is being tested.

The structure behind a strong answer

  1. 1

    Deployment. For stateless, interchangeable replicas: web servers, APIs, workers. Pods are identical and order does not matter.

  2. 2

    StatefulSet. For workloads needing stable identity and stable per-pod storage: databases and clustered systems. Pods get stable names, ordered startup/shutdown, and their own persistent volumes.

  3. 3

    DaemonSet. For one pod per node: node-level agents like log collectors, metrics agents, and CNI plugins. As nodes join, the pod is placed automatically.

  4. 4

    Plus Jobs. For run-to-completion work, a Job (or a CronJob for scheduled work) rather than a long-running controller.

What gets you hired

I match the controller to the shape of the workload. A Deployment is for stateless, interchangeable replicas, web servers, APIs, background workers, where every pod is identical and order does not matter. I can run 3 replicas or 30 and the rollout can replace them in any order, which covers most application workloads. A StatefulSet is for workloads that need stable identity and stable storage, so databases, message brokers, clustered systems. Each pod gets a stable ordinal name, kafka-0, kafka-1, kafka-2, a stable network identity, ordered startup and shutdown, and its own persistent volume that follows it across restarts. A Deployment simply cannot give you that. A DaemonSet runs exactly 1 pod per node, which is what node level agents need, log shippers, metrics agents, CNI or storage plugins. On a 40 node cluster that means 40 pods, and when node 41 joins, the DaemonSet schedules onto it automatically. For run to completion work rather than long running services I use a Job, or a CronJob for something scheduled, like a backup that runs at 2am. The classic mistake is running a stateful database as a plain Deployment and then wondering why the pod came back with a different name and an empty volume.

Deployment=stateless, StatefulSet=identity+storage, DaemonSet=per-node Knows StatefulSet ordering + stable volumes Mentions Jobs/CronJobs

Then they probe: Why does a database need a StatefulSet rather than a Deployment?

Practise this one
Junior

How do you inject configuration and secrets into a pod, and what is the catch with Kubernetes Secrets?

What most people say

You put secrets in a Kubernetes Secret, which keeps them safe and separate from the code.

"Keeps them safe" is the dangerous misconception. Secrets are base64, not encrypted, so without encryption at rest and RBAC they are not actually protected. This is a frequent real-world gap.

The structure behind a strong answer

  1. 1

    ConfigMaps for config. Non-sensitive config goes in a ConfigMap, injected as environment variables or mounted as files, so the same image runs in every environment.

  2. 2

    Secrets for sensitive values. Passwords, tokens, and keys go in Secrets, consumed the same way (env or volume), kept out of the image and out of git.

  3. 3

    The catch. A Secret is only base64-encoded, not encrypted. Anyone with API or etcd access can read it unless you enable encryption at rest and lock down RBAC.

  4. 4

    Do it properly. Enable etcd encryption at rest, restrict access with RBAC, prefer mounted files over env vars, and for real secret management use an external store (Vault, cloud secrets manager) via a CSI driver or external-secrets operator.

What gets you hired

I keep configuration out of the image so the same artifact runs everywhere. Non-sensitive config goes in a ConfigMap, injected as environment variables or mounted as files. Sensitive values, database passwords, API keys, tokens, go in Secrets, consumed the same way and kept out of git. The catch that trips people up is that a Kubernetes Secret is only base64-encoded, which is encoding, not encryption. By default, anyone who can read the object through the API or get at etcd can decode it. So to actually protect secrets I enable encryption at rest for etcd, lock down access with RBAC so only the right service accounts can read them, and I prefer mounting secrets as files over environment variables because env vars leak more easily into logs and child processes. For anything serious I integrate an external secrets manager, Vault or a cloud secrets service, through the Secrets Store CSI driver or the external-secrets operator, so the source of truth is a real vault with rotation and audit, not a base64 blob in etcd.

Separates ConfigMap (config) from Secret (sensitive) Knows Secrets are base64, not encrypted Mentions encryption at rest + RBAC + external secret stores

Then they probe: Are Kubernetes Secrets encrypted by default?

Practise this one
Junior

What is the difference between liveness, readiness, and startup probes?

What most people say

They are health checks that tell Kubernetes whether the container is working.

It lumps all three together. The whole point is that they do different things, restart vs pull from traffic vs gate startup, and conflating them leads to restart loops and dropped traffic.

The structure behind a strong answer

  1. 1

    Liveness. Answers "is this container still healthy". On failure, the kubelet restarts the container. Use it to recover from deadlocks and hangs.

  2. 2

    Readiness. Answers "can this pod serve traffic right now". On failure, the pod is removed from Service endpoints but NOT restarted. Use it for warmup and temporary dependency loss.

  3. 3

    Startup. Gates the other probes for slow-starting apps: liveness and readiness do not run until the startup probe passes, so a slow boot is not mistaken for a failure.

  4. 4

    The danger. A liveness probe that is too aggressive (or that checks a dependency) creates restart loops: the app is fine but keeps getting killed. Liveness should check the app itself, not downstreams.

What gets you hired

They look similar but they do 3 different things. A liveness probe asks whether the container is still healthy, and if it fails, the kubelet restarts the container, so it is how you recover from a hang or a deadlock. A readiness probe asks whether the pod can serve traffic right now, and if it fails, the pod is pulled from the Service endpoints but not restarted. That is exactly what you want during warmup or when a dependency blips, you stop sending it traffic without killing it. A startup probe is for slow starting applications, and it gates the other 2, so liveness and readiness do not even run until startup succeeds. The defaults matter here, a periodSeconds of 10 and a failureThreshold of 3 mean a container is restarted about 30 seconds after it goes bad, and a JVM that needs 2 minutes to boot would be killed 4 times before it ever came up. A startup probe with a failureThreshold of 30 gives it a 5 minute budget and prevents exactly that. The big danger is a misconfigured liveness probe. If it is too aggressive, or worse, if it checks a downstream dependency, 1 slow database can restart loop every healthy pod you have at once. So liveness should test the app itself, and dependency or warmup conditions belong in readiness.

Liveness=restart, readiness=remove from traffic, startup=gate Knows readiness does not restart Warns about aggressive/dependency-checking liveness

Then they probe: Why should a liveness probe not check a database connection?

Practise this one
Junior

What is the difference between resource requests and limits, and what happens when a pod exceeds them?

What most people say

Requests are the minimum resources and limits are the maximum, and the pod stays within them.

It misses what each is actually for (requests drive scheduling) and the critical asymmetry: CPU over-limit is throttled but memory over-limit is killed. That difference is exactly what causes mysterious OOMKills.

The structure behind a strong answer

  1. 1

    Requests = scheduling. A request is what the pod is guaranteed and what the scheduler uses to place it. The node must have the requested amount free.

  2. 2

    Limits = the cap. A limit is the maximum the container may use. It is enforced at runtime.

  3. 3

    CPU vs memory on overage. Exceeding a CPU limit gets you throttled (slowed, not killed). Exceeding a memory limit gets the container OOMKilled, because memory cannot be compressed.

  4. 4

    QoS and right-sizing. Requests and limits set the pod QoS class (Guaranteed, Burstable, BestEffort), which decides eviction order under pressure. Set requests from real usage: too low risks eviction, too high wastes money.

What gets you hired

A request is what the pod is guaranteed, and crucially it is what the scheduler uses to place it. If a pod requests 500 millicores and 1 gigabyte of memory, a node needs that much free to accept it at all. A limit is the hard ceiling on what the container may actually use at runtime. The behavior on overage is asymmetric, and that is the part people miss. If a container exceeds its CPU limit it gets throttled, slowed down but kept alive, because CPU is compressible. If it exceeds its memory limit it gets OOMKilled, exit code 137, because memory cannot be reclaimed on the fly. So a memory limit set 20 percent too low does not show up as slowness, it shows up as containers dying and restarting under load. Requests and limits together also set the QoS class, Guaranteed when they are equal, Burstable when the request is lower than the limit, BestEffort when neither is set, and that order decides who gets evicted first when a node runs out of memory. BestEffort goes first. So I set requests from observed real usage, roughly the p95 plus a little headroom. Too low and pods get starved or evicted, too high and I reserve 4 gigabytes on every node that nothing ever touches, which is money burned every hour.

Requests drive scheduling, limits cap usage Knows CPU throttled vs memory OOMKilled Mentions QoS/eviction and right-sizing for cost

Then they probe: A container keeps getting OOMKilled. What do you check?

Practise this one
Mid

How does the scheduler decide where a pod runs, and how do you influence it with affinity and taints?

What most people say

The scheduler puts the pod on a node that has enough resources.

Resource fit is only the first filter. It misses affinity, taints/tolerations, and spreading, which are the actual tools for controlling placement and the source of "why is my pod Pending".

The structure behind a strong answer

  1. 1

    Filter then score. The scheduler first filters nodes that cannot run the pod (insufficient resources, unsatisfied selectors, untolerated taints), then scores the rest and picks the best.

  2. 2

    nodeSelector and affinity. nodeSelector is a simple label match. Node affinity is the richer version (required vs preferred). Pod affinity/anti-affinity place pods relative to other pods (co-locate or spread).

  3. 3

    Taints and tolerations. A taint on a node repels pods unless they have a matching toleration. It is the inverse of affinity: nodes reject pods rather than pods choosing nodes.

  4. 4

    Spreading. Topology spread constraints and anti-affinity distribute replicas across nodes/zones so one failure does not take them all out.

What gets you hired

The scheduler works in 2 phases. First it filters, dropping any node that cannot run the pod, not enough free CPU or memory against the pod's requests, node selectors that do not match, taints that are not tolerated. Then it scores the survivors and places the pod on the best one. To influence that, there are two complementary mechanisms. Affinity attracts. nodeSelector is a plain label match, node affinity is the richer form with required and preferred rules, where a preferred rule carries a weight from 1 to 100, and pod affinity and anti affinity place a pod relative to other pods, so I can co locate two services that chat constantly or keep replicas apart. Taints and tolerations are the inverse, a taint repels every pod unless the pod carries a matching toleration, which is how you reserve nodes, say a pool of 4 GPU nodes or the control plane, for specific workloads. For resilience I use topology spread constraints or anti affinity with a maxSkew of 1 across zones, so 6 replicas land 2 per zone across 3 availability zones and losing one zone never takes the whole service down. And most pod stuck in Pending cases trace right back to this filter phase, no node satisfied the requests, the selectors, or the tolerations.

Knows filter-then-score Distinguishes affinity (attract) from taints (repel) Uses spread/anti-affinity for resilience

Then they probe: A pod is stuck in Pending. What scheduling causes do you check?

Practise this one
Mid

How does a rolling update work in Kubernetes, and how do you do zero-downtime deploys and rollbacks?

What most people say

Kubernetes replaces the old pods with new ones when you update the image.

It describes the what but not the how. Without readiness gating and maxSurge/maxUnavailable, the rollout is not actually safe, and the answer says nothing about rollback or canary.

The structure behind a strong answer

  1. 1

    Rolling update mechanics. A Deployment creates a new ReplicaSet and shifts pods over gradually, controlled by maxSurge (how many extra above desired) and maxUnavailable (how many can be down at once).

  2. 2

    Readiness makes it safe. New pods only receive traffic once they pass readiness, and old pods are not removed until new ones are ready, so a broken new version does not take traffic.

  3. 3

    Rollback. Each rollout keeps a ReplicaSet in history, so kubectl rollout undo scales the previous ReplicaSet back up, which is why rollback is fast.

  4. 4

    Advanced strategies. For higher safety, canary (shift a small percentage first) or blue-green (switch traffic between two full versions), often via Argo Rollouts or a service mesh for progressive delivery.

What gets you hired

When I change a Deployment it does not replace everything at once. It creates a new ReplicaSet and shifts pods over gradually, governed by two settings, maxSurge, how many pods above the desired count it may add, and maxUnavailable, how many it may take down at a time. Both default to 25 percent, so on 8 replicas it brings up 2 extra and takes at most 2 down. The thing that makes it zero downtime is the readiness probe. A new pod only receives traffic once it reports ready, and old pods are kept until the new ones are, so if the new version is broken and never goes ready the rollout stalls and 100 percent of traffic keeps hitting the healthy old pods. Rollback is fast because each rollout keeps the previous ReplicaSets in revision history, 10 by default, so kubectl rollout undo just scales the old one back up, usually inside a minute. For riskier changes I go past a plain rolling update to a canary, send 5 percent of traffic to the new version and watch error rate and latency for 10 minutes before going further, or blue green, run both and switch at once, typically with Argo Rollouts or a service mesh for metric driven promotion and automatic rollback. The mistakes I avoid are a missing readiness probe, which serves traffic to unready pods, and a maxUnavailable set so high it drops capacity mid deploy.

Explains maxSurge/maxUnavailable Knows readiness gating enables zero-downtime Knows rollback uses ReplicaSet history; mentions canary/blue-green

Then they probe: Why are readiness probes essential for a safe rolling update?

Practise this one
Mid

How does networking work in Kubernetes: pod-to-pod, Services, and Ingress?

What most people say

Pods talk to each other over the network and Services route traffic to them.

It states the outcome but none of the mechanism: the flat pod network, the CNI, kube-proxy, cluster DNS, or NetworkPolicies. That mechanism is what you need when connectivity breaks.

The structure behind a strong answer

  1. 1

    Flat pod network. Every pod gets its own IP and can reach every other pod directly, no NAT between pods. A CNI plugin (Calico, Cilium) implements this.

  2. 2

    Services and kube-proxy. A Service gives a stable virtual IP; kube-proxy (via iptables/IPVS, or eBPF in some CNIs) routes that IP to healthy backing pods and load-balances.

  3. 3

    Cluster DNS. CoreDNS resolves Service names to their ClusterIP, so apps talk to service-name rather than pod IPs.

  4. 4

    Ingress and policies. An Ingress controller provides L7 HTTP routing and TLS at the edge above Services. NetworkPolicies segment traffic, by default everything can talk, until a policy restricts it.

What gets you hired

The model starts with a flat pod network: every pod gets its own IP and can reach any other pod directly without NAT, and a CNI plugin like Calico or Cilium actually implements that networking. On top of that, a Service provides a stable virtual IP for a set of pods, and kube-proxy programs the data path, traditionally with iptables or IPVS rules, or eBPF in newer CNIs, to route the service IP to a healthy backing pod and load-balance. Service discovery is by name: CoreDNS resolves a Service DNS name to its ClusterIP, so applications connect to a name, not an ephemeral pod IP. For external HTTP traffic, an Ingress controller sits at the edge and does L7 routing by host and path plus TLS termination, so many services share one entry point. And for security, NetworkPolicies segment traffic, the important default being that without any policy all pods can talk to each other, so I add default-deny policies and then explicitly allow what is needed. When connectivity breaks, I work down this stack: DNS resolving, the Service has endpoints, the pods are ready, and no NetworkPolicy is blocking.

Knows flat pod network + CNI Explains Service/kube-proxy + CoreDNS Knows default allow-all and NetworkPolicies; Ingress for L7

Then they probe: A Service exists but requests to it fail. What do you check?

Practise this one
Mid

What are the ways to autoscale in Kubernetes (HPA, VPA, Cluster Autoscaler), and how do they fit together?

What most people say

You use the Horizontal Pod Autoscaler to add more pods when CPU is high.

HPA alone is not the full picture. If the cluster has no spare capacity, more pods just go Pending, so you also need node scaling. It also misses VPA for right-sizing.

The structure behind a strong answer

  1. 1

    HPA scales replicas. The Horizontal Pod Autoscaler adds or removes pod replicas based on CPU, memory, or custom/external metrics, to handle changing load.

  2. 2

    VPA scales resources. The Vertical Pod Autoscaler adjusts a pod requests and limits to fit its actual usage. It right-sizes rather than adding replicas (and generally should not run on the same workload as HPA on the same metric).

  3. 3

    Cluster Autoscaler scales nodes. When pods cannot be scheduled for lack of capacity, the Cluster Autoscaler adds nodes; when nodes are underused, it removes them. Karpenter is a popular modern alternative.

  4. 4

    They compose. HPA adds pods, and if there is no room the Cluster Autoscaler adds nodes to place them. Different layers: pods, pod-size, and nodes.

What gets you hired

There are 3, and the key is that they work at different layers. The Horizontal Pod Autoscaler scales out, adding or removing pod replicas against CPU, memory, or custom and external metrics, and a target like 70 percent CPU is a common starting point. It is the usual answer to load. The Vertical Pod Autoscaler scales up, adjusting a pod's requests and limits toward real usage, so a pod asking for 1 CPU but using 200 millicores gets right sized. You generally do not point VPA and HPA at the same metric on the same workload, because they fight each other. The Cluster Autoscaler scales the cluster itself, adding nodes when pods cannot schedule and removing nodes that sit underused for about 10 minutes, with Karpenter as the popular modern alternative that provisions right sized nodes in well under a minute. The part interviewers care about is how they compose. HPA can decide it needs 20 replicas, but if no node has room those extra pods sit in Pending until the node autoscaler brings capacity, so pod scaling without node scaling just changes where you queue. A complete story is usually HPA for load, a node autoscaler for capacity, and VPA or carefully set requests for right sizing.

Distinguishes pods (HPA), pod-size (VPA), nodes (Cluster Autoscaler) Knows HPA pods go Pending without node scaling Knows HPA+VPA conflict; mentions Karpenter

Then they probe: You set up HPA but under load new pods stay Pending. Why?

Practise this one
Mid

A pod is not running. Walk me through debugging Pending, ImagePullBackOff, and CrashLoopBackOff.

What most people say

I would delete the pod and let Kubernetes recreate it.

Recreating a deterministically failing pod just reproduces the failure. Pending, ImagePullBackOff, and CrashLoopBackOff each have specific causes that describe and logs reveal; deleting skips the diagnosis entirely.

The structure behind a strong answer

  1. 1

    Start with describe and events. kubectl describe pod and kubectl get events show why the pod is in its state, the events section usually names the exact reason.

  2. 2

    Pending. It cannot be scheduled: insufficient CPU/memory for the requests, an unsatisfied nodeSelector/affinity, an untolerated taint, or an unbound PersistentVolumeClaim.

  3. 3

    ImagePullBackOff. It cannot pull the image: wrong image name or tag, the registry is unreachable, or missing/incorrect pull credentials (imagePullSecrets).

  4. 4

    CrashLoopBackOff. The container starts then keeps crashing. Read the logs, including the previous container (kubectl logs --previous), check the exit code, and look for a bad config, missing dependency, failed migration, or an OOMKill.

What gets you hired

I always start the same way, kubectl describe pod and read the last 10 or so events, because the events section almost always states the reason directly. Then I read the status. Pending means it could not be scheduled, so I look for insufficient CPU or memory against the requests, and I have seen a pod ask for 4 CPU when every node only had 2 allocatable, a nodeSelector or affinity nothing satisfies, a taint with no toleration, or a PersistentVolumeClaim that is not bound. ImagePullBackOff means it could not pull the image, so I check the image name and tag for typos, whether the registry is reachable, and whether the imagePullSecrets are present and correct, which is the usual cause on a private registry. CrashLoopBackOff means the container starts and keeps exiting, and the restart backoff doubles up to a 5 minute cap, so I stop waiting and go to the logs, including kubectl logs --previous because the current container may only live for 2 seconds. I check the exit code, and exit 137 tells me straight away it was OOMKilled, usually a memory limit set at 128Mi for a JVM that needs 512Mi. Other common causes are a missing config or secret, a failed migration, or a missing dependency. Each status narrows the cause, so I diagnose from describe and logs rather than blindly restarting.

Leads with describe + events Maps each status to specific causes Knows logs --previous and exit codes

Then they probe: CrashLoopBackOff but the current logs are empty. How do you see what happened?

Practise this one
Mid

How does persistent storage work in Kubernetes (PV, PVC, StorageClass)?

What most people say

You create a PersistentVolumeClaim and the pod uses it for storage that survives restarts.

It names the PVC but skips the StorageClass and dynamic provisioning, access modes, and reclaim policy, the things that determine whether the storage actually works for your workload and what happens to your data.

The structure behind a strong answer

  1. 1

    PV and PVC. A PersistentVolume is a piece of storage in the cluster; a PersistentVolumeClaim is a pod request for storage of a given size and access mode. The pod mounts the claim.

  2. 2

    StorageClass and dynamic provisioning. A StorageClass defines a provisioner and parameters, so when a PVC is created the volume is provisioned automatically (for example an EBS volume), instead of an admin pre-creating PVs.

  3. 3

    Access modes. ReadWriteOnce (one node), ReadOnlyMany, ReadWriteMany (many nodes). Most block storage is RWO; shared file storage is needed for RWX, a common gotcha.

  4. 4

    Reclaim policy and StatefulSets. The reclaim policy (Retain vs Delete) decides what happens to the volume when the claim is deleted. StatefulSets use volumeClaimTemplates to give each replica its own stable volume.

What gets you hired

The abstraction separates the request from the implementation. A PersistentVolume is actual storage in the cluster, and a PersistentVolumeClaim is a pod request for a size and access mode, say 20Gi ReadWriteOnce, and the pod mounts the claim and does not care about the underlying disk. In practice you rarely pre-create PVs by hand. A StorageClass defines a provisioner and parameters, for example gp3 EBS on AWS, so when the PVC is created the volume is provisioned dynamically and bound automatically, usually within about 30 seconds. Access modes trip people up. ReadWriteOnce means one node mounts it read-write, and ReadOnlyMany and ReadWriteMany allow many. Almost all cloud block storage is ReadWriteOnce, so if 3 pods on different nodes need to share a volume you need a shared file system like EFS, not a plain block volume. The reclaim policy, Retain or Delete, decides whether the underlying volume and its data survive when the claim is deleted, and for a database I want Retain so that deleting one claim does not destroy the data. And StatefulSets use volumeClaimTemplates, so a 3 replica set gets 3 stable PVCs, one per replica, that follow each pod across restarts and rescheduling. That is exactly what stateful workloads need.

Explains PV/PVC abstraction + dynamic provisioning Knows access modes (RWO vs RWX) gotcha Knows reclaim policy + StatefulSet volumeClaimTemplates

Then they probe: Several pods on different nodes need to share a volume but it will not mount. Why?

Practise this one
Senior

How do you secure a Kubernetes cluster? Cover RBAC, workload hardening, and network controls.

What most people say

I would set up RBAC so only the right people have access to the cluster.

RBAC is one layer and only addresses identity. A secure cluster also needs workload hardening (non-root, no privileged), network policies (default is allow-all), image/supply-chain controls, and secrets encryption, all missing here.

The structure behind a strong answer

  1. 1

    RBAC and identities. Use RBAC with least privilege: Roles/ClusterRoles bound to specific subjects, dedicated ServiceAccounts per workload, and no handing out cluster-admin. Disable default SA token automounting where unneeded.

  2. 2

    Harden workloads. Pod Security (the restricted standard): run as non-root, read-only root filesystem, drop Linux capabilities, no privileged containers, no host namespaces.

  3. 3

    Network segmentation. Default-deny NetworkPolicies, then allow only required flows, since the default is all-pods-can-talk. Consider a service mesh for mTLS between services.

  4. 4

    Supply chain and secrets. Scan and sign images, enforce trusted registries via admission control (OPA/Gatekeeper or Kyverno), enable etcd encryption at rest, and use an external secrets manager.

What gets you hired

I treat it as layers, because no single control is enough. Identity and access: RBAC with least privilege, Roles and ClusterRoles bound to specific users or groups, a dedicated ServiceAccount per workload with only the permissions it needs, and never default cluster-admin; I also turn off automounting the default service account token where a pod does not call the API. Workload hardening: enforce the restricted Pod Security standard, run as non-root, read-only root filesystem, drop Linux capabilities, no privileged containers, no host network or host PID, so a compromised container is boxed in. Network: NetworkPolicies, and because the default is that all pods can talk to each other, I start from default-deny and explicitly allow required flows, and for service-to-service auth I use a mesh with mTLS. Supply chain and secrets: scan images for vulnerabilities and sign them, enforce trusted registries and these policies with an admission controller like Gatekeeper or Kyverno, enable encryption at rest for etcd, and pull real secrets from an external manager rather than relying on base64 Secrets. The theme is assume breach, least privilege at every layer, and limit blast radius if one container is compromised.

Layers identity/workload/network/supply-chain Least privilege RBAC + per-workload SAs Default-deny network + non-root/restricted PSS + admission control

Then they probe: A container is compromised. What limits the blast radius?

Practise this one
Principal

When would you run multiple Kubernetes clusters instead of one big one, and how do you manage a fleet?

What most people say

I would run separate clusters for each environment and team so they are isolated.

A cluster per team/env can be the right call, but stated as a reflex it ignores the heavy operational cost and that namespaces handle much isolation. A principal weighs the trade-off and has a fleet-management answer.

The structure behind a strong answer

  1. 1

    Default to fewer, bigger clusters. Namespaces, RBAC, quotas, and NetworkPolicies handle most isolation needs within one cluster. Multi-cluster multiplies operational cost, so it needs a real reason.

  2. 2

    Real drivers. Blast-radius and upgrade isolation, regional proximity and DR, hard compliance or tenant isolation, and scale limits (very large clusters hit control-plane and etcd limits).

  3. 3

    Manage as a fleet with GitOps. Use GitOps (Argo CD or Flux) to apply consistent config across clusters from git, preventing drift, plus a fleet/management layer for policy and visibility.

  4. 4

    Cross-cluster concerns. Networking and service discovery across clusters (a multi-cluster mesh), centralized observability and identity, and consistent policy enforcement everywhere.

What gets you hired

My default is actually fewer, bigger clusters, because inside one cluster, namespaces, RBAC, resource quotas, and NetworkPolicies already give strong logical isolation, and every extra cluster multiplies upgrades, monitoring, and config surface. With a release every 4 months upstream, running 20 clusters means you are almost always mid upgrade somewhere. So I want a real driver before going multi cluster. The legitimate ones are blast radius and upgrade isolation, so a bad change does not take everything down and I can roll clusters one at a time; regional proximity and disaster recovery, clusters near users and in separate regions; hard isolation for compliance or untrusted tenants where a namespace is not enough; and raw scale, since a single cluster eventually hits control plane and etcd limits, and the official guidance tops out around 5,000 nodes. Once I am running a fleet, the key is consistency. Everything goes through GitOps with Argo CD or Flux, so each cluster's config comes from git and drift is detected and reconciled automatically rather than someone kubectl applying at midnight. I add a management layer for policy and a single view. Then I solve the cross cluster concerns, service discovery and networking through a multi cluster mesh, centralized observability and identity, and policy enforced uniformly with Kyverno or Gatekeeper. So, resist multi cluster until a real driver justifies it, then run it as a GitOps managed fleet so the extra clusters never become snowflakes.

Defaults to fewer clusters + namespace isolation Names real drivers (blast radius, region/DR, compliance, scale) Manages the fleet with GitOps + uniform policy

Then they probe: What is the strongest argument against multi-cluster?

Practise this one

Docker

14 questions · Foundation, Junior, Mid, Senior
Foundation

What is a container, and how is it different from a virtual machine?

What most people say

A container is like a lightweight virtual machine that runs your app.

The "lightweight VM" analogy hides the key fact: a container shares the host kernel and is really an isolated process, not a virtualized machine. That difference drives the isolation and security trade-off.

The structure behind a strong answer

  1. 1

    A container isolates a process. It packages an app and its dependencies and runs it as an isolated process on the host, sharing the host kernel rather than booting its own.

  2. 2

    A VM virtualizes hardware. A VM runs a full guest OS with its own kernel on top of a hypervisor, so it emulates a whole machine.

  3. 3

    Weight and speed. Containers are lightweight and start in milliseconds because there is no OS to boot; VMs are heavier and start in seconds-to-minutes.

  4. 4

    The trade-off. VMs give a stronger isolation boundary (separate kernel), which matters for untrusted or multi-tenant workloads; containers give density and speed.

What gets you hired

A container packages an application with its dependencies and runs it as an isolated process on the host, and the crucial part is that it shares the host kernel instead of booting its own operating system. A virtual machine is different in kind. It runs a full guest OS with its own kernel on a hypervisor, so it is emulating a whole machine. That difference explains the numbers. A container starts in well under a second, often around 100 milliseconds, because there is no OS to boot, and an image might be 50 to 200 megabytes. A VM takes 30 seconds or more to boot and carries a multi-gigabyte disk image and its own kernel and services in memory. So on one host I might comfortably pack 50 containers where I would fit a handful of VMs. The trade-off is isolation. Because each VM has its own kernel, the boundary is stronger, and that matters for untrusted code or strict multi-tenancy, where a single shared kernel is 1 exploit away from being the whole blast radius. Containers trade some of that boundary for density and startup speed. In practice they compose rather than compete. Containers very often run inside VMs, which is exactly what a managed Kubernetes node is, hardware-level isolation from the VM plus container packaging and speed on top.

Knows containers share the host kernel Knows VMs have their own kernel/hypervisor Articulates the isolation vs density trade-off

Then they probe: If containers are lighter, why do cloud providers still run them inside VMs?

Practise this one
Foundation

What is the difference between a Docker image and a container?

What most people say

An image is the file and a container is when it runs.

Roughly right but imprecise. It misses that images are immutable and layered, that many containers come from one image, and that the container writable layer is ephemeral, which matters for data.

The structure behind a strong answer

  1. 1

    Image is the template. An image is an immutable, read-only package built from a Dockerfile: the app, dependencies, and filesystem, stored as stacked layers.

  2. 2

    Container is a running instance. A container is a running (or stopped) instance of an image, with a thin writable layer added on top of the read-only image layers.

  3. 3

    One to many. You run many containers from one image, like many objects from a class. They share the image layers and differ only in their writable layer and runtime state.

  4. 4

    Ephemeral writable layer. Changes a container makes live in its writable layer and are lost when it is removed, which is why persistent data needs a volume.

What gets you hired

An image is an immutable, read-only template, built from a Dockerfile, holding the application, its dependencies, and a filesystem, stored as a stack of layers. A container is a running, or stopped, instance of that image. Docker adds one thin writable layer on top of those read-only layers and runs the process. The relationship is one-to-many, like a class and its objects. If I start 10 containers from the same 200 megabyte image, that image is on disk once, and all 10 share those layers, differing only in their own writable layer and runtime state. That is a big part of why containers are cheap to run, 10 containers cost me roughly 200 megabytes of image, not 2 gigabytes. The consequence people get bitten by is that the writable layer is ephemeral. Anything the container writes to its own filesystem, a database file, an uploaded image, is gone the moment the container is removed, and containers get removed all the time. So anything that has to survive belongs in a volume or in external storage, never in the container filesystem. The clean mental model is that the image is what you ship and version, and the container is a disposable process you can throw away and recreate in seconds.

Image=immutable template, container=running instance Knows the writable-layer/ephemeral point Mentions layers / many-from-one

Then they probe: You made changes inside a running container and removed it; the changes are gone. Why?

Practise this one
Foundation

What is a Dockerfile, and what do the main instructions do?

What most people say

A Dockerfile is a file with commands that builds a Docker image.

True but contentless. It cannot name FROM, RUN, COPY, CMD or explain that instructions create layers, which is what the question is actually asking.

The structure behind a strong answer

  1. 1

    What it is. A Dockerfile is a text recipe of instructions Docker runs in order to build an image reproducibly.

  2. 2

    Base and build steps. FROM sets the base image; RUN executes a build command (install packages, compile); COPY brings files from the build context into the image.

  3. 3

    Config and metadata. WORKDIR sets the working directory, ENV sets environment variables, EXPOSE documents the port (it does not publish it).

  4. 4

    The default process. CMD or ENTRYPOINT define what runs when a container starts. Each RUN/COPY/ADD typically creates a layer.

What gets you hired

A Dockerfile is a text recipe of instructions that Docker runs top to bottom to build an image reproducibly. It starts with FROM, which sets the base image, and I would pick a slim one, since a node:20-slim base is maybe 80 megabytes against roughly 1 gigabyte for the full image. RUN executes commands at build time, installing packages or compiling, and every RUN adds a layer. COPY brings files from the build context into the image. ADD does the same but also handles URLs and tar extraction, which is exactly why I default to COPY unless I need those. WORKDIR sets the working directory, ENV sets environment variables, and EXPOSE documents which port the app listens on, say 8080, but it publishes nothing, that only happens at run time with a flag like -p 8080:8080. Then CMD or ENTRYPOINT define the process that runs when the container starts. The thing I always keep in mind is that most instructions create a cached layer, so order drives build speed and image size. If I copy my package manifest and install dependencies before copying the rest of the source, a code change reuses the dependency layer and the rebuild drops from 2 minutes to about 10 seconds.

Names FROM/RUN/COPY/CMD with correct roles Knows EXPOSE is documentation only Knows instructions create layers

Then they probe: What is the difference between COPY and ADD?

Practise this one
Junior

How does the Docker build cache work, and how do you order a Dockerfile to use it well?

What most people say

Docker caches layers, so builds are faster the second time.

It knows caching exists but not how to exploit it. The actual skill, copying dependency manifests before source so code edits do not rebuild dependencies, is the difference between a 5-second and a 5-minute rebuild.

The structure behind a strong answer

  1. 1

    Each instruction is a cached layer. Docker caches the result of each instruction. On rebuild, if an instruction and its inputs are unchanged, it reuses the cached layer instead of re-running.

  2. 2

    A change busts everything after it. Once one layer changes, every layer after it is rebuilt, because each depends on the previous. So the order of instructions is a performance decision.

  3. 3

    Dependencies before source. Copy the dependency manifest (package.json, requirements.txt, go.mod) and install dependencies BEFORE copying the application source, so a code change does not invalidate the expensive dependency-install layer.

  4. 4

    Trim the context. Use a .dockerignore to keep junk (node_modules, .git, build artifacts) out of the build context, which keeps COPY layers stable and the context small.

What gets you hired

Docker caches the result of each instruction as a layer, and on a rebuild it reuses a layer if that instruction and its inputs have not changed. The key behavior is that as soon as one layer changes, every layer after it is invalidated and rebuilt, because layers stack on each other. So instruction order is a real performance lever. The classic optimization is to copy the dependency manifest and install dependencies before copying the application source. Dependencies change rarely, source changes constantly. On a Node service I worked on, copying package.json and package-lock.json and running npm ci first, then copying the rest of the code, meant a normal code edit only rebuilt from the source-copy layer onward and reused the cached dependency layer, which was the slow part. That took our image build from about 4 minutes down to under 30 seconds on a code-only change. If instead I copy everything and then install, all 900-odd packages get reinstalled on every single commit. I also add a .dockerignore to keep node_modules, .git, and local artifacts out of the build context, which keeps COPY layers from being invalidated unnecessarily and shrinks the context sent to the daemon. On that same project it cut the context from roughly 300 MB to about 2 MB, so even the upload before the build got faster.

Knows a change busts all later layers Copies deps before source deliberately Uses .dockerignore

Then they probe: Why copy package.json and install before copying the rest of the source?

Practise this one
Junior

What is the difference between CMD and ENTRYPOINT, and what is the exec versus shell form?

What most people say

They both set the command that runs in the container, so you can use either.

"Use either" is wrong: they behave differently with run-time args, and exec vs shell form affects whether the app gets shutdown signals. Treating them as interchangeable leads to ungraceful shutdowns and surprising overrides.

The structure behind a strong answer

  1. 1

    ENTRYPOINT is the executable. ENTRYPOINT defines the command that always runs. It is not overridden by run-time arguments (only by an explicit flag).

  2. 2

    CMD is the default args. CMD provides default arguments (or a default command). Anything you pass to docker run replaces CMD.

  3. 3

    The common pattern. Set ENTRYPOINT to the binary and CMD to default flags, so the container always runs your program but its arguments are easily overridden.

  4. 4

    Exec vs shell form. Exec form (a JSON array) runs the binary directly as PID 1, so it receives signals like SIGTERM. Shell form runs via /bin/sh -c, which can swallow signals, so exec form is preferred.

What gets you hired

They serve different roles. ENTRYPOINT defines the executable that always runs and is not replaced by the arguments you pass to docker run. CMD provides the default arguments, or a default command, and is replaced by anything you pass on the run command line. The common pattern combines them: ENTRYPOINT is the program, CMD is its default flags, so the container always runs my binary but a user can override the arguments easily, and if I use only CMD, the whole command is overridable. Just as important is exec versus shell form. Exec form, the JSON-array syntax, runs the binary directly as PID 1, so it receives signals like SIGTERM and can shut down gracefully. Shell form runs the command through /bin/sh -c, which becomes PID 1 and can swallow the signal, so your app may not get SIGTERM and gets hard-killed after the timeout. So I default to exec form and use ENTRYPOINT plus CMD together for clean, overridable, signal-aware containers.

ENTRYPOINT=fixed exe, CMD=overridable args Knows the combined pattern Knows exec form for signal handling

Then they probe: Why does shell form (CMD app) cause problems with graceful shutdown?

Practise this one
Junior

How does container networking work in Docker, and how do containers talk to each other?

What most people say

Containers get an IP address and you map ports with -p to access them.

It covers port publishing but misses container-to-container discovery: on the default bridge they cannot resolve each other by name, which is exactly why multi-container setups fail until you use a user-defined network or Compose.

The structure behind a strong answer

  1. 1

    Default bridge. By default a container joins a bridge network with its own internal IP. It can reach the internet outbound but is not reachable from the host without publishing a port.

  2. 2

    Publishing ports. docker run -p hostPort:containerPort maps a host port to the container, which is how you reach the app from outside. EXPOSE alone does not do this.

  3. 3

    User-defined networks for discovery. On a user-defined bridge network, containers can resolve each other by container name via Docker built-in DNS, which the default bridge does not provide.

  4. 4

    Other modes. host networking shares the host network stack (no isolation, no port mapping), none disables networking, and overlay networks span multiple hosts.

What gets you hired

By default a container attaches to a bridge network and gets its own internal IP, usually somewhere in 172.17.0.0/16. It can make outbound connections, but it is not reachable from the host until I publish a port with docker run -p 8080:3000, which maps host port 8080 to container port 3000. EXPOSE by itself only documents the port, it does not publish it. The part people miss is container-to-container communication. On the default bridge, containers can reach each other by IP but not by name. On a user-defined bridge network, Docker runs an embedded DNS server at 127.0.0.11 inside each container, so they can resolve each other by container name. That is why the right way to wire up a multi-container app is to put them on a shared user-defined network, or just use Docker Compose, which creates one automatically. There are also other modes. Host networking means the container shares the host network stack directly with no isolation and no port mapping, so a service listening on 3000 is just on the host's 3000. None disables networking entirely. Overlay networks connect containers across multiple hosts in a swarm. In my experience 9 times out of 10 a local connectivity problem is either a missing -p or relying on the default bridge instead of a user-defined network for name resolution.

Knows default bridge + outbound-only until published Knows -p publishes, EXPOSE documents Knows user-defined networks give name DNS

Then they probe: Two containers cannot reach each other by name. What is the fix?

Practise this one
Junior

How do you persist data in Docker? Explain volumes versus bind mounts.

What most people say

You use a volume to save data so it is not lost when the container stops.

It knows volumes exist but does not distinguish named volumes from bind mounts or say when to use each, which is the actual question and matters for portability vs dev convenience.

The structure behind a strong answer

  1. 1

    The container FS is ephemeral. Anything written to the container writable layer is lost when the container is removed, so persistent data needs external storage.

  2. 2

    Named volumes. Docker-managed storage that lives outside the container lifecycle, portable and the recommended way to persist data like a database.

  3. 3

    Bind mounts. Mount a specific host directory into the container. Great for local development (live-editing source), but tied to the host path and layout.

  4. 4

    tmpfs and choosing. tmpfs keeps data in memory only. Rule of thumb: named volumes for app/production data, bind mounts for dev workflows where you want host files reflected live.

What gets you hired

The starting point is that the container filesystem is ephemeral. Whatever is written to the writable layer disappears when the container is removed, so any data that must survive needs external storage. I learned that the hard way once when a docker compose down wiped about 3 weeks of local Postgres data. There are two main options. A named volume is storage managed by Docker, living under /var/lib/docker/volumes on the host, outside the container lifecycle. It is portable, not tied to a host path, and is the recommended way to persist real application data like a database, because I can stop, remove, and recreate the container 100 times without losing it. A bind mount maps a specific directory on the host into the container. It is perfect for local development, since I can edit source on the host and see it live inside the container in under a second with no rebuild, but it is coupled to the host path and filesystem layout, so it is less portable. There is also tmpfs, which keeps data in memory only and never touches disk, useful for sensitive scratch data. My rule of thumb is named volumes for persistent application and production data, and bind mounts for development workflows where I want my local files live in the container.

Knows container FS is ephemeral Distinguishes named volume vs bind mount Maps each to the right use case

Then they probe: Why prefer a named volume over a bind mount for a database in production?

Practise this one
Mid

What is a multi-stage build, and why does it matter?

What most people say

It is a Dockerfile with multiple stages to organize the build.

It names the mechanism but not the benefit. The point is shipping only the artifact in a slim final image, dropping build tools, size, and attack surface, which is what makes it worth doing.

The structure behind a strong answer

  1. 1

    Multiple FROM stages. A multi-stage Dockerfile has more than one FROM. An early stage does the build (compilers, dev dependencies, tests); a later, slim stage is the final image.

  2. 2

    Copy only the artifact. The final stage copies just the built artifact (a binary, compiled assets) from the build stage and leaves all the build tooling behind.

  3. 3

    Why it matters. The runtime image is far smaller and has a much smaller attack surface, because compilers, package managers, and source never ship to production.

  4. 4

    Knock-on benefits. Smaller images pull and start faster, cost less to store, and have fewer CVEs to patch. You can also target intermediate stages for CI (build, test).

What gets you hired

A multi-stage build uses more than one FROM in a single Dockerfile. An early stage is the builder, it has the compilers, dev dependencies, and full source, and it produces the artifact, a compiled binary or bundled assets. Then the final stage starts from a slim or distroless base and copies only that artifact out of the builder, leaving everything else behind. The final image is where it pays off. Without multi-stage, a Node build image can easily sit at 1.1GB carrying the whole toolchain, package manager, and source, all dead weight and attack surface. With multi-stage I have taken the same app down to roughly 80MB on distroless, because the runtime image holds just the app and its runtime, with no compiler and no shell for an intruder to pivot from. The knock-on benefits are real. A 12 times smaller image pulls and starts faster, so a scale-up that took 40 seconds to pull now takes a few, it costs less to store across every tag you keep, and it has far fewer packages, so a scan that used to report 200 plus CVEs drops to a handful you can actually patch. I also name the stages so CI can target the builder stage to run tests before the final image is assembled.

Builder stage + slim final stage Copies only the artifact Cites size + attack-surface + CVE benefits

Then they probe: What specifically does multi-stage keep out of the production image?

Practise this one
Mid

Your Docker image is 1.5GB. How do you make it smaller?

What most people say

I would use a smaller base image like alpine.

A smaller base helps but is one lever. A 1.5GB image usually also carries build tools (needs multi-stage), uncleaned caches in layers, and copied junk (needs .dockerignore). One trick rarely gets you there.

The structure behind a strong answer

  1. 1

    Start from a smaller base. Swap a full OS base for a slim, alpine, or distroless image. The base is often the biggest single contributor.

  2. 2

    Multi-stage to drop build tooling. Build in one stage and copy only the artifact into a slim final stage, so compilers and dev dependencies never ship.

  3. 3

    Layer hygiene. Combine related RUN steps and clean up in the same layer (remove package caches, temp files), because deleting in a later layer does not shrink the earlier one.

  4. 4

    Keep junk out. Use a .dockerignore so node_modules, .git, tests, and local artifacts are not copied in, and only install production dependencies.

What gets you hired

I attack it on several fronts because bloat usually has several causes. First the base image, which is often the biggest single piece: I move from a full OS base to a slim, alpine, or distroless image. Second, multi-stage build, so compilers, dev dependencies, and source stay in the builder stage and only the final artifact lands in a slim runtime image, that alone often cuts the most. Third, layer hygiene: I combine related RUN commands and clean up within the same layer, removing package-manager caches and temp files in the same RUN that created them, because deleting files in a later layer does not reclaim the space already baked into an earlier one. Fourth, keeping junk out: a .dockerignore so .git, node_modules, tests, and local build output never enter the context or image, and installing only production dependencies, not dev ones. I would also inspect the layers with docker history or a tool like dive to see exactly which layer is fat and target it. Usually the combination of a slim base plus multi-stage takes a 1.5GB image down to a fraction.

Multiple levers: base, multi-stage, layer cleanup, .dockerignore Knows cleanup must be in the same layer Inspects layers (history/dive)

Then they probe: Why does running rm in a later RUN not shrink the image?

Practise this one
Mid

What is Docker Compose for, and where does it stop being the right tool?

What most people say

Docker Compose runs multiple containers together, so you use it to run your app in production.

The first half is right, but "use it in production" is the mistake. Compose is single-host with no cross-node scheduling, healing, or rolling updates; production at scale needs Kubernetes or a managed service.

The structure behind a strong answer

  1. 1

    Declarative multi-container. Compose defines a multi-container app in one YAML file: services, their images/builds, networks, volumes, and dependencies, brought up with one command.

  2. 2

    Great for local dev/test. It is ideal for spinning up an app plus its database, cache, and queue locally, with a shared network so they find each other by service name.

  3. 3

    Where it stops. It is single-host by default and has no real scheduling, self-healing across nodes, rolling updates, or autoscaling, so it is not a production cluster orchestrator.

  4. 4

    The step up. For production at scale you move to Kubernetes (or a managed container service), which provides the multi-node scheduling, healing, and rollout features Compose lacks.

What gets you hired

Docker Compose lets me define a whole multi-container application declaratively in one YAML file, the services and their images or builds, the networks, volumes, and the dependencies between them, then bring it all up or down with a single command. It is excellent for local development and testing. On my last project one compose file stood up 5 services, the app, Postgres, Redis, a worker, and RabbitMQ, on a shared network so they resolve each other by service name, and a new joiner went from a fresh laptop to a running stack in about 10 minutes instead of a day of setup. Where it stops is production at scale. Compose is single-host by default, so everything lives or dies with that 1 machine, and it has no real scheduling across nodes, no cross-node self-healing, no rolling updates or rollbacks, and no autoscaling. For production I move to Kubernetes or a managed container service, which give me exactly those things, placing containers across nodes, restarting and rescheduling on failure, doing controlled rollouts, and scaling on demand. The clean way to put it is that Compose describes and runs a stack on one machine, mostly for development, and Kubernetes runs it resiliently across many machines in production.

Knows Compose = declarative multi-container stack Knows it shines for local dev Knows the single-host/no-orchestration boundary

Then they probe: Why is Compose not a substitute for Kubernetes in production?

Practise this one
Mid

A container exits immediately on start. How do you debug it?

What most people say

I would restart the container and see if it works.

A container that exits deterministically will just exit again. The cause is in the logs, the exit code, or the start command; restarting skips diagnosis entirely.

The structure behind a strong answer

  1. 1

    Understand why containers exit. A container runs exactly as long as its main process. If that process finishes or crashes, the container exits, there is no daemon keeping it alive.

  2. 2

    Read the logs and exit code. docker logs <container> shows stdout/stderr, and docker ps -a plus docker inspect show the exit code, which often points straight at the cause (for example 137 = OOM/SIGKILL).

  3. 3

    Get a shell. If it dies too fast, override the entrypoint to run a shell (docker run -it --entrypoint sh image) and poke around, or docker exec into it if it stays up briefly.

  4. 4

    Check the usual causes. A command that runs and exits rather than staying foreground, a missing config or env var, a bad path, a crash on startup, or the process being backgrounded so PID 1 exits.

What gets you hired

First I keep in mind that a container lives only as long as its main process: when that process exits or crashes, the container stops, there is nothing else holding it open. So I look at what that process did. docker logs shows its stdout and stderr, which usually contains the error or stack trace, and docker ps -a with docker inspect gives me the exit code, which is often diagnostic on its own, for example 137 means it was SIGKILLed, typically an out-of-memory kill. If it dies too fast to inspect, I override the entrypoint to drop into a shell, docker run -it --entrypoint sh on the image, and run things by hand to see what fails, or exec in if it stays up for a moment. Then I check the usual suspects: a command that simply runs and finishes instead of staying in the foreground, a missing environment variable or config file, a wrong path or permission, a startup crash, or the main process being backgrounded so PID 1 exits and takes the container with it. The principle is to diagnose from logs, exit code, and an interactive shell rather than blindly restarting.

Knows container life = main process life Uses logs + exit code + inspect Overrides entrypoint to get a shell

Then they probe: A container exits with code 137. What does that tell you?

Practise this one
Mid

How do containers actually work under the hood?

What most people say

Docker uses its own technology to isolate and run containers efficiently.

It treats containers as a black box. The expected answer names the actual Linux primitives, namespaces, cgroups, and overlay filesystem, which is what shows real understanding versus surface familiarity.

The structure behind a strong answer

  1. 1

    Namespaces give isolation. Linux namespaces isolate what a process can see: PID, network, mount, UTS (hostname), IPC, and user namespaces. Each container gets its own view of these.

  2. 2

    cgroups give resource limits. Control groups limit and account for what a process can use, CPU, memory, I/O, so one container cannot starve the host or its neighbors.

  3. 3

    Union/overlay filesystem. Images are stacked read-only layers; a container adds a thin writable layer on top via an overlay filesystem, which is how layers and copy-on-write work.

  4. 4

    No separate kernel. Put together, a container is just a normal Linux process with isolation and limits applied. It shares the host kernel, which is why it is light and why a Linux container needs a Linux kernel.

What gets you hired

A container is not magic and not a VM, it is a normal Linux process with kernel features applied to it. Three primitives do the work. Namespaces give isolation, and there are 6 or 7 of them in common use, a PID namespace so the process sees its own process tree with itself as PID 1, a network namespace for its own interfaces and IP, plus mount, UTS for hostname, IPC, and user namespaces. cgroups, control groups, give resource control, they limit and account for CPU, memory, and I/O so one container cannot exhaust the host or starve its neighbours, and that is what enforces a 512Mi memory limit and triggers the OOM kill you see as exit code 137. The third piece is the union or overlay filesystem, image layers are stacked read-only and the container gets a thin writable layer on top with copy-on-write, which is how layering and the ephemeral writable layer actually work. Put it together and a container is just a process the kernel has isolated with namespaces and constrained with cgroups on a layered filesystem. Because it shares the host kernel instead of booting its own, it starts in tens of milliseconds where a VM takes 30 seconds or more, and that same fact means a Linux container fundamentally needs a Linux kernel underneath.

Names namespaces (isolation) and cgroups (limits) Knows union/overlay filesystem for layers Knows it shares the host kernel, not a VM

Then they probe: Which primitive enforces a container memory limit, and what happens at the limit?

Practise this one
Senior

How do you harden a Docker image and container at build and run time?

What most people say

I would scan the image for vulnerabilities before deploying it.

Scanning is one part, but it ignores the biggest Dockerfile mistakes: running as root by default and baking secrets into layers, plus runtime hardening. A senior addresses build and run, not just scanning.

The structure behind a strong answer

  1. 1

    Do not run as root. Containers run as root by default. Create and switch to a non-root USER in the Dockerfile, so a container escape does not start with root.

  2. 2

    Keep secrets out of layers. Never put secrets in ARG, ENV, or a COPYd file, they persist in the image history even if later removed. Use BuildKit build secrets (--mount=type=secret) at build time and inject runtime secrets via the platform.

  3. 3

    Minimal base and pinning. Use a slim or distroless base to cut attack surface, pin the base by digest for reproducibility, and scan images for CVEs in CI.

  4. 4

    Runtime hardening. Run with a read-only root filesystem, drop Linux capabilities (--cap-drop), prevent privilege escalation (no-new-privileges), and never use --privileged unless truly required.

What gets you hired

I harden at both build time and run time. At build time, first, do not run as root, which is the default, meaning UID 0 inside the container. I create a dedicated non-root user, say UID 10001, and switch to it with USER, so a container compromise does not immediately hand over root. Second, keep secrets out of the image entirely. A secret put in an ARG, an ENV, or a copied file stays in the image history even if a later layer deletes it, and anyone who pulls the image can recover it with 1 docker history command, so I use BuildKit build secrets with --mount=type=secret for build-time credentials and inject runtime secrets through the platform, never baked in. Third, a minimal base, slim or distroless, which can take an image from around 1 GB down to under 100 MB and cuts the attack surface with it, pinned by digest for reproducibility, plus image scanning in CI to catch known CVEs. At run time I add the OS-level controls: a read-only root filesystem so the container cannot be modified, dropping Linux capabilities with --cap-drop ALL and adding back only the 1 or 2 that are genuinely needed, setting no-new-privileges to block privilege escalation, and never using --privileged unless there is a hard requirement. The mindset is least privilege end to end, and assume the container could be compromised, so limit what that buys an attacker.

Knows default is root, sets non-root USER Knows secrets persist in layer history; uses BuildKit/runtime injection Runtime hardening: read-only FS, cap-drop, no-new-privileges

Then they probe: Why is putting a secret in an ENV or ARG dangerous even if you delete it later?

Practise this one
Senior

How should you tag and promote images across environments? What is wrong with using :latest?

What most people say

You tag images with version numbers, and :latest just points to the newest one.

It describes :latest but not the danger: it is mutable and not reproducible, so two pulls can differ. It also misses promoting one immutable artifact across environments, which is the core practice.

The structure behind a strong answer

  1. 1

    Tags are mutable pointers. A tag like :latest or :v1 is just a movable label; it can be repointed to a different image at any time, so it is not a guarantee of what you are running.

  2. 2

    Digests are immutable. An image digest (sha256:...) identifies exact content and never changes. Pinning by digest guarantees you run precisely the image you tested.

  3. 3

    Promote, do not rebuild. Build the image once, then promote that same artifact (same digest) through dev, staging, and prod. Rebuilding per environment risks subtly different images.

  4. 4

    Tagging scheme. Use immutable, meaningful tags (a version or git SHA) for traceability, keep :latest only as a convenience pointer, and sign images so you can verify provenance.

What gets you hired

The key distinction is mutable tags versus immutable digests. A tag, including :latest, is just a movable pointer: it can be repointed to a different image whenever someone pushes, so pulling the same tag at two times can give you two different images, which is exactly how you get works-in-staging-broken-in-prod or non-reproducible deploys. An image digest, the sha256 content hash, is immutable and identifies the exact bytes, so pinning by digest guarantees you run precisely what you tested. The practice that follows is build once, promote the same artifact: I build the image a single time, tag it with something immutable and meaningful like a git SHA or version, and then promote that exact image, ideally by digest, through dev, staging, and prod, rather than rebuilding in each environment, because a rebuild can pull different base layers or dependencies and produce a subtly different image. :latest is fine as a human convenience pointer but should never be what production pins to. I also sign images so each environment can verify provenance before running. So: immutable tags or digests for what actually runs, promote one artifact across environments, and treat :latest as a label, not a contract.

Mutable tags vs immutable digests Build-once, promote-same-artifact Pins prod by digest/immutable tag; mentions signing

Then they probe: Why can deploying :latest give two servers different images?

Practise this one

Helm

14 questions · Foundation, Junior, Mid, Senior
Foundation

What is Helm, and what problem does it solve for Kubernetes?

What most people say

Helm is a tool that installs applications on Kubernetes using charts.

True but shallow. It misses the three things that make Helm valuable: templating for multi-environment reuse, versioned release lifecycle with rollback, and chart sharing. "Installs apps" alone undersells it.

The structure behind a strong answer

  1. 1

    Package manager for Kubernetes. Helm bundles the many manifests an app needs (Deployment, Service, ConfigMap, Ingress) into one versioned package called a chart.

  2. 2

    Templating for reuse. Charts are templated, so one chart serves many environments by swapping values instead of duplicating YAML per environment.

  3. 3

    Release lifecycle. Helm installs a chart as a named release with revision history, so you can upgrade and roll back as versioned units.

  4. 4

    Sharing. Charts can be published to repositories or OCI registries, so common apps are installed in one command instead of hand-written manifests.

What gets you hired

Helm is the package manager for Kubernetes, and it does 3 jobs. First, packaging. A real app is not 1 manifest, it is a Deployment, a Service, a ConfigMap, an Ingress, an HPA, a ServiceAccount, easily 8 or 10 YAML files, and Helm bundles them into a single versioned package called a chart, so I install the whole app as one unit. Second, templating. The chart is parameterized with values, so instead of copying the whole folder and hand-editing it per environment, I keep 1 chart and supply different values files, 2 replicas in dev and 6 in prod, different hostnames, different resource limits. Third, the release lifecycle. Helm installs a chart as a named release and keeps revision history, so an upgrade becomes revision 5 and a bad deploy is one helm rollback away, back on the previous revision in well under a minute. Plain kubectl apply gives me none of that history. And because charts are published to repositories or OCI registries, standing up something like an ingress controller or a Postgres becomes 1 command instead of authoring dozens of manifests by hand. So it is packaging, templating, and lifecycle together, which is a lot more than just applying YAML.

Names packaging + templating + release lifecycle Knows rollback/revisions Mentions chart sharing/repos

Then they probe: What does Helm give you over plain kubectl apply?

Practise this one
Foundation

Explain the core Helm concepts: chart, release, and repository.

What most people say

A chart is the app, a release is when it runs, and a repository is where charts live.

Roughly right but loose. It misses that a release is named and versioned (you can have many releases from one chart) and that revisions are what enable rollback, which is the practically important detail.

The structure behind a strong answer

  1. 1

    Chart. The package itself: templates, default values, and metadata. It is the blueprint, not something running yet.

  2. 2

    Release. A specific installation of a chart into a cluster, with a name and a revision history. Installing the same chart twice gives two independent releases.

  3. 3

    Repository. Where charts are stored and shared, a classic Helm repo or, increasingly, an OCI registry. You add a repo and pull charts from it.

  4. 4

    How they relate. You pull a chart from a repository and install it as a release; upgrading the release creates a new revision of that same release.

What gets you hired

A chart is the package. It is the templates, the default values.yaml, and the metadata in Chart.yaml, and on its own it is a blueprint that is running nothing. A release is one specific installation of a chart into a cluster, identified by a name, and it carries a revision history. That distinction is the one people blur, and it matters. From 1 chart I can create 3 releases, say a Postgres chart installed as billing-db, auth-db, and reporting-db, each with its own values and its own lifecycle, completely independent of each other. Every upgrade creates a new revision of that release, so after 4 upgrades I am on revision 5, and that history is exactly what lets me run helm rollback and be back on revision 4 in seconds. A repository is where charts are stored and distributed. That is the traditional Helm chart repo, an index over packaged charts, or increasingly an OCI registry, the same kind of registry that holds container images. So the flow is straightforward. I add a repository, pull version 1.2.3 of a chart from it, install that chart as a named release, and from then on I manage that release through its revisions rather than touching the chart itself.

Chart=package, release=named install, repo=distribution Knows multiple releases from one chart Knows revisions enable rollback

Then they probe: Can you install the same chart more than once in a cluster?

Practise this one
Foundation

How do values and templating work in a Helm chart?

What most people say

You put your configuration in values.yaml and Helm uses it to deploy.

It knows values.yaml exists but not how templates consume values or how overrides and precedence work, which is what actually lets one chart serve dev/staging/prod.

The structure behind a strong answer

  1. 1

    values.yaml is the defaults. The chart ships a values.yaml with default configuration. Templates reference these as {{ .Values.something }}.

  2. 2

    Templates render manifests. Files in templates/ are Go templates; Helm fills in the values and produces final Kubernetes manifests at install/upgrade time.

  3. 3

    Overriding values. Override defaults with a custom values file (-f my-values.yaml) or inline (--set key=value), without editing the chart.

  4. 4

    Precedence. Later sources win: chart defaults, then each -f file in order, then --set on top. This layering is how one chart serves many environments.

What gets you hired

A chart ships a values.yaml that holds the default configuration, things like image tag, replica count, and resource limits. On our charts that default block was small, maybe 30 lines, but it drove everything. The files in the templates directory are Go templates that reference those values as {{ .Values.something }}, and at install or upgrade time Helm renders the templates against the values to produce the final Kubernetes manifests it applies. The power is in overriding without touching the chart. I can pass a custom values file with -f my-values.yaml, or set individual keys inline with --set replicaCount=6, which I mostly reserve for one-off or CI cases like pinning an image tag from the build. Precedence is layered and predictable, and later sources override earlier ones: the chart defaults first, then each -f file in the order given, then --set on top. So if values.yaml says 2 replicas, values-prod.yaml says 6, and I pass --set replicaCount=10, I get 10. That layering is exactly how one chart served all 3 of our environments, a base values.yaml plus a values-prod.yaml override, rather than maintaining separate copies of the manifests. When I am unsure what will actually land, I run helm template or helm upgrade with --dry-run and read the rendered output before it touches the cluster.

values.yaml = defaults, templates reference .Values Knows -f and --set overrides Knows precedence/layering for environments

Then they probe: If a value is set in both a -f file and --set, which wins?

Practise this one
Junior

Walk me through the Helm release lifecycle: install, upgrade, rollback, uninstall.

What most people say

You install with helm install and update with helm upgrade.

It names two commands but misses revisions and rollback, which are the whole point of releases, and shows no awareness of where Helm tracks release state.

The structure behind a strong answer

  1. 1

    Install. helm install creates a named release at revision 1, rendering the chart with the supplied values and applying the manifests.

  2. 2

    Upgrade. helm upgrade applies changes (new chart version or new values) and creates a new revision, keeping the previous ones in history.

  3. 3

    Rollback. helm rollback reverts the release to a prior revision, which is why Helm deploys are easy to undo. helm history shows the revisions.

  4. 4

    Uninstall and state. helm uninstall removes the release. Helm 3 stores each release state as a Secret in the release namespace (no Tiller), which is the source of truth for revisions.

What gets you hired

A release moves through versioned revisions. helm install creates a named release at revision 1: Helm renders the chart with my values and applies the resulting manifests. helm upgrade applies a change, a new chart version, new values, or both, and creates a new revision while keeping the prior revisions in history, which I can see with helm history. Because of that history, helm rollback is a first-class operation: I can revert the release to a previous revision in one command, which makes a bad deploy quick to undo. helm uninstall removes the release and its resources. An important Helm 3 detail is where this state lives: Helm 3 stores each release, the rendered manifests and metadata for every revision, as a Secret in the release namespace. There is no Tiller anymore, which was the privileged in-cluster component in Helm 2 that was removed for security. So the release Secret is the source of truth that makes upgrade and rollback work.

Knows install/upgrade create revisions Knows rollback + history Knows Helm 3 stores state in Secrets (no Tiller)

Then they probe: Where does Helm 3 store release state, and why does that matter?

Practise this one
Junior

How does Helm templating work? Mention the template objects and helpers.

What most people say

Helm uses templates with placeholders that get replaced by your values.

It treats templating as simple substitution. Real charts use conditionals, loops, named helpers, and objects like .Release, and missing that means you cannot author or debug a non-trivial chart.

The structure behind a strong answer

  1. 1

    Go templates plus Sprig. Templates use Go template syntax with the Sprig function library, so you have conditionals, loops (range), and functions for strings, defaults, and more.

  2. 2

    Built-in objects. .Values is your config, .Release gives release name and namespace, .Chart gives chart metadata, and .Capabilities exposes cluster/API info.

  3. 3

    Named templates and helpers. _helpers.tpl holds reusable named templates (define / include), commonly for label blocks and naming, so you do not repeat them across files.

  4. 4

    Whitespace and safety. Whitespace control ({{- -}}) keeps rendered YAML valid, and functions like default and required make templates robust. helm template renders locally to verify output.

What gets you hired

Helm templates are Go templates extended with the Sprig function library, which gives me roughly 200 functions on top of simple substitution: conditionals, loops with range, and helpers for strings, defaults, and encoding. Inside a template I have several built-in objects. .Values is the merged configuration. .Release gives me the release name and namespace, which is great for naming resources uniquely per release. .Chart exposes the metadata like name and version. .Capabilities tells me about the cluster and available API versions, so a chart can adapt, for example picking the right autoscaling API on a 1.23 cluster versus a newer one. For reuse, _helpers.tpl holds named templates defined with define and pulled in with include, which is how a chart avoids repeating the same 6 line label block across every manifest. Two practical details. Whitespace control with the dash syntax, {{- and -}}, keeps the rendered YAML valid, because a single stray space of indentation breaks the parse. And helper functions like default and required make templates robust, where required fails the render with a clear message if a needed value is missing. Before anything reaches a cluster I run helm template or helm install --dry-run and read the rendered output, which catches most mistakes in about 10 seconds.

Go templates + Sprig Knows .Values/.Release/.Chart objects Knows _helpers.tpl named templates + whitespace control

Then they probe: What does the _helpers.tpl file do?

Practise this one
Junior

What is the structure of a Helm chart? What are the key files and directories?

What most people say

A chart has a Chart.yaml, a values.yaml, and a templates folder.

It names the basics but misses the distinction between version and appVersion, the charts/ subchart directory, _helpers.tpl, and values.schema.json, details that show real authoring experience.

The structure behind a strong answer

  1. 1

    Chart.yaml. The metadata: chart name, version, appVersion, and the dependencies list. version is the chart version; appVersion is the app it deploys.

  2. 2

    values.yaml. The default configuration that templates consume and users override.

  3. 3

    templates/. The Go-templated manifests, plus _helpers.tpl for named templates and NOTES.txt for the post-install message shown to the user.

  4. 4

    charts/ and extras. charts/ holds dependency subcharts; values.schema.json validates values; .helmignore excludes files from packaging.

What gets you hired

A chart is a directory with a defined layout, and there are really 4 things at the top level. Chart.yaml holds the metadata: the chart name, the chart version, the appVersion, and the dependencies. The distinction between version and appVersion matters. version is the version of the chart itself, so a pure templating fix might take it from 1.4.2 to 1.4.3, while appVersion is the version of the application it deploys, and the 2 move independently. values.yaml is the default configuration that the templates consume and that users override. The templates directory contains the Go-templated Kubernetes manifests, plus a couple of special files: _helpers.tpl for reusable named templates, and NOTES.txt, which is itself templated and printed to the user after install, handy for usage hints. The charts directory holds dependency subcharts, either vendored or pulled in by helm dependency update. Then there are supporting files: values.schema.json to validate supplied values against a JSON schema, and .helmignore to keep junk out of the packaged chart. Knowing this layout is what lets me open an unfamiliar 40 file chart and find what I need in a couple of minutes, and author a clean one myself.

Knows Chart.yaml/values.yaml/templates/charts Distinguishes version vs appVersion Knows _helpers.tpl, NOTES.txt, values.schema.json

Then they probe: What is the difference between version and appVersion in Chart.yaml?

Practise this one
Junior

How do you manage environment-specific configuration (dev, staging, prod) with one chart?

What most people say

I would make a separate chart or copy for each environment.

Copying the chart per environment is the anti-pattern: the copies drift and a fix has to be applied several times. The right approach is one chart with layered per-environment values files.

The structure behind a strong answer

  1. 1

    One chart, many values. Keep a single chart and a base values.yaml, then a values file per environment (values-prod.yaml) for the differences.

  2. 2

    Layer with -f. Apply them in order, helm upgrade ... -f values.yaml -f values-prod.yaml, so the environment file overrides the base.

  3. 3

    --set for the small stuff. Use --set for one-off or CI-injected values like an image tag, since it overrides the files.

  4. 4

    Avoid duplication. Do not fork the chart per environment. Differences live only in the per-environment values, which keeps everything consistent and reviewable.

What gets you hired

I keep one chart and vary only the values. There is a base values.yaml with the shared defaults, and then a small values file per environment, values-dev.yaml, values-staging.yaml, values-prod.yaml, containing only the differences. In practice those are tiny, maybe 15 lines each, covering things like replica counts, so dev runs 1 replica while prod runs 6, plus resource sizes and hostnames. At deploy time I layer them: helm upgrade with -f values.yaml -f values-prod.yaml. Later files override earlier ones, so the environment file wins over the base. For the truly dynamic or one-off values, the image tag from CI for instance, I use --set, which overrides the files and is convenient in a pipeline. The thing I deliberately avoid is forking the chart per environment, because 3 copies inevitably drift and every fix has to be applied in 3 places, and one of them always gets missed. With this layout the chart is the single source of truth for structure, and the per-environment values stay small, reviewable in a 2 minute pull request, and the only thing that differs.

One chart + layered per-env values files Knows -f ordering/precedence Uses --set for dynamic/CI values

Then they probe: Why not maintain a separate chart per environment?

Practise this one
Mid

How do chart dependencies and subcharts work in Helm?

What most people say

You can include other charts as dependencies in your chart.

It knows dependencies exist but not how to configure them, how parent values map to subcharts, what global values do, or how conditions toggle them, which is exactly where umbrella charts get confusing.

The structure behind a strong answer

  1. 1

    Declare dependencies. List dependencies in Chart.yaml (name, version, repository), then helm dependency update pulls them into the charts/ directory.

  2. 2

    Subcharts and umbrella charts. A parent (umbrella) chart aggregates subcharts so a whole stack, app plus its database and cache, installs as one release.

  3. 3

    Passing values down. The parent sets a subchart values under a key named after the subchart, and .Values.global makes values visible to all subcharts at once.

  4. 4

    Conditions and aliases. condition or tags enable or disable a subchart, and alias lets you include the same chart twice under different names.

What gets you hired

Dependencies are declared in Chart.yaml under dependencies, each with a name, a version constraint such as 12.1.x, and a repository, and helm dependency update resolves and vendors them into the charts directory. The common pattern is an umbrella, or parent, chart that aggregates several subcharts so a whole stack, the app plus its database and cache, installs and upgrades as 1 release instead of 3. The part people get wrong is how configuration flows. To configure a subchart from the parent, you nest its values under a key matching the subchart name in the parent values.yaml, so the parent owns the whole stack config. For values that many subcharts need, like a shared image registry or the environment name, you put them under .Values.global, which Helm exposes to every subchart, so 1 line sets the registry for all of them. And there are controls. A condition or tags field enables or disables a subchart, so the same umbrella can turn the bundled Postgres on in dev and off in prod where a managed database is used, and alias lets you include the same chart twice under different names, for example 2 Redis instances, one for cache and one for queues. Dependencies plus globals plus conditions are what make an umbrella chart clean rather than a mess.

Declares deps in Chart.yaml + dependency update Knows parent-key and global value flow Knows conditions/tags and alias

Then they probe: How do you set a value for a subchart from the parent chart?

Practise this one
Mid

What actually happens during a helm upgrade, and how does Helm compute the changes?

What most people say

Helm just re-applies all the manifests with the new values.

Helm 3 does not blindly re-apply, it does a three-way merge against live state and the previously-applied manifest. Missing this means you cannot reason about why an upgrade keeps or reverts an out-of-band change.

The structure behind a strong answer

  1. 1

    Render the new manifests. Helm renders the chart with the new values/version to produce the desired manifests for this revision.

  2. 2

    Three-way merge. Helm 3 computes a patch from three inputs: the old manifest (last applied), the new manifest, and the live state in the cluster, so it reconciles correctly even if something changed out of band.

  3. 3

    Track state in the release. It reads the previous revision manifests from the release Secret to know the old state, then records the new revision after applying.

  4. 4

    Apply and record. Helm applies the computed changes, and on failure you can rollback (or use --atomic to auto-rollback and --wait to gate on readiness).

What gets you hired

On a helm upgrade, Helm first renders the chart with the new values or chart version to get the desired manifests for the new revision. Then, importantly, Helm 3 does a three-way strategic merge to figure out what to change. It considers three states: the old manifest, which is what Helm last applied and reads from the release Secret, the new manifest it just rendered, and the live state of the objects in the cluster. Merging all three means it computes a correct patch even when something was changed out of band, it can tell the difference between a field you changed in the chart and a field something else modified live, which the older two-way approach in Helm 2 got wrong. It then applies the computed changes and records a new revision in the release Secret, keeping the prior ones for rollback. I usually pair this with --atomic, so a failed upgrade automatically rolls back instead of leaving a half-applied release, and --wait, so the upgrade only succeeds once the new resources are actually ready.

Knows Helm 3 three-way merge (old/new/live) Knows it reads previous state from the release Secret Mentions --atomic/--wait

Then they probe: Why is the three-way merge better than just diffing old and new manifests?

Practise this one
Mid

What are Helm hooks, and how would you use one for a database migration?

What most people say

Hooks let you run things at certain times, like before or after install.

It states the concept but gives no concrete pattern. The interviewer wants the migration Job as a pre-upgrade hook, with ordering, cleanup, and the caveat that hooks are not managed like normal resources.

The structure behind a strong answer

  1. 1

    What hooks are. Hooks are resources annotated to run at a specific lifecycle point, pre-install, post-install, pre-upgrade, post-upgrade, pre-delete, rather than as part of the normal manifest set.

  2. 2

    The migration pattern. A database migration is typically a Kubernetes Job annotated as a pre-upgrade (and pre-install) hook, so the schema is migrated before the new app version rolls out.

  3. 3

    Ordering and cleanup. helm.sh/hook-weight orders multiple hooks; helm.sh/hook-delete-policy controls whether the hook resource (the Job) is cleaned up after it succeeds.

  4. 4

    The caveat. Hooks are outside the normal release reconciliation, so they are not tracked or rolled back like regular resources. A failed hook fails the release, and you must handle idempotency and cleanup yourself.

What gets you hired

Hooks let me run a resource at a specific point in the release lifecycle instead of as part of the normal manifest set. I annotate a resource with helm.sh/hook and a value like pre-install, post-install, pre-upgrade, post-upgrade, or pre-delete. The canonical use is a database migration. I define a Kubernetes Job that runs the migration and annotate it as a pre-upgrade and pre-install hook, so Helm runs that Job and waits for it to complete before rolling out the new application version, which guarantees the schema is ready first. Helm waits up to its default 5 minute timeout, so if a migration realistically takes 10 minutes I raise the timeout rather than let the release fail. For control, helm.sh/hook-weight orders multiple hooks and lower runs first, so a weight of -5 goes ahead of 0, and helm.sh/hook-delete-policy decides whether the Job is cleaned up after success, otherwise you look up in 6 months and find 300 completed migration Jobs in the namespace. The important caveat is that hooks live outside normal release reconciliation. They are not part of the tracked manifest set, so they are not rolled back automatically and Helm does not manage their drift. A failing hook fails the release, so I make migrations idempotent and handle cleanup deliberately.

Knows hook points + that they are outside normal reconciliation Migration = pre-upgrade Job hook Knows hook-weight ordering + delete-policy cleanup

Then they probe: Why run a migration as a pre-upgrade hook rather than an init container?

Practise this one
Mid

A Helm release failed or is misbehaving. How do you debug it?

What most people say

I would run helm upgrade again and see if it works the second time.

Re-running can make it worse, especially if the release is stuck in a pending state. The right move is to render and inspect with helm template/get, then debug the actual resources, and rollback to recover a stuck release.

The structure behind a strong answer

  1. 1

    Render and inspect. helm template or helm install --dry-run --debug renders the manifests locally so you can see exactly what Helm would apply and catch templating or values errors.

  2. 2

    Inspect the live release. helm status shows release state, helm get manifest/values/all shows what was actually applied and the merged values, and helm history shows the revisions.

  3. 3

    Drop to kubectl. Once you know the rendered resources, use kubectl describe/logs/events on them, the actual failure (a bad image, a crash, a missing secret) is usually at the Kubernetes level.

  4. 4

    Recover a stuck release. A release stuck in pending-upgrade or pending-install (an interrupted operation) can be recovered with helm rollback to the last good revision; --atomic avoids this by auto-rolling-back.

What gets you hired

I separate templating problems from runtime problems. First I render and inspect, helm template, or helm install and upgrade with --dry-run --debug, shows me exactly the manifests Helm would apply, which catches templating mistakes, missing values, or malformed YAML before they touch the cluster. Then for a live release I use helm status for its state, helm get manifest and helm get values to see what was actually applied and the final merged values, and 9 times out of 10 the bug is a value I did not expect winning precedence, and helm history to see the revisions. Once I know the rendered resources I drop to kubectl describe, logs, and events on them, because the real failure, a bad image, a crashing pod, a missing secret, is at the Kubernetes level and Helm is just surfacing it. The specific Helm failure mode to know is a release stuck in pending-upgrade or pending-install, which happens when an operation is interrupted, for example CI is cancelled partway through, and I recover it with helm rollback to the last good revision, so if revision 8 is stuck I roll back to 7. Going forward I run upgrades with --atomic and an explicit --timeout, since the default is 5 minutes, so a failed upgrade auto rolls back instead of leaving the release wedged.

Renders with helm template/--dry-run Uses helm get/status/history Knows pending-state recovery via rollback

Then they probe: A release is stuck in pending-upgrade. How do you recover it?

Practise this one
Senior

When would you choose Helm versus Kustomize?

What most people say

Helm is better because it has more features like templating and rollback.

"Helm is better" is dogma, not judgment. Kustomize fits when you own simple manifests and want no templating language. A senior frames it as a use-case trade-off and knows they can be combined.

The structure behind a strong answer

  1. 1

    Helm is templating + packaging + lifecycle. Helm parameterizes manifests with a templating language, packages an app as a versioned chart, and manages releases with upgrade/rollback. Strong for distributing reusable, configurable apps.

  2. 2

    Kustomize is template-free overlays. Kustomize patches a base set of plain manifests with environment overlays, no templating language, and it is built into kubectl (-k). Strong for managing your own manifests.

  3. 3

    Pick by use case. Helm when you publish or consume reusable charts and want releases/rollback; Kustomize when you own the manifests and want simple, transparent env variation without a templating DSL.

  4. 4

    They can combine. You can render a Helm chart and then apply Kustomize patches on top, and GitOps tools like Argo CD support both, so it is not strictly either/or.

What gets you hired

They solve overlapping problems differently. Helm is templating plus packaging plus lifecycle. It parameterizes manifests through a templating language, packages an application as a versioned chart you can publish and share, and manages installations as releases, so a bad upgrade is 1 helm rollback away and, by default, Helm keeps the last 10 revisions to roll back to. That makes it the natural choice for distributing or consuming reusable, configurable applications, an off-the-shelf ingress controller or database, where first-class release and rollback matter. Kustomize is template-free. You keep plain Kubernetes manifests as a base and layer environment-specific overlays and patches on top, with no templating DSL, and it has been built straight into kubectl since 1.14 with the -k flag. That shines when you own your manifests and want simple, transparent, reviewable variation across 3 or 4 environments without learning a template language or fighting whitespace. So I choose Helm when packaging and sharing reusable apps with lifecycle management matters, and Kustomize when I manage my own manifests and value simplicity and transparency. And they are not mutually exclusive. You can render a Helm chart and apply Kustomize patches over the output, and GitOps tools like Argo CD support both natively, so I pick based on the use case rather than treating it as a rivalry.

Frames Helm=template+package+lifecycle vs Kustomize=overlays Picks by use case not dogma Knows they can combine / Argo supports both

Then they probe: What is the main philosophical difference between the two?

Practise this one
Senior

How does Helm fit into a GitOps and CI/CD workflow?

What most people say

You run helm install in your CI pipeline to deploy the chart.

Imperative helm install from CI is the pre-GitOps approach, no drift detection, the cluster is not reconciled to git. Modern GitOps declares the chart and values in git and lets Argo CD or Flux reconcile, and it must handle secrets safely.

The structure behind a strong answer

  1. 1

    Declarative, not imperative. In GitOps you do not run helm install by hand; you declare the chart, version, and values in git and let a controller reconcile the cluster to match.

  2. 2

    How the tools consume charts. Argo CD and Flux can take a chart reference plus values from git, render it (helm template under the hood), and apply it, with drift detection and auto-sync.

  3. 3

    Pin and promote. Pin chart and image versions in git so deploys are reproducible and auditable, and promote by changing values in git through environments via pull requests.

  4. 4

    Secrets. Never commit plaintext secrets in values. Use SOPS-encrypted values, sealed secrets, or an external secrets operator, so git holds only encrypted or referenced secrets.

What gets you hired

The shift is from imperative to declarative. Rather than a pipeline running helm install or helm upgrade against the cluster, in GitOps I declare the desired state in git, which chart, which version, and the values, and a controller continuously reconciles the cluster to match, Argo CD polling roughly every 3 minutes by default. Argo CD and Flux both consume Helm charts this way: I point an Application at a chart reference and a values source in git, the tool renders the chart, effectively helm template, and applies the result, and I get drift detection and auto-sync, so if someone edits the cluster by hand it is corrected back to git within a couple of minutes. For reproducibility I pin the chart version and the image tags in git, never a floating latest, so every deploy is auditable and rollback is just reverting 1 commit. I promote across environments by changing values in git through pull requests, which gives review and a clear history. The one thing I am careful about is secrets. I never commit plaintext secrets in values files. Instead I use SOPS-encrypted values, sealed secrets, or an external secrets operator that pulls from a real secrets manager, so git only ever holds encrypted or referenced secrets. So Helm provides the packaging and templating, and GitOps provides the declarative, audited delivery around it.

Declarative chart+values in git, not imperative install Knows Argo CD/Flux render+reconcile charts Pins versions + handles secrets (SOPS/external secrets)

Then they probe: Why is declaring a chart in git better than running helm install from CI?

Practise this one
Senior

How do you operate Helm securely and at scale across an organization?

What most people say

I would make sure only admins can run Helm and keep the charts updated.

It gestures at access control but misses the concrete levers: RBAC (since Helm 3 uses your own creds, no Tiller), chart signing/provenance, OCI distribution, version pinning, and keeping secrets out of values.

The structure behind a strong answer

  1. 1

    No Tiller (Helm 3). Helm 3 removed Tiller, the privileged in-cluster server, so Helm now runs with the user own kubeconfig and RBAC. Use Kubernetes RBAC to control who can deploy what, where.

  2. 2

    Chart provenance and trust. Sign charts (helm package --sign produces a .prov provenance file) and verify on install (--verify), and vet third-party charts rather than installing them blindly.

  3. 3

    Distribution and pinning. Host internal charts in an OCI registry, pin chart and image versions (ideally by digest), and scan the rendered manifests and images for issues in CI.

  4. 4

    Values hygiene and policy. Keep secrets out of values (SOPS/external secrets), enforce sane defaults with values.schema.json, and apply admission policy (OPA/Kyverno) so rendered resources meet org standards.

What gets you hired

A few things, starting with the big Helm 3 change: Tiller is gone. Helm 2 had Tiller, a privileged in-cluster component that was a real security liability; Helm 3 removed it, so Helm now acts with the user own kubeconfig and respects Kubernetes RBAC. That means I control who can deploy what and where through normal RBAC and namespace scoping, rather than a shared privileged service. For trust in the charts themselves, I use provenance: helm package --sign produces a signed .prov file and helm install --verify checks it, and I vet third-party charts instead of installing arbitrary ones blindly, since a chart can create anything. For distribution and reproducibility, I host internal charts in an OCI registry, pin both chart versions and image versions, ideally by digest, and scan the rendered manifests and referenced images in CI. And for hygiene at scale: keep secrets out of values entirely using SOPS or an external secrets operator, enforce a values.schema.json so misconfigured values fail fast, and back it with admission policy like OPA Gatekeeper or Kyverno so whatever a chart renders still has to meet org standards, non-root, resource limits, approved registries. The theme is treat charts as supply-chain artifacts: signed, pinned, scanned, RBAC-scoped, and policy-checked.

Knows Helm 3 removed Tiller, uses RBAC Chart signing/provenance + vetting third-party charts OCI distribution, version pinning, secrets out of values, admission policy

Then they probe: Why was removing Tiller a security improvement?

Practise this one

GitOps

13 questions · Foundation, Junior, Mid, Senior
Foundation

What is GitOps, and what problem does it solve?

What most people say

GitOps means you deploy your application from a Git repository.

Deploying from git is just CI/CD. GitOps is specifically git as the source of truth plus a controller that continuously reconciles the cluster to it, with the resulting auditability, rollback, and drift correction, none of which this captures.

The structure behind a strong answer

  1. 1

    Git is the source of truth. The desired state of infrastructure and apps is declared in git, so the repository, not a person running commands, defines what should be running.

  2. 2

    An agent reconciles. A controller in the cluster continuously compares the live state to git and makes the cluster match, rather than someone pushing changes manually.

  3. 3

    The benefits. Full audit trail (git history), trivial rollback (revert a commit), consistency across environments, and automatic correction of drift.

  4. 4

    When it fits. It shines for Kubernetes and declarative infrastructure where you want every change reviewed, versioned, and reconciled.

What gets you hired

GitOps is an operating model where git is the single source of truth for the desired state of your infrastructure and applications, and a controller running in the cluster continuously reconciles the actual state to match what is in git. Argo CD, for example, re-syncs on a 3 minute interval by default, so it is not a one-shot deploy. That is the real distinction from ordinary deploy-from-git: reconciliation is continuous, and the cluster is always being driven toward the declared state, not just at deploy time. The model gives you several things at once. A complete audit trail, because every change is a reviewed commit with an author, a timestamp, and history. Trivial rollback, because reverting is just reverting a commit and letting the controller reconcile, which on my last team turned a bad release into a 2 minute recovery instead of a scramble for the right helm command. Consistency, because the same declarative definitions produce the same environments. And drift correction, because if someone changes the cluster by hand, the controller notices within that sync window and pulls it back to what git says. It fits best with Kubernetes and declarative infrastructure, where I want every change versioned, reviewed, and applied automatically rather than someone running kubectl straight against production.

Git as source of truth + continuous reconciliation Names audit/rollback/drift-correction benefits Distinguishes from plain deploy-from-git

Then they probe: How is GitOps different from normal CI/CD that deploys from git?

Practise this one
Foundation

What is the difference between push-based and pull-based deployment, and why does GitOps prefer pull?

What most people say

Push is when CI deploys to the cluster and pull is when the cluster pulls changes; they both work.

"They both work" misses why GitOps chose pull: keeping cluster credentials out of CI (security) and continuous reconciliation with drift correction (correctness). Those are the whole reason for the preference.

The structure behind a strong answer

  1. 1

    Push model. A CI pipeline holds cluster credentials and runs kubectl or helm to push changes into the cluster from outside.

  2. 2

    Pull model. An agent inside the cluster watches git and pulls changes in, applying them itself. Nothing external needs cluster credentials.

  3. 3

    Why pull is safer. No cluster credentials live in CI or any external system, shrinking the attack surface. A leaked CI token cannot deploy to prod.

  4. 4

    Why pull is more correct. The in-cluster agent reconciles continuously, so it also corrects drift, while a push pipeline only acts at deploy time and never notices manual changes.

What gets you hired

In the push model, a CI pipeline outside the cluster holds cluster credentials and runs kubectl or helm to push manifests in. In the pull model, an agent running inside the cluster watches the git repository and pulls changes in, applying them itself. GitOps prefers pull for two reasons. First, security. With pull, no external system holds cluster credentials, so a leaked CI token cannot be used to deploy to or tamper with the cluster. When we moved 12 services off push pipelines, we deleted the long-lived kubeconfig secret from CI entirely, and the credentials stayed inside the trust boundary. Second, correctness. Because the in-cluster agent reconciles continuously, on a default 3 minute loop with Argo CD, it does not just apply once at deploy time. It keeps watching and corrects drift if someone changes the cluster out of band, which a push pipeline never sees, because it fires once and is done. I have watched a hand-edited replica count get reverted automatically inside a couple of minutes. So pull gives you a smaller attack surface and a self-healing, always-converging system, which is exactly what the GitOps model is built around.

Push = CI holds creds, pull = in-cluster agent Knows pull keeps creds out of CI Knows pull enables continuous drift correction

Then they probe: What is the security advantage of pull-based deployment?

Practise this one
Foundation

What are the core principles of GitOps?

What most people say

You use git to store your config and deploy from it automatically.

It gestures at one or two principles but misses the precise four: declarative, versioned/immutable, pulled automatically, and continuously reconciled. The continuous-reconciliation principle in particular is what makes it GitOps.

The structure behind a strong answer

  1. 1

    Declarative. The entire desired state is expressed declaratively, what you want, not the steps to get there.

  2. 2

    Versioned and immutable. That desired state is stored in git, so it is versioned, auditable, and immutable, every change is a commit.

  3. 3

    Pulled automatically. Software agents automatically pull the desired state from git, no manual apply.

  4. 4

    Continuously reconciled. Agents continuously observe actual state and reconcile it toward the declared state, correcting any drift.

What gets you hired

There are 4, often called the OpenGitOps principles. Declarative: the whole desired state of the system is expressed declaratively, so you describe what you want, not a sequence of imperative steps. Versioned and immutable: that desired state lives in git, so it is versioned, auditable, and immutable, and every change is a commit you can review and trace back to an author. Pulled automatically: software agents automatically pull the declared state from git, rather than a human running an apply. And continuously reconciled: those agents keep observing the actual state and work to make it match the declared state, correcting drift whenever the two diverge. Argo CD does that on a 3 minute sync loop by default, and Flux is similar, so drift typically gets healed within minutes rather than sitting there until someone notices. That fourth principle is what really separates GitOps from just storing manifests in git. The system is always converging on what git says, not only at the moment of a deploy. And the first two are what buy you the operational wins people care about, a full audit trail across every commit and a rollback that is one git revert away.

Names declarative/versioned/pulled/reconciled Stresses continuous reconciliation Connects to audit + rollback

Then they probe: Which principle separates GitOps from simply keeping manifests in a repo?

Practise this one
Junior

What are Argo CD and Flux, and how do they differ?

What most people say

They are both GitOps tools that deploy from git; Argo CD is the popular one.

Naming popularity is not a comparison. The interviewer wants the actual difference: Argo is app-centric with a UI, Flux is a modular toolkit with strong image automation, and how that affects the choice.

The structure behind a strong answer

  1. 1

    Both are GitOps controllers. Argo CD and Flux are CNCF graduated tools that implement the pull-based reconcile-from-git model for Kubernetes.

  2. 2

    Argo CD. Application-centric with a strong web UI for visualizing sync status and diffs. You define Application resources pointing at a repo path and target cluster.

  3. 3

    Flux. A composable GitOps toolkit of controllers (source, kustomize, helm, image automation). More CRD-driven and modular, lighter footprint, integrates tightly with image automation.

  4. 4

    Choosing. Argo CD when teams want visibility and a UI; Flux when you want a lean, composable, GitOps-native toolkit. Both are solid; it is a fit-and-preference choice.

What gets you hired

Both Argo CD and Flux are CNCF graduated GitOps controllers that implement the pull-based, reconcile-from-git model for Kubernetes, and either is a fine choice. The difference is in flavor. Argo CD is application-centric and known for its strong web UI. I define Application resources that point at a repo path and a target cluster, and the UI shows sync status, the diff between git and live, and resource health, which teams love for visibility. It also has patterns like app-of-apps and ApplicationSets, which is how one Argo instance can drive 200 applications across several clusters without hand-writing each one. Flux is more of a composable GitOps toolkit, a set of controllers, source, kustomize, helm, and image-automation, that I assemble. It is more CRD-driven and modular, it has a lighter footprint, and its image automation is particularly strong, so it can watch a registry and open a commit bumping a tag on its own. So in practice I reach for Argo CD when a team wants the dashboard and clear visualization, and Flux when I want a lean, GitOps-native toolkit that composes well and automates image updates. Both reconcile continuously, Argo defaults to polling git about every 3 minutes on top of webhooks, and both support Kustomize and Helm.

Knows both are CNCF GitOps controllers Argo = app-centric + UI, Flux = modular toolkit Picks by fit, mentions image automation

Then they probe: When might you prefer Argo CD over Flux?

Practise this one
Junior

How does drift detection and self-healing work in GitOps?

What most people say

It checks if the cluster matches git and updates it if not.

Roughly right but misses the key behavior and its consequence: self-heal reverts manual changes, so a hotfix done with kubectl gets undone. Not knowing that leads to confusion when fixes vanish.

The structure behind a strong answer

  1. 1

    The reconcile loop. On an interval (and on git webhooks), the controller compares the desired state in git with the live state in the cluster.

  2. 2

    Detecting drift. If they differ, the application is marked OutOfSync and the controller reports exactly what diverged.

  3. 3

    Self-heal. With self-heal enabled, the controller automatically re-applies git, reverting any manual or out-of-band change so the cluster always matches the repo.

  4. 4

    The implication. You stop fixing things by hand in the cluster, because those changes get reverted. The fix must go through git, which is the whole point.

What gets you hired

The controller runs a reconcile loop. On a timer, Argo CD polls roughly every 3 minutes by default, and immediately on a git webhook, it compares the desired state declared in git with the live state of the objects in the cluster. If they match, the application is in sync. If they differ, it is marked OutOfSync and the tool shows exactly which resources and which fields diverged, right down to a single replica count going from 3 to 5, which is great for visibility. The defining behavior is self-heal. With it enabled, the controller does not just report drift, it automatically re-applies git and reverts the out-of-band change, typically within a minute or 2, so the cluster is always pulled back to what the repository says. The big operational implication, which catches people out, is that you can no longer fix things by editing the cluster directly. If I kubectl edit a resource to hotfix prod at 2 in the morning, the controller quietly reverts it on the next reconcile and I have wasted the night. The fix has to go through git, a commit and a pull request. That is exactly the discipline GitOps is enforcing: git is the source of truth, not the live cluster.

Describes the git-vs-live reconcile loop Knows OutOfSync detection + self-heal revert Knows you must fix via git, not the cluster

Then they probe: You kubectl edit a resource to hotfix prod and it reverts minutes later. Why?

Practise this one
Junior

How do you structure repositories for GitOps? Should app code and deployment config live together?

What most people say

I would put the Kubernetes manifests in the same repo as the application code.

Co-locating is tempting but couples build and deploy: a manifest tweak triggers an app build, an app commit can change prod directly, and access control gets muddy. The common practice is a separate config repo.

The structure behind a strong answer

  1. 1

    Separate config from code. Keep the application source repo (built by CI) separate from the deployment config repo (reconciled by the GitOps controller).

  2. 2

    Why separate. A config change should not trigger an app rebuild, and an app commit should not directly mutate prod. It also lets you apply different access controls to each.

  3. 3

    Environments. Represent environments as folders or overlays (or sometimes branches) in the config repo, base plus per-environment overrides, so promotion is a change in the right path.

  4. 4

    Scope. Choose mono-repo vs repo-per-team by org size; many use a single config repo with clear directory structure and app-of-apps for many applications.

What gets you hired

The common practice is to separate the application source repository, the one CI builds and tests, from the deployment config repository, the one the GitOps controller reconciles. There are good reasons. It decouples build from deploy, so bumping an image tag or a replica count does not kick off a 12 minute application rebuild, and an application code commit cannot directly mutate production, because the deploy is a separate, reviewed change in the config repo. It also lets me apply different access controls, and that gap is usually wide, maybe 40 engineers can commit app code while only 5 can approve a change to what runs in prod. Within the config repo I represent environments as directories or Kustomize overlays, sometimes branches, with a base plus 3 per-environment overlays for dev, staging, and prod, so promoting a change is just updating the right path. For scope, mono-repo versus repo-per-team depends on org size. Plenty of teams run a single config repo with a clear directory structure plus an app-of-apps pattern to manage many applications. The thing I avoid is co-locating manifests with app code, because it tangles the build and deploy lifecycles and weakens exactly the separation GitOps is meant to give me.

Separates app source from config repo Explains build/deploy decoupling + access control Environments as overlays/folders

Then they probe: Why not keep manifests in the application repo?

Practise this one
Mid

How do you promote a change from dev to staging to prod in a GitOps workflow?

What most people say

I would redeploy the application to staging and then to production.

"Redeploy" is the push-model mindset. In GitOps, promotion is a git change, updating the tracked version in the next environment via a reviewed PR, which the controller then reconciles. The audit and gating come from that.

The structure behind a strong answer

  1. 1

    Promotion is a git change. Promoting means updating the desired state for the next environment in git, typically bumping the image tag or chart version in that environment overlay.

  2. 2

    Through a pull request. The change goes through a PR, so promotion is reviewed and audited, and merging is what triggers the controller to reconcile that environment.

  3. 3

    Per-environment config. Each environment has its own path/overlay, so you promote by copying the validated version into the next environment values, not by re-running a deploy.

  4. 4

    Gates. Gate promotion with required reviews, checks, or automated tests/analysis, and only promote a version that already passed the previous environment.

What gets you hired

In GitOps, promotion is a change in git, not a manual redeploy. Each environment has its own desired state in the config repo, an overlay or directory with its own image tag or chart version. To promote, I update that next environment's desired state, setting it to the exact version validated in the previous one, so the same image digest that soaked in staging for 24 hours is the digest that lands in the prod overlay, not a rebuild. I make that change through a pull request, so the promotion is reviewed and fully audited, and merging it is what triggers the controller to reconcile the target environment. That gives me natural gates, required reviewers, we used 2 approvers for prod, status checks, and automated tests or analysis as conditions on the PR. I only ever promote a version that already succeeded in the prior environment, because I am moving a known-good artifact forward. The whole flow is known-good version, PR into the next environment path, review and checks, merge, and the controller reconciles, typically within its poll interval, 3 minutes by default in Argo CD, or immediately on a webhook. It is auditable and reversible by design, because rolling back a promotion is just reverting 1 commit and letting the controller converge again.

Promotion = a git/PR change to the next env Promotes a known-good immutable artifact Gates via PR reviews/checks

Then they probe: Why promote the same artifact rather than rebuild per environment?

Practise this one
Mid

You cannot commit plaintext secrets to git. How do you handle secrets in GitOps?

What most people say

I would store the secrets in a Kubernetes Secret and apply them with everything else.

A plain Kubernetes Secret is only base64 and would sit in plaintext in git. The whole question is how to avoid that: SOPS-encrypted values, Sealed Secrets, or an external secrets manager referenced from git.

The structure behind a strong answer

  1. 1

    The problem. Git is the source of truth and everything goes in it, but you cannot put plaintext secrets in a repository, so you need a way to store them encrypted or by reference.

  2. 2

    SOPS. Encrypt the secret values in git with SOPS (backed by a KMS or age key); the controller decrypts them in-cluster at apply time. Encrypted secrets live in git.

  3. 3

    Sealed Secrets. Encrypt a secret to a cluster-specific public key producing a SealedSecret; only that cluster controller can decrypt it, so the encrypted form is safe in git.

  4. 4

    External Secrets Operator. Keep the real secret in a manager (Vault, AWS/GCP secrets) and store only a reference in git; the operator fetches and creates the Kubernetes Secret at runtime.

What gets you hired

This is the genuinely awkward part of GitOps, because the model wants everything in git but you obviously cannot put plaintext secrets in a repo, and a Kubernetes Secret is only base64-encoded, which is not encryption. There are three common approaches. SOPS: I encrypt the secret values in git using SOPS backed by a KMS or age key, so what is committed is ciphertext, and the controller, or a SOPS integration, decrypts it in-cluster at apply time. Sealed Secrets: I encrypt a secret to a cluster-specific public key, producing a SealedSecret custom resource that only that cluster controller can decrypt with its private key, so the sealed form is safe to commit. External Secrets Operator: I do not put the secret in git at all, only a reference, the real secret lives in a manager like Vault or a cloud secrets service, and the operator fetches it and materializes a Kubernetes Secret at runtime. The trade-offs: SOPS and Sealed Secrets keep an encrypted copy in git, simple and self-contained but you manage encryption keys and rotation in-band; the External Secrets approach keeps git free of secret material and centralizes rotation and audit in a real manager, at the cost of running that integration. I lean toward External Secrets when there is already a secrets manager, and SOPS or Sealed Secrets for simpler setups.

Knows plain Secret/base64 is not safe in git Names SOPS / Sealed Secrets / External Secrets Articulates the trade-offs

Then they probe: What is the trade-off between Sealed Secrets/SOPS and the External Secrets Operator?

Practise this one
Mid

In GitOps, CI builds a new image. How does that new version actually get deployed?

What most people say

CI builds the image and deploys it to the cluster.

That is the push model and breaks GitOps: CI should not deploy directly. The new image gets deployed by updating its tag in git (via image automation or a CI commit) so the controller reconciles it.

The structure behind a strong answer

  1. 1

    CI builds, GitOps deploys. CI builds, tests, and pushes the image to a registry. It does not deploy directly to the cluster, that is the GitOps controller job.

  2. 2

    Update the version in git. Something must change the image tag or digest in the config repo, because the controller only acts on git. That is the bridge between CI and CD.

  3. 3

    Two ways to do it. Either image automation (Argo CD Image Updater or Flux image automation) watches the registry and commits the new tag back to git, or the CI pipeline itself commits the new tag to the config repo.

  4. 4

    Then reconcile. Once git has the new version, the controller detects it and reconciles, deploying the new image. Git stays the source of truth throughout.

What gets you hired

The key is the separation between CI and CD. CI builds the image, runs tests, and pushes it to a registry, but it does not deploy to the cluster, deployment is the GitOps controller's job. So the bridge is updating the image version in git, because the controller only ever acts on what is in the repository. There are 2 common ways to make that update. The first is image automation, a component like Argo CD Image Updater or Flux image automation watches the registry on an interval, we ran ours every 5 minutes, matches new tags against a policy such as semver 1.x, and commits the updated tag or digest back to the config repo itself. The second is having the CI pipeline commit the new image tag into the config repo as a step right after the push, usually 1 extra job at the end of the build. Either way, once the new version lands in git the controller detects the change and reconciles the cluster to deploy it. The property that matters is that git stays the source of truth the whole time, even an automated image bump is a commit, so every deploy is auditable and revertible with a single git revert, and CI never needs cluster credentials, which removes a standing set of production secrets from the pipeline.

CI builds/pushes, GitOps deploys (separation) Knows the tag must change in git Names image automation or CI-commits-tag

Then they probe: Why should CI not deploy directly to the cluster in GitOps?

Practise this one
Mid

How do you do progressive delivery (canary, blue-green) in a GitOps setup?

What most people say

GitOps does canary deployments automatically when you push a change.

It does not. A GitOps controller does a normal rollout. Traffic-shifted, metric-analyzed canaries need a dedicated controller like Argo Rollouts or Flagger layered on top, with automated analysis and rollback.

The structure behind a strong answer

  1. 1

    Plain GitOps is all-or-nothing. A GitOps controller reconciles to the declared state; on its own it does a standard rollout, not a metric-driven canary with traffic shifting.

  2. 2

    Add a rollout controller. Argo Rollouts or Flagger add canary and blue-green strategies: they gradually shift traffic to the new version in steps.

  3. 3

    Automated analysis. At each step they run analysis against metrics (error rate, latency) and only proceed if the new version is healthy, otherwise they automatically roll back.

  4. 4

    Still GitOps. The rollout strategy is declared in git like everything else, so the GitOps controller manages it; progressive delivery layers on top, it does not replace GitOps.

What gets you hired

Plain GitOps does not give you traffic-shifted canaries by itself, the controller reconciles the cluster to the declared state, which results in a standard rollout, not a metric-driven progressive one. To get canary or blue-green, I add a progressive-delivery controller, Argo Rollouts or Flagger. These replace the plain Deployment with a strategy that gradually shifts traffic to the new version in defined steps, say 10 percent, then 25, then 50. Crucially, at each step they run automated analysis against real metrics, error rate, latency, custom SLOs, and only advance if the new version is healthy; if a step fails the analysis, they automatically roll back to the stable version. And this stays fully within GitOps: the rollout strategy and its analysis are declared in git just like any other resource, so the GitOps controller manages the Rollout object and the progressive-delivery controller executes the traffic shifting and analysis. So progressive delivery layers on top of GitOps, GitOps provides the declarative, reconciled delivery, and Argo Rollouts or Flagger add the safe, automated, metric-gated rollout.

Knows plain GitOps is a standard rollout Adds Argo Rollouts/Flagger for canary/blue-green Knows automated metric analysis + auto-rollback, still declared in git

Then they probe: What does the analysis step in a canary actually do?

Practise this one
Senior

How do you manage GitOps across many applications and clusters at scale?

What most people say

I would create an Argo CD Application for each app on each cluster.

That is exactly what does not scale, dozens of clusters times dozens of apps is unmanageable by hand. The senior answer is templating with app-of-apps and ApplicationSets to generate them from git.

The structure behind a strong answer

  1. 1

    Do not hand-define everything. Manually creating an Application per app per cluster does not scale. You need a way to generate and manage them from git.

  2. 2

    App-of-apps. A parent Application that manages a set of child Applications, so a whole environment or fleet is bootstrapped and managed from one declarative root.

  3. 3

    ApplicationSets. Argo CD ApplicationSets template Applications from generators (a list of clusters, git directories, or pull requests), so adding a cluster or app is a config change, not manual wiring. Flux has equivalent patterns.

  4. 4

    Fleet concerns. Consistent policy across clusters, central visibility, per-cluster overrides, and bootstrap (how the GitOps tool itself gets installed everywhere).

What gets you hired

Hand-defining an Application for every app on every cluster does not scale. With 50 apps across 10 clusters that is 500 hand-written definitions, which becomes unmanageable and drifts. So I use the fleet patterns. The app-of-apps pattern uses a single parent Application that manages a set of child Applications, so an entire environment is bootstrapped and reconciled from 1 declarative root: I point Argo at the root and it brings up everything beneath it. For real scale I use ApplicationSets, which template Applications from generators, a list generator over clusters, a git-directory generator that creates 1 app per folder, or a pull-request generator that spins up a preview environment per open PR and tears it down on merge. The effect is that adding a new cluster or a new app is a small config change that fans out automatically instead of manual wiring, and Flux has equivalent patterns with its kustomization and source controllers. Beyond the generation mechanics, the fleet concerns are enforcing consistent policy across all clusters, central visibility into sync status across the fleet, handling per-cluster overrides cleanly, and bootstrap, how the GitOps controller itself gets installed on every cluster, often managed by a higher-level GitOps setup. The principle is to declare the fleet, not each member.

Knows hand-defining each app does not scale Names app-of-apps + ApplicationSets (or Flux equivalents) Raises fleet policy/visibility/bootstrap

Then they probe: What problem do ApplicationSets solve?

Practise this one
Senior

What are the real challenges or downsides of GitOps?

What most people say

GitOps is great and does not really have downsides if you set it up properly.

Claiming no downsides is a maturity red flag. Real GitOps has well-known pain: secret management, non-declarative operations, a new debugging surface, and bootstrap. Acknowledging them shows you have actually run it.

The structure behind a strong answer

  1. 1

    Secrets are awkward. You cannot commit plaintext, so secrets always need extra tooling (SOPS, Sealed Secrets, External Secrets), which is the most cited pain point.

  2. 2

    Not everything is declarative. Some operations (one-off jobs, imperative tasks, certain CRD ordering) do not map cleanly to declarative git state, and sync ordering/waves add complexity.

  3. 3

    New failure and debugging surface. You now debug the reconcile layer too: OutOfSync states, failed syncs, the controller itself, and you cannot hotfix by hand because self-heal reverts it.

  4. 4

    Bootstrap and sprawl. A chicken-and-egg bootstrap (who installs the GitOps tool), plus repo and PR sprawl and drift for anything that lives outside git.

What gets you hired

GitOps is strong, but I would not pretend it is free. The most commonly cited pain is secrets. Since you cannot commit plaintext, every secret needs extra tooling like SOPS, Sealed Secrets, or an External Secrets operator, which is real overhead and, in the 2 teams I have seen adopt it, the single biggest source of friction in the first month. Second, not everything is cleanly declarative. One-off jobs, imperative operational tasks, and CRD-before-CR ordering do not map neatly to declarative state, so you lean on sync waves and hooks, which add complexity. Third, GitOps adds a new failure and debugging surface. Now I also debug the reconcile layer, OutOfSync conditions, failed syncs, controller health, and crucially I cannot hotfix prod by hand, because self-heal will revert my change on the next sync, often within 3 minutes, so even an emergency fix has to go through a commit and a review, which feels slow at 2am. Fourth, bootstrap is a chicken-and-egg problem, something has to install and configure the GitOps controller before it can manage anything, and there is potential repo and PR sprawl, plus drift for any resource that lives outside git. None of these are dealbreakers, and the auditability and consistency are usually worth it, but a mature setup plans deliberately for secrets, bootstrap, and break-glass procedures rather than assuming GitOps just works.

Names secrets, non-declarative ops, debugging surface, bootstrap Mentions break-glass for hotfixes Balanced, not a sales pitch

Then they probe: How do you handle an emergency hotfix when self-heal reverts manual changes?

Practise this one
Senior

What is the security model of GitOps, and how do you secure the pipeline?

What most people say

GitOps is secure because everything is in git and you can see the history.

Visibility is one part, but it misses the core security win (pull-based keeps creds out of CI) and the new responsibility (git is now the control plane and must be locked down with branch protection, reviews, and signed commits).

The structure behind a strong answer

  1. 1

    Pull-based reduces attack surface. Because deployment is pull-based, no external CI system holds cluster credentials, so a compromised pipeline cannot directly deploy to or attack the cluster.

  2. 2

    Git is now the control plane. Changing git changes production, so the repository must be protected like a control plane: branch protection, required reviews, and signed/verified commits.

  3. 3

    Audit and provenance. Every change is an attributed commit, giving a complete audit trail of who changed what and when, and you can require signed commits and verified images.

  4. 4

    Least privilege agent. Scope the GitOps controller RBAC to only what it manages, per namespace/cluster, so a compromise of the agent is contained.

What gets you hired

The security model has two sides. The win: because delivery is pull-based, no external CI system holds cluster credentials. An in-cluster agent pulls from git, so a compromised CI runner or a leaked token cannot deploy to or attack the cluster directly. If you have 30 pipelines that previously each carried a kubeconfig, that is 30 sets of standing cluster credentials you just deleted, which is a real reduction in attack surface. The responsibility that comes with it: git is now effectively the control plane, because 1 merge to the config repo changes production. So I protect the repository accordingly, branch protection on deploy branches, required pull-request reviews, at least 1 and often 2 approvers on production paths, and signed, verified commits so changes are authentic, plus restricting who can merge. The audit side is a benefit: every change is an attributed commit, so I have a complete, tamper-evident history of who changed what and when, and I can additionally require verified container images. Finally, least privilege for the agent itself. The GitOps controller should hold only the RBAC it needs for the namespaces and clusters it manages, not cluster-admin everywhere, so if the agent is compromised the blast radius is contained. The summary: pull-based shrinks the credential attack surface, but you must secure git as the control plane and scope the agent tightly.

Pull-based = no cluster creds in CI Treats git as the control plane (branch protection/reviews/signed commits) Least-privilege agent + audit trail

Then they probe: Why does pull-based delivery improve security over push?

Practise this one

Observability

15 questions · Foundation, Junior, Mid, Senior
Foundation

What is the difference between monitoring and observability?

What most people say

They are basically the same thing, watching your system to know if it is working.

"Basically the same" is the screen-out. Monitoring answers predefined questions about known failures; observability lets you ask new questions about unknown ones. Conflating them means you build dashboards but cannot debug novel incidents.

The structure behind a strong answer

  1. 1

    Monitoring watches the known. Monitoring tracks predefined metrics and known failure modes: is it up, is CPU high, predefined dashboards and alerts. It tells you something is wrong.

  2. 2

    Observability explores the unknown. Observability is the ability to ask arbitrary new questions about the system from its outputs, so you can understand novel failures (unknown-unknowns) without shipping new instrumentation.

  3. 3

    What enables it. Rich, high-context telemetry, metrics, logs, and traces, especially data you can slice by many dimensions, is what makes a system observable.

  4. 4

    Relationship. Monitoring tells you that something is broken; observability helps you understand why. Monitoring is a subset of what an observable system gives you.

What gets you hired

Monitoring is about watching the known. You define metrics and failure modes ahead of time, is the service up, is p99 latency above 300ms, is CPU past 80 percent, and you get dashboards and alerts for those. It tells you that something is wrong. Observability is broader. It is the ability to ask arbitrary new questions about the internal state of the system purely from its outputs, so when a novel failure happens, an unknown-unknown you never predicted, you can investigate and understand it without shipping new code to add instrumentation. What makes a system observable is rich, high-context telemetry across the 3 pillars, metrics, logs, and traces, especially data you can slice by many dimensions to chase a hypothesis. The one that saved me most often was being able to break error rate down by customer ID and find that a single tenant accounted for almost all of a 2 percent error spike. So the relationship is that monitoring tells you that something is broken and observability helps you understand why, and good monitoring is really a subset of what an observable system gives you. In practice I want both, predefined alerts for the known problems and enough telemetry to debug the ones I did not anticipate.

Monitoring=known failures, observability=ask new questions Mentions unknown-unknowns Wants both, not one

Then they probe: Why is "unknown-unknowns" the key phrase for observability?

Practise this one
Foundation

What are the three pillars of observability, and what is each good for?

What most people say

Metrics, logs, and traces, the three ways to monitor a system.

It names them but not what each is best at or how they differ. Knowing that metrics are for trends/alerting, logs for detail, and traces for distributed latency is what makes the answer useful.

The structure behind a strong answer

  1. 1

    Metrics. Numeric time series, aggregatable and cheap. Best for trends, dashboards, and alerting: how many requests, error rate, latency over time.

  2. 2

    Logs. Discrete, timestamped, detailed event records. Best for the specifics of what happened, the why, but expensive and noisy at scale.

  3. 3

    Traces. The path of a single request across services, broken into spans. Best for finding where time goes in a distributed system.

  4. 4

    They compose. Metrics tell you what and when, traces tell you where, logs tell you why. You move between them during an investigation.

What gets you hired

The 3 pillars are metrics, logs, and traces, and the key is that each is good at something different. Metrics are numeric time series, cheap to store and aggregate, often just a few bytes per data point at a 15 second scrape interval, so they are ideal for trends, dashboards, and alerting, things like request rate, error rate, and latency percentiles over time. Logs are discrete, timestamped, detailed event records, so they are where I find the specifics of what actually happened, the exception, the bad input, the why. They are also expensive and noisy at scale, easily orders of magnitude more costly per event than a metric, so I am deliberate, and I usually sample or keep debug logs to a 7 day retention while metrics live for a year. Traces follow a single request across services, broken into spans for each operation, which is what tells you where the time goes in a distributed system, for example that 180ms of a 200ms request was one downstream call. The important thing is that they compose rather than overlap. Metrics tell me what is wrong and when, a trace tells me where in the request path, and logs tell me why at that point. A real investigation moves between all 3, and modern tooling links them so I can jump from a metric spike to an exemplar trace to the relevant logs.

Names all three with their strengths Knows logs are expensive, metrics cheap, traces for distributed latency Knows they compose

Then they probe: Why not just use logs for everything?

Practise this one
Foundation

What should you measure first for a service? Explain the golden signals (or RED/USE).

What most people say

I would measure CPU, memory, and disk usage of the servers.

Those are resource metrics (part of USE) but not what users feel. The golden signals start with latency and errors of user requests; leading with CPU misses the symptoms that actually matter.

The structure behind a strong answer

  1. 1

    The four golden signals. From Google SRE: latency (how long requests take), traffic (how much demand), errors (rate of failures), and saturation (how full the system is).

  2. 2

    RED for request services. Rate, Errors, Duration, a simple lens for any request-driven service: how many, how many failed, how long.

  3. 3

    USE for resources. Utilization, Saturation, Errors, for resources like CPU, memory, disk: how busy, how queued/overloaded, how many errors.

  4. 4

    Start user-facing. Measure the user-facing signals first (latency and errors of the requests users make), because those map to actual experience, then go deeper.

What gets you hired

I start from a principled set rather than measuring everything. The classic is the 4 golden signals from Google SRE: latency, how long requests take, traffic, how much demand the system is under, errors, the rate of failing requests, and saturation, how full or close to its limits the system is. For request-driven services there is a simpler lens, RED, meaning Rate, Errors, and Duration, which I can apply to any endpoint on day one. For resources like CPU, memory, and disk there is USE, meaning Utilization, Saturation, and Errors. The key judgment is to start with the user-facing signals, the latency and error rate of the requests users actually make, because those map directly to experience and are what your SLOs get built on. My default first cut is a p99 latency target, say 300ms, and an error budget from a 99.9 percent availability SLO, which allows roughly 43 minutes of badness a month. Only then do I go deeper into resource saturation to explain the symptoms, and I will alert at something like 80 percent of a connection pool. So if someone tells me to measure CPU first, I would gently push back. CPU is a cause you investigate, but latency and errors are the symptoms that tell you whether users are actually being hurt.

Names the four golden signals Knows RED for services, USE for resources Starts with user-facing symptoms

Then they probe: What is the difference between RED and USE?

Practise this one
Junior

What are good logging practices for a distributed system?

What most people say

I would log important events and errors so I can look at them later.

It misses everything that makes logs usable at scale: structure for querying, correlation IDs to trace a request across services, centralization, and the discipline of not logging secrets or drowning in noise.

The structure behind a strong answer

  1. 1

    Structured logs. Log structured (for example JSON) with consistent fields, not free-form text, so logs are queryable and machine-parseable.

  2. 2

    Levels and signal. Use log levels (error/warn/info/debug) deliberately and avoid over-logging, noise drowns signal and drives up cost.

  3. 3

    Correlation IDs. Attach a request/correlation ID (and trace ID) to every log line so you can follow one request across services, which is essential in distributed systems.

  4. 4

    Centralize and protect. Ship logs to a central store (ELK, Loki, a cloud service) for search, and never log secrets or PII. Set retention to control cost.

What gets you hired

A few practices matter once you are past 1 service. First, structured logging: emit logs as records, typically JSON with consistent field names, so they are queryable instead of regex-scraped out of free text. Second, use levels deliberately, error, warn, info, debug, and resist over-logging. A chatty debug line on a hot path at 5,000 requests per second buries the signal and can add thousands of dollars a month in ingest and index cost. On one service, dropping 2 needless info lines per request cut our log volume by about 60 percent. Third, and especially in a distributed system, attach a correlation or request ID, and ideally the trace ID, to every line, so I can pull together every log for a single request as it crosses 6 or 7 services. Without that, distributed debugging is nearly impossible. Fourth, centralize: ship logs to an aggregation system like ELK, Loki, or a managed service so they are searchable in one place, and set sensible retention, often 30 days hot and cheaper cold storage after that, to control cost. And one hard rule: never log secrets or PII, because logs are widely accessible and long-lived. The goal is logs that are structured, correlated, centralized, secure, and deliberately scoped, not print statements I read later.

Structured logging + levels Correlation/trace IDs across services Centralized + no secrets/PII + retention

Then they probe: Why are correlation IDs essential in microservices?

Practise this one
Junior

What are the main metric types (counter, gauge, histogram), and when do you use each?

What most people say

You use counters to count things and gauges for values; metrics are just numbers over time.

It misses histograms entirely, which is how you measure latency percentiles. Tracking latency as a gauge or average hides the tail (p99) that actually reflects user pain.

The structure behind a strong answer

  1. 1

    Counter. A value that only goes up (resets on restart), like total requests or total errors. You rate() it to get per-second rates.

  2. 2

    Gauge. A value that goes up and down, like current memory use, queue depth, or active connections. A snapshot of now.

  3. 3

    Histogram. Buckets observations (like request durations) so you can compute percentiles (p95, p99) across instances. The right tool for latency.

  4. 4

    Summary vs histogram. A summary computes quantiles client-side but cannot be aggregated across instances; histograms aggregate server-side, which is usually what you want.

What gets you hired

There are three main types. A counter only ever increases (resetting on restart), so it is for cumulative things like total requests or total errors; you apply rate() to a counter to get a per-second rate. A gauge goes up and down and represents a current value, memory in use, queue depth, active connections, a snapshot of now. A histogram is the important one for latency: it buckets observations like request durations, which lets you compute percentiles such as p95 and p99 aggregated across all instances, and that matters because an average latency hides the tail where users actually suffer, p99 tells you the worst experiences. There is also a summary, which computes quantiles on the client side, but the catch is summaries cannot be aggregated across instances, so for something like overall p99 across a fleet you want a histogram, which Prometheus aggregates server-side. The practical mistake to avoid is tracking latency as an average or a gauge, you need a histogram to see the distribution and the tail.

Counter (up-only, rate it), gauge (up/down), histogram (buckets) Knows histograms give percentiles for latency Knows summary cannot aggregate across instances

Then they probe: Why do you need a histogram for latency rather than an average?

Practise this one
Junior

What is distributed tracing, and how does it work?

What most people say

Tracing follows a request through your system to see what it does.

Directionally right but missing the mechanics: spans, parent-child structure, and especially context propagation via headers, which is what makes a trace span multiple services. Without that you cannot explain how it works.

The structure behind a strong answer

  1. 1

    A trace is one request. A trace represents a single request as it flows through the whole system, end to end across services.

  2. 2

    Spans are operations. A trace is made of spans, each a timed operation (a service call, a DB query), with parent-child relationships forming the request tree.

  3. 3

    Context propagation. A trace ID and span ID are passed across service boundaries (usually via request headers), so each service attaches its spans to the same trace.

  4. 4

    Why and sampling. It shows exactly where time goes across services, the bottleneck span, which metrics and logs alone cannot. Sampling keeps volume and cost manageable.

What gets you hired

Distributed tracing follows a single request end to end as it moves through every service that handles it. A trace is made of spans, where each span is one timed operation, an incoming request, a downstream call, a database query, and spans have parent-child relationships that form a tree showing the structure and timing. The mechanism that makes it work across services is context propagation. When one service calls another it passes the trace ID and the current span ID along, usually in the W3C traceparent header, so the receiving service creates its spans as children under the same trace. Stitch those spans together and you get a waterfall of where the time went. That is why tracing is the pillar for distributed latency. Metrics told us p99 checkout latency had gone from 400ms to 1.2 seconds, and logs told us what happened inside one service, but the trace showed the actual culprit: a single downstream call fanning out into 30 sequential database queries, about 700ms of the total. Because tracing every request is expensive, I use sampling, head-based or tail-based, often keeping something like 1 percent of normal traffic while tail-sampling holds on to every error and every slow request, so cost stays sane and the interesting traces are still there.

Trace=one request, spans=operations with parent-child Explains context propagation via headers Knows tracing for distributed latency + sampling

Then they probe: How does a trace span multiple services?

Practise this one
Junior

How does Prometheus collect metrics, and what is the pull model?

What most people say

Applications send their metrics to Prometheus and it stores them.

That is the push model, which Prometheus does not use by default. It pulls by scraping /metrics endpoints. Getting this backwards means you misunderstand how targets, service discovery, and failed-scrape detection work.

The structure behind a strong answer

  1. 1

    Pull by scraping. Prometheus pulls: on an interval it scrapes an HTTP /metrics endpoint that each target exposes, rather than targets pushing to it.

  2. 2

    Service discovery. It uses service discovery (Kubernetes, cloud, file-based) to find what to scrape, so targets are picked up automatically as they come and go.

  3. 3

    Benefits of pull. A failed scrape is itself a signal the target is down, Prometheus controls the scrape rate centrally, and there is no need for every target to know where to send.

  4. 4

    The push exception. Short-lived batch jobs that may not exist at scrape time use the Pushgateway to push their metrics, the documented exception to the pull model.

What gets you hired

Prometheus is pull-based. Rather than applications pushing metrics to it, Prometheus periodically scrapes an HTTP endpoint, conventionally /metrics, that each target exposes in the Prometheus exposition format. The default scrape interval is 15 seconds, and I tune that per job. It uses service discovery, Kubernetes, cloud APIs, or static and file config, to know what to scrape, so as pods come and go across a fleet of a few hundred they are added or dropped automatically without anyone editing config. The pull model has real benefits. A failed scrape is itself a health signal, so if Prometheus cannot reach a target you already know something is wrong, and the built-in up metric goes to 0, which you get for free. Prometheus controls the interval centrally instead of trusting each app to push correctly. And targets never need to know the address of the monitoring system. The one documented exception is short-lived batch jobs that might finish in 5 seconds, well before any 15 second scrape lands. Those push to a Pushgateway, which Prometheus then scrapes as a normal target. So my rule is simple: pull by scraping for anything long-lived, and the Pushgateway only for genuinely ephemeral jobs.

Knows Prometheus pulls/scrapes /metrics Knows service discovery + failed-scrape-as-signal Knows Pushgateway for batch jobs

Then they probe: What is a benefit of pull over push for monitoring?

Practise this one
Mid

You are setting up monitoring for a web service on Azure. What do you actually monitor, and why? Use a method, not a wishlist.

What most people say

I would turn on Azure Monitor and set up alerts on CPU and memory so I know when the servers are stressed.

CPU and memory are resource clues, not user-facing signals, a service can be failing users with healthy CPU, or have high CPU while serving fine. There is no method, no error rate or latency, and alerting on raw CPU produces noise that trains people to ignore the pager.

The structure behind a strong answer

  1. 1

    Anchor on a method. RED for request-driven services: Rate, Errors, Duration. USE for resources: Utilization, Saturation, Errors. The method stops the "monitor everything" wishlist.

  2. 2

    RED at the service edge. Rate (requests/sec), Errors (failure rate, especially 5xx), and Duration (latency, watch p95/p99 not the average). These map directly to what users feel.

  3. 3

    USE for the resources behind it. For each resource (CPU, memory, disk, connection pool): Utilization, Saturation (queue depth, throttling), and Errors. This finds the cause once RED shows a symptom.

  4. 4

    Wire it into Azure tooling. Azure Monitor with Application Insights for app-level RED (requests, failures, dependency calls, distributed traces), and platform metrics/Log Analytics for USE on the underlying resources.

  5. 5

    Alert on symptoms, not noise. Alert on user-facing SLOs (error rate, latency, error-budget burn), not raw CPU. CPU is a clue you look at after a symptom alert fires, not a pager-worthy event by itself.

What gets you hired

I use a method so I am not just listing metrics. RED for the service: Rate, Errors, and Duration, request volume, failure rate especially 5xx, and latency at p95/p99 rather than the average, because those map straight to what users feel. USE for the resources behind it: for CPU, memory, disk, and the connection pool I watch Utilization, Saturation like queue depth or throttling, and Errors, which is how I find the cause once RED shows a symptom. On Azure I wire RED through Application Insights, requests, failures, dependency calls, and distributed traces, and use Azure Monitor platform metrics and Log Analytics for the USE side. Crucially I alert on symptoms, user-facing SLOs like error rate, latency, and error-budget burn, not on raw CPU, because CPU is a clue I look at after a symptom alert fires, not something worth paging on by itself.

Leads with a method (RED and USE) Watches p95/p99 latency, not just averages Alerts on user-facing symptoms, not raw CPU

Then they probe: Your CPU alert fires constantly but users never notice. What is wrong with the alert?

Practise this one
Mid

Explain SLI, SLO, SLA, and error budgets, and how they relate.

What most people say

SLAs, SLOs, and SLIs are all about uptime targets you promise to customers.

It blurs all three and misses error budgets entirely. The point is the hierarchy (measured SLI, internal SLO target, contractual SLA) and that the error budget is a decision tool, not just a number.

The structure behind a strong answer

  1. 1

    SLI. A Service Level Indicator is a measured signal of health, for example the percentage of successful requests or the fraction served under 300ms.

  2. 2

    SLO. A Service Level Objective is the target for an SLI, for example 99.9% of requests succeed over 30 days. It is your internal reliability goal.

  3. 3

    SLA. A Service Level Agreement is a contractual promise to customers with consequences (refunds, penalties). SLOs are usually stricter than the SLA.

  4. 4

    Error budget. The allowed unreliability, 1 minus the SLO (a 99.9% SLO gives 0.1% budget). You spend it on change/velocity, and when it is exhausted you slow risky releases and prioritize reliability.

What gets you hired

They form a hierarchy. An SLI, Service Level Indicator, is something you actually measure about health, like the percentage of successful requests or the fraction served under a latency threshold. An SLO, Service Level Objective, is the target you set for that SLI, say 99.9% of requests succeed over a rolling 30 days, it is your internal reliability goal. An SLA, Service Level Agreement, is a contractual commitment to customers with real consequences like refunds or penalties if you miss it, and you almost always set your SLO stricter than your SLA so you have margin before breaching the contract. The piece that ties it together and is the most useful in practice is the error budget: it is the allowed amount of unreliability, one minus the SLO, so a 99.9% SLO gives a 0.1% error budget. That turns reliability into a decision tool. When you have budget left, you can spend it on velocity, ship faster, take more risk; when you have burned through it, that is the signal to slow down risky changes and invest in reliability and bug fixing. It also drives alerting: instead of alerting on every blip, you alert on the rate at which you are burning the error budget. So SLIs measure, SLOs target, SLAs commit, and the error budget is how you balance shipping speed against stability.

Defines SLI/SLO/SLA distinctly + hierarchy Explains error budget = 1 - SLO Frames error budget as a velocity/stability decision tool

Then they probe: Why set your SLO stricter than your SLA?

Practise this one
Mid

What makes a good alerting strategy, and how do you avoid alert fatigue?

What most people say

I would set up alerts on CPU, memory, and disk so I know when something is wrong.

Alerting on every resource metric is exactly how you get fatigue: most fire without user impact and get ignored. Good alerting pages on user-facing symptoms (SLO burn), and each alert must be actionable.

The structure behind a strong answer

  1. 1

    Alert on symptoms, not causes. Page on what affects users, SLO violations like high error rate or latency, not on every internal cause (a single high-CPU box that users do not feel).

  2. 2

    Every alert is actionable. An alert should mean a human needs to do something now. If there is no action, it should not page, it is a dashboard or a ticket.

  3. 3

    Page vs ticket. Reserve paging for urgent, user-impacting, actionable issues; route non-urgent things to tickets or dashboards so the pager stays meaningful.

  4. 4

    Fight fatigue. Noisy or non-actionable alerts get ignored, which is dangerous. Tune thresholds, use burn-rate alerts on SLOs, group and dedupe, and attach runbooks so each page is clear.

What gets you hired

The core principle is to alert on symptoms, not causes. I page on things that actually affect users, an SLO being violated, error rate spiking, latency breaching its target, not on every internal cause like a single node with high CPU that users never feel. Alerting on causes is how you drown in pages. The second principle is that every alert must be actionable: a page should mean a human needs to do something right now, and if there is no action to take, it does not belong as a page, it is a dashboard panel or at most a ticket. That leads to the page-versus-ticket distinction, reserve paging for urgent, user-impacting, actionable problems, and route everything non-urgent to tickets or dashboards, so the pager stays meaningful and people trust it. And I actively fight alert fatigue, because noisy or false alerts get ignored and then a real one is missed, which is genuinely dangerous: I tune thresholds, prefer SLO burn-rate alerts over raw thresholds so I alert on sustained user impact rather than momentary blips, group and deduplicate related alerts, and attach a runbook to each alert so whoever is paged knows what to do. The test I apply is: if this fires at 3am, is there something a human must do, if not, it should not page.

Alerts on user-facing symptoms/SLOs Insists every alert is actionable Page vs ticket + burn-rate + runbooks to fight fatigue

Then they probe: Why alert on symptoms rather than causes?

Practise this one
Mid

What is metric cardinality, and why are high-cardinality labels dangerous?

What most people say

Cardinality is how many metrics you have; more is fine as long as you have storage.

"More is fine" is the dangerous misconception. Each label-value combo is a separate time series, so an unbounded label like user ID can create millions of series and take down Prometheus. High cardinality is a top operational hazard, not a storage detail.

The structure behind a strong answer

  1. 1

    Cardinality is unique series. Each unique combination of a metric and its label values is a separate time series. Cardinality is the total number of those series.

  2. 2

    High-cardinality labels explode it. A label with unbounded values, user ID, email, request ID, full URL, multiplies the series count enormously, since every distinct value creates new series.

  3. 3

    The damage. It blows up memory and storage, slows queries, and can crash the metrics backend (Prometheus is memory-hungry per series). It is a common cause of monitoring outages.

  4. 4

    The fix. Keep metric labels low-cardinality and bounded (status code, method, route template). Put high-cardinality detail (user ID, request ID) in logs and traces, not metric labels.

What gets you hired

Cardinality is the number of unique time series, and the key fact is that every distinct combination of a metric name and its label values is its own series. Add a label and the series count multiplies by the number of values it can take, so 5 status codes times 4 methods times 20 routes is 400 series, which is completely fine because those labels are bounded. It is dangerous for high-cardinality labels whose values are effectively unbounded, user ID, email, request ID, a full URL with IDs in it, session ID. Put user ID on a metric in a product with 2 million users and you have just asked for 2 million series. The damage is real. Prometheus holds series in memory, roughly a couple of kilobytes each, so a million new series is several gigabytes of extra RAM, queries slow down or time out, and the backend can be OOM killed outright, which is how a single bad label takes down the monitoring system right when you need it. The fix is discipline. Keep labels low-cardinality and bounded, status code, method, route template rather than the raw path with IDs in it, and push the high-cardinality detail, user ID and request ID, into logs and traces, which are built for it. I treat every new label as a cardinality decision, not a free addition.

Knows each label combo = a series Knows unbounded labels explode series/memory Puts high-cardinality data in logs/traces not labels

Then they probe: Why is putting a user ID in a metric label a bad idea?

Practise this one
Mid

What is OpenTelemetry, and why does it matter?

What most people say

OpenTelemetry is a tool for collecting traces from your application.

It undersells it: OTel covers all three signals (not just traces), is a vendor-neutral standard plus a Collector, and its whole point is instrument-once, export-anywhere to avoid lock-in. That breadth is the answer.

The structure behind a strong answer

  1. 1

    A vendor-neutral standard. OpenTelemetry (OTel) is an open standard and set of SDKs for generating telemetry, metrics, logs, and traces, with a common data model and context.

  2. 2

    Instrument once, export anywhere. You instrument your code once with OTel, then export to any compatible backend (Prometheus, Jaeger, a cloud or commercial vendor) without re-instrumenting.

  3. 3

    The Collector. The OTel Collector receives, processes, and routes telemetry, so you can switch or fan out to multiple backends centrally without touching app code.

  4. 4

    Why it matters. It avoids vendor lock-in, standardizes instrumentation across services and languages, and unifies the three signals with shared context (a trace ID linking logs, metrics, and traces).

What gets you hired

OpenTelemetry, OTel, is a vendor-neutral open standard and a set of SDKs for generating telemetry across all 3 signals, traces, metrics, and logs, with a shared data model and context format. The central value is instrument once, export anywhere. I add OTel instrumentation to a service one time and can then send that telemetry to any compatible backend, Prometheus, Jaeger, or a commercial or cloud vendor, without re-instrumenting when the tooling changes. The OTel Collector sits in the middle, receiving, processing, batching, filtering and scrubbing, then routing telemetry, so I can swap a backend or fan out to 2 of them from one config file without touching a line of application code. Why it matters is threefold. It removes vendor lock-in, which used to be a real trap when every observability vendor shipped its own agent and SDK, and re-instrumenting 60 services to switch tools is a project nobody funds. It standardizes instrumentation across many services and languages, so everything looks consistent. And because the signals share context, the same W3C trace ID, a 32 character hex value, flows through logs, metrics, and traces, so I can pivot from a spiking error rate to the exact request. OTel is now the default expectation for new instrumentation because it decouples how you produce telemetry from where you analyse it.

Vendor-neutral standard for all three signals Instrument-once, export-anywhere Knows the Collector + avoiding lock-in + shared context

Then they probe: How does OpenTelemetry help avoid vendor lock-in?

Practise this one
Mid

Latency for an endpoint suddenly spiked. How do you use observability to find the cause?

What most people say

I would look at the logs to see what is going wrong.

Jumping straight to grepping logs without first scoping the symptom in metrics and localizing with traces is slow and unfocused. The three pillars work together: metrics for what/when, traces for where, logs for why.

The structure behind a strong answer

  1. 1

    Confirm the symptom in metrics. Start at the dashboards: which endpoint, when did it start, how bad (p95/p99), and is it errors or pure latency? Pin the blast radius and the start time.

  2. 2

    Correlate with changes and saturation. Line the spike up against deploys, traffic changes, and resource saturation (CPU, memory, connections, queue depth). A spike at a deploy time is a strong lead.

  3. 3

    Localize with traces. Pull traces for the slow requests and read the waterfall: which service or span is eating the time, the database, a downstream call, a lock.

  4. 4

    Find the why in logs. Drill into that service logs for the same time window, filtered by the trace/correlation ID, to see the actual cause: slow queries, errors, retries, a misconfiguration.

What gets you hired

I work the three pillars in order rather than guessing. First I confirm and scope the symptom in metrics: which endpoint, exactly when it started, how severe in p95 and p99, and whether it is accompanied by errors or it is pure latency, that tells me the blast radius and gives me a precise time window. Second, I correlate that window with changes and saturation: did it start at a deploy, a config change, or a traffic surge, and is anything saturated, CPU, memory, connection pools, queue depth, a downstream dependency. A spike that begins right at a deploy time is a very strong lead. Third, I localize with traces: I pull traces for the slow requests and read the waterfall to see which service and which span is consuming the time, often it is a database query, a slow downstream call, or lock contention, and the trace tells me where without guessing. Fourth, I find the why in logs: I go to that specific service logs for the same window, filtered by the trace or correlation ID, to see the actual cause, slow queries, a flood of retries, errors, a bad config. So metrics tell me what and when, traces tell me where, and logs tell me why, and using them together is far faster than grepping logs blind.

Uses metrics->traces->logs in order Correlates with deploys/saturation Localizes with traces before reading logs

Then they probe: Why not just start with the logs?

Practise this one
Senior

How would you design observability for a microservices platform?

What most people say

I would install Prometheus and Grafana and add some dashboards.

Tools are not a strategy. A microservices platform needs consistent instrumentation (OTel + context propagation), correlated signals, per-service SLOs and symptom alerting, and deliberate cost control, not just a dashboarding stack bolted on.

The structure behind a strong answer

  1. 1

    Standardize instrumentation. Adopt OpenTelemetry across all services and languages so telemetry is consistent, and propagate trace context everywhere so requests are traceable end to end.

  2. 2

    All three signals, correlated. RED metrics per service plus the golden signals, structured logs with correlation/trace IDs, and distributed traces, all sharing context so you can pivot between them.

  3. 3

    SLOs and symptom alerting. Define SLOs per service (or per user journey), build dashboards on the golden signals, and alert on SLO burn (symptoms), with runbooks, not on every cause.

  4. 4

    Cost and consistency. Control cost with trace sampling, log levels/retention, and bounded metric cardinality; enforce consistency with shared libraries/templates so every service is observable the same way.

What gets you hired

I would design it as a platform capability, not a tool install. First, standardize instrumentation: adopt OpenTelemetry across every service and language so telemetry is consistent rather than a per-team snowflake, and make trace-context propagation mandatory so any 1 request can be followed end to end across 20 or 30 service hops. Second, cover all three signals and keep them correlated: RED metrics per service plus the golden signals for the user-facing view, structured logs carrying the correlation and trace ID, and distributed traces, all sharing context so an engineer can pivot from a p99 spike, say 200ms jumping to 2 seconds, straight to the exemplar trace and then the exact logs. Third, make it actionable: define SLOs per service and ideally per critical user journey, something like 99.9 percent availability, build dashboards on the golden signals, and alert on SLO burn rate, symptoms, with a runbook attached to every alert, rather than paging on every internal cause. Fourth, treat cost and consistency as first-class: control spend with trace sampling, often 1 to 5 percent of traces plus all errors, sensible log levels and retention, maybe 30 days hot, and bounded metric cardinality, and enforce consistency through shared instrumentation libraries and a paved-road service scaffold, so every new service is observable the same way by default. The goal is that any engineer can debug any service quickly, and that it does not bankrupt us in storage.

Standardizes on OTel + context propagation Correlated three signals + per-service SLOs + symptom alerting Plans cost (sampling/retention/cardinality) and consistency

Then they probe: How do you keep observability consistent across many teams and services?

Practise this one
Senior

How do you run on-call and incident response well, and reduce MTTR?

What most people say

I would make sure someone is on call to fix things quickly when they break.

Just having someone on call is heroics, not a system. Low MTTR comes from fast detection (good alerts/dashboards), fast diagnosis (runbooks, correlated telemetry), sustainable rotations, and blameless postmortems that prevent recurrence.

The structure behind a strong answer

  1. 1

    Detect fast (MTTD). Symptom-based, SLO-burn alerting and good golden-signal dashboards so problems are caught quickly and accurately, with low noise so alerts are trusted.

  2. 2

    Diagnose fast. Runbooks attached to every alert, correlated telemetry to jump metrics-to-traces-to-logs, and clear ownership/escalation so the right person is engaged quickly.

  3. 3

    Sustainable on-call. Reasonable rotations, manageable alert volume (fight fatigue), clear severities, and well-defined roles (incident commander, comms) during a major incident.

  4. 4

    Learn from it. Blameless postmortems that find systemic causes and produce concrete action items, and track MTTR/MTTD over time so you measure whether you are improving.

What gets you hired

Reducing mean-time-to-recovery is about making each phase fast and the whole thing sustainable, not relying on heroics. Detection: I want symptom-based, SLO-burn alerting and clear golden-signal dashboards so issues are caught quickly and accurately, and critically low-noise so people trust the pager, because alert fatigue directly hurts detection. Diagnosis: every alert links to a runbook so the on-call person is not starting from scratch at 3am, and the telemetry is correlated so they can pivot from the alerting metric to the relevant traces to the exact logs in seconds; clear ownership and escalation paths get the right expert engaged fast. During a major incident I use defined roles, an incident commander to coordinate and someone owning communications, so responders can focus on the fix. Sustainability matters because burned-out on-call is slow and error-prone: reasonable rotation sizes, manageable alert volume, and clear severity levels so not everything is a page. And then learning: blameless postmortems that look for systemic causes rather than blaming an individual, each producing concrete, owned action items, plus tracking MTTR and MTTD over time so I can actually tell whether the system is getting better. So observability feeds detection and diagnosis, and the operational practices, runbooks, roles, sustainable rotations, and postmortems, turn that into consistently low MTTR.

Connects observability to detection/diagnosis Runbooks + correlated telemetry + roles (incident commander) Blameless postmortems + tracks MTTR/MTTD + sustainable on-call

Then they probe: Why are blameless postmortems important?

Practise this one

Azure

14 questions · Foundation, Junior, Mid, Senior
Foundation

What is Microsoft Entra ID (formerly Azure AD), and how does authentication work in Azure?

What most people say

Entra ID is just the new name for Active Directory in the cloud.

Calling it cloud AD is the common misconception. Entra ID is a token-based (OAuth2/OIDC) cloud identity service, architecturally different from on-prem AD with its LDAP and Kerberos. Conflating them leads to wrong assumptions about how auth works.

The structure behind a strong answer

  1. 1

    The identity provider. Microsoft Entra ID (renamed from Azure AD) is Azure cloud identity and access management service: it holds identities and authenticates them.

  2. 2

    What it holds. Identities include users, groups, and application identities, service principals and managed identities, all living in a tenant (the directory).

  3. 3

    Token-based auth. Authentication is modern and token-based over HTTPS using OAuth2 and OIDC: Entra verifies the identity and issues JWT access and ID tokens that services validate.

  4. 4

    Not on-prem AD. It is not the same as Windows Server Active Directory (no LDAP/Kerberos domain controllers). It is a cloud, internet-facing identity service, though you can sync from on-prem AD.

What gets you hired

Microsoft Entra ID, which was renamed from Azure AD, is Azure cloud identity and access management service, it is the identity provider that everything in Azure authenticates against. It holds identities of several kinds: users and groups, and application identities, namely service principals and managed identities, all within a tenant, which is the Entra directory and the top-level identity boundary for an organization. Authentication is modern and token-based: it uses OAuth2 and OpenID Connect over HTTPS, so when an identity signs in, Entra verifies it, applies any policies, and issues JWT tokens, an ID token for who you are and access tokens for calling APIs, which the target service validates. The important nuance, and a common interview trap, is that Entra ID is not just on-prem Active Directory in the cloud. Classic AD is an LDAP and Kerberos domain service for a local network; Entra ID is an internet-facing, REST and token-based identity service. They solve related problems differently, and you can synchronize on-prem AD into Entra for hybrid identity, but they are not the same system.

Knows Entra ID = cloud identity provider Token-based OAuth2/OIDC Knows it is not on-prem AD

Then they probe: How is Entra ID different from on-prem Active Directory?

Practise this one
Foundation

In Azure, what handles authentication versus authorization?

What most people say

Entra ID handles both logging in and permissions for everything in Azure.

It collapses two systems. Entra authenticates and manages directory roles, but access to Azure resources is governed by Azure RBAC, a separate authorization system. Missing that distinction causes real permissions confusion.

The structure behind a strong answer

  1. 1

    Entra ID authenticates. Entra ID handles authentication: it proves who you are and issues a token. It does not decide what you can do with a resource.

  2. 2

    Azure RBAC authorizes. Azure role-based access control handles authorization for Azure resources: given your authenticated identity, what actions you may perform on which resources.

  3. 3

    Two different role systems. Entra (directory) roles like Global Administrator govern identity/directory itself; Azure RBAC roles like Contributor govern resources. They are separate and easy to confuse.

  4. 4

    The flow. You authenticate to Entra, get a token, then Azure checks RBAC at the resource scope to authorize the action.

What gets you hired

They are 2 distinct responsibilities. Entra ID handles authentication. It proves who you are and issues a token, typically an access token valid for about 1 hour, but on its own it does not decide what you are allowed to do with a given resource. Azure RBAC, role-based access control, handles authorization for Azure resources: given your authenticated identity, it determines what actions you can perform on which resources, scoped to a management group, a subscription, a resource group, or an individual resource. The subtlety that trips people up is that there are actually 2 separate role systems. Entra directory roles, like Global Administrator or User Administrator, govern the identity and directory plane itself, managing users, app registrations, and so on. Azure RBAC roles, like Owner, Contributor, and Reader, govern access to Azure resources. They are different systems with different scopes, so being a Global Admin in Entra does not by itself give you rights over resources, and the reverse is true too. The flow, then, is that you authenticate to Entra and get a token, and Azure evaluates RBAC at the resource scope to authorize each individual action. One practical note: RBAC changes are eventually consistent and can take a few minutes to take effect, so I do not panic if a fresh assignment is not live on the first try.

Entra = authN, Azure RBAC = authZ Knows directory roles vs RBAC roles are separate Describes token-then-RBAC flow

Then they probe: What is the difference between an Entra directory role and an Azure RBAC role?

Practise this one
Foundation

Explain the Azure resource hierarchy: tenant, management group, subscription, resource group.

What most people say

You have subscriptions and resource groups that contain your resources.

It names the middle two but misses the tenant (identity boundary) and management groups (where org-wide policy/RBAC are applied), and says nothing about inheritance, which is the governance-relevant part.

The structure behind a strong answer

  1. 1

    Tenant. The Entra directory, the top-level identity boundary for an organization. Subscriptions trust a tenant for identity.

  2. 2

    Management groups. Containers above subscriptions for organizing many subscriptions and applying policy/RBAC broadly (for example by business unit or environment).

  3. 3

    Subscription. A billing and access boundary: resources are billed per subscription and it is a common unit for separating environments or teams.

  4. 4

    Resource group. A lifecycle container for related resources you deploy, manage, and delete together. Resources live in exactly one resource group.

What gets you hired

It is a nested hierarchy with 4 levels, and the key is that access and policy flow down it. At the top is the tenant, which is the Entra directory and the identity boundary for the organization, and subscriptions trust exactly 1 tenant for authentication. Below that are management groups, containers that sit above subscriptions so you can organize many subscriptions, by business unit or environment for example, and apply Azure Policy and RBAC broadly in one place. You can nest them up to 6 levels deep below the root. Then subscriptions, which are both a billing boundary, because costs are tracked per subscription, and an access boundary, which makes them a common way to give teams or environments hard isolation. Finally resource groups, lifecycle containers for related resources you deploy and manage together, and a resource belongs to exactly 1 resource group. The reason the hierarchy matters beyond tidiness is inheritance. An RBAC assignment or an Azure Policy applied at a management group or subscription flows down to everything beneath it, so I set broad guardrails high up, an allowed-regions policy at the management group say, and narrow, specific grants low down, like Contributor on a single resource group. That is the backbone of Azure governance.

Names all four levels correctly Knows tenant = identity boundary, subscription = billing/access Knows RBAC/policy inherit down

Then they probe: Why do RBAC assignments and policies applied high in the hierarchy matter?

Practise this one
Junior

How does Azure RBAC work? Explain roles, scopes, and assignments.

What most people say

You assign roles like Owner or Contributor to users to give them access.

It names roles but not scope, the third essential part, or inheritance, and defaults toward broad roles. RBAC is who plus role plus scope, and least privilege means the narrowest role at the narrowest scope.

The structure behind a strong answer

  1. 1

    Role definition. A role is a set of allowed actions (and not-actions), built-in like Owner, Contributor, Reader, or a custom role you define for finer control.

  2. 2

    Scope. The level the role applies at: management group, subscription, resource group, or a single resource. Assignments inherit to everything below the scope.

  3. 3

    Role assignment. An assignment binds a security principal (user, group, service principal, or managed identity), a role, and a scope. That triple is the grant.

  4. 4

    Least privilege. Assign the narrowest role at the narrowest scope that works, prefer Reader/Contributor over Owner, and assign to groups, not individuals, for manageability.

What gets you hired

An Azure RBAC grant is always 3 parts: a role, a scope, and a principal. The role definition is a set of permitted actions. Most of the time I use built-in roles, and there are a few hundred of them, but the 3 that come up constantly are Owner, which is full control including granting access, Contributor, which manages resources but cannot grant access, and Reader, which is view only. I write a custom role when I need something finer than the built-ins offer. The scope is the level the role applies at: management group, subscription, resource group, or an individual resource. Crucially an assignment inherits downward, so one Contributor assignment at a resource group covers all 20 resources inside it. The role assignment ties it together, binding a security principal, a user, a group, a service principal, or a managed identity, to a role at a scope. For least privilege I assign the narrowest role at the narrowest scope that still lets the principal do its job, I avoid handing out Owner when Contributor or a custom role is enough, and I assign to groups rather than individuals so access stays manageable and auditable as people join and leave. So when someone asks me to give an app access, I think: which principal, the minimal role, the tightest scope.

Grant = principal + role + scope Knows scope inheritance + built-in vs custom Least privilege, assigns to groups

Then they probe: What is the difference between Owner and Contributor?

Practise this one
Junior

What happens when a user signs in to an Azure/Entra-protected application?

What most people say

The user types their password and Entra logs them in.

It ignores the whole OIDC flow, the tokens issued, and where MFA and Conditional Access apply. The interviewer wants the redirect-verify-token-validate flow, not just password in.

The structure behind a strong answer

  1. 1

    Redirect to Entra. The app redirects the user to Entra ID to authenticate (OpenID Connect), rather than handling the password itself.

  2. 2

    Verify identity. Entra validates the credentials and applies controls: MFA if required, and Conditional Access policies evaluating signals like device, location, and risk.

  3. 3

    Issue tokens. On success Entra issues JWT tokens: an ID token (who the user is) and access tokens (to call APIs), back to the app.

  4. 4

    App validates and authorizes. The app validates the token signature and claims, then authorizes the user based on roles/groups in the token or Azure RBAC for resources.

What gets you hired

Modern sign-in is a delegated, token-based flow, and the app never handles the password. When the user hits a protected app, the app redirects them to Entra ID using OpenID Connect. Entra authenticates the user and applies its controls right there: multi-factor authentication if it is required, and Conditional Access policies that evaluate signals, who the user is, the device state, the location, and the sign-in risk, to allow, block, or demand extra verification. On success, Entra issues signed JWT tokens back to the app: an ID token describing who the user is, and access tokens the app or its APIs use to call downstream resources. Those access tokens are short-lived, about 60 to 90 minutes by default, and the refresh token quietly gets a new one so the user is not prompted again. The app validates the token, checking the signature against Entra's published keys and the claims like audience and expiry, then authorizes from the roles or group claims inside it, or via Azure RBAC if it is acting on Azure resources. So the chain is redirect to Entra, verify with MFA and Conditional Access, issue tokens, then the app validates and authorizes. That is why credentials live in exactly 1 place, the identity provider, and apps simply trust a validated token.

Describes the OIDC redirect/token flow Knows app never sees the password Places MFA + Conditional Access correctly

Then they probe: Where does Conditional Access fit in the sign-in?

Practise this one
Junior

What is the difference between a service principal and a managed identity, and why prefer managed identities?

What most people say

A managed identity is just an easier service principal; they are basically the same.

It misses the whole point: a managed identity has no credentials you handle, the platform manages and rotates them, so there is no secret to leak. That is the security win, not just convenience.

The structure behind a strong answer

  1. 1

    Service principal. An identity in Entra for an application or service, with credentials (a client secret or certificate) that you create, store, and must rotate yourself.

  2. 2

    Managed identity. A special service principal whose credentials are fully managed by Azure: nothing for you to store or rotate. The platform issues and rotates them automatically.

  3. 3

    System vs user-assigned. System-assigned is tied to one resource lifecycle (deleted with it); user-assigned is a standalone identity you can share across multiple resources.

  4. 4

    Why prefer managed. No secret in code or config means nothing to leak, and no manual rotation. The app gets a token from the local metadata endpoint at runtime.

What gets you hired

A service principal is the identity an application uses in Entra ID, and in its basic form it has credentials, a client secret or a certificate, that you create, store somewhere, and are responsible for rotating. A client secret in the portal defaults to a 6 month or 12 month expiry, so somebody has to remember to roll it, and every one of those secrets is a thing that can leak. A managed identity is a special kind of service principal where Azure fully manages the credentials: there is nothing for you to store or rotate, the platform provisions the certificate and rotates it automatically, roughly every 45 days, with no action from me. There are two flavors. System-assigned is tied to the lifecycle of a single resource and is deleted when that resource is. User-assigned is a standalone identity I create once and attach to many resources, which is what I reach for when 5 or 6 services need the same permissions. I prefer managed identities for anything running on Azure because they remove stored secrets entirely. The app just asks the local instance metadata endpoint at 169.254.169.254 for a token at runtime and uses it, so there is no client secret in code or config to leak. I only fall back to a service principal with a secret or certificate for workloads outside Azure that cannot use a managed identity.

Managed identity = platform-managed credentials, none to store/rotate Knows system vs user-assigned Knows the no-secret security benefit

Then they probe: When would you use a user-assigned managed identity over system-assigned?

Practise this one
Junior

What is Azure Key Vault, and how should an application access a secret from it securely?

What most people say

Key Vault stores your secrets, and the app reads them using a key or connection string.

Using a key or connection string to read Key Vault recreates the original problem: a bootstrap secret you must store. The correct pattern is the app authenticating with its managed identity, so nothing is stored.

The structure behind a strong answer

  1. 1

    What it stores. Key Vault is a managed service for secrets (passwords, connection strings), cryptographic keys, and certificates, with access control and audit logging.

  2. 2

    Access control. Access is governed by Azure RBAC (recommended) or legacy vault access policies, so you grant a principal a role like Key Vault Secrets User scoped to the vault.

  3. 3

    Authenticate with managed identity. The app uses its managed identity to authenticate to Key Vault and pull the secret at runtime, so there is no secret stored in code or config to bootstrap.

  4. 4

    Protection features. Soft-delete and purge protection prevent accidental or malicious permanent deletion, and access is logged for audit.

What gets you hired

Azure Key Vault is a managed service for storing secrets like passwords and connection strings, plus cryptographic keys and certificates, with centralized access control, versioning, and audit logging. Access is governed by Azure RBAC, which is the recommended model now, or the older vault access policies, so I grant a specific principal one scoped role, usually Key Vault Secrets User, rather than blanket access. The crucial part is how the app authenticates, because storing a Key Vault credential in config would just move the secret one hop and defeat the whole point. The right pattern is the managed identity: I give the workload a managed identity, grant that identity Secrets User on the vault, and the app pulls the secret at runtime via DefaultAzureCredential, with zero bootstrap secret stored anywhere. I also cache the fetched secret in memory rather than calling on every request, because the vault is throttled at a few thousand transactions per 10 seconds and a hot path will hit that. On top of that I turn on soft delete, which is on by default with a 90 day retention window, and purge protection, so a deleted secret can be recovered and cannot be permanently wiped by an attacker. Then I keep the diagnostic logs for auditing. Key Vault plus a managed identity is how you get secrets out of code entirely.

Stores secrets/keys/certs with RBAC + audit App authenticates via managed identity (no stored secret) Knows soft-delete/purge protection

Then they probe: Why is accessing Key Vault with a managed identity better than a connection string?

Practise this one
Mid

What is Conditional Access in Entra ID, and how does it support a Zero Trust model?

What most people say

Conditional Access lets you require MFA for users when they log in.

MFA is one control, but Conditional Access is a broader signal-driven engine (device, location, risk -> block/MFA/compliant device). Reducing it to "require MFA" misses how it enforces Zero Trust contextually.

The structure behind a strong answer

  1. 1

    A policy engine. Conditional Access evaluates each sign-in against policies of the form: if these conditions hold, then enforce these controls.

  2. 2

    The signals. Conditions use signals like user/group, application, device state (compliant/managed), location/IP, and real-time sign-in risk from Identity Protection.

  3. 3

    The controls. Controls include block, require MFA, require a compliant or hybrid-joined device, or limit the session, applied at the moment of sign-in.

  4. 4

    Zero Trust fit. It replaces implicit trust (inside the network = trusted) with explicit, contextual verification on every access, the verify-explicitly principle of Zero Trust.

What gets you hired

Conditional Access is the policy engine in Entra ID that evaluates every single sign-in and enforces controls based on context. The model is essentially if these conditions are met, then apply these controls. The conditions draw on a rich set of signals, which user or group, which application, device state such as whether it is compliant or managed, the location or IP range, and the real-time sign-in risk that Identity Protection scores as low, medium, or high from anomalous behaviour like impossible travel. Based on those the policy applies controls, block access outright, require multi-factor authentication, require a compliant or hybrid-joined device, or limit the session, for example forcing reauthentication every 8 hours instead of trusting a long-lived token. Why it matters is that this is how Azure implements Zero Trust on the identity plane. Instead of the old assumption that sitting inside the corporate network makes you trusted, Conditional Access verifies explicitly on every access using current context, so a sign-in from an unmanaged device or a risk score of medium or above gets forced through MFA or blocked, while a low-risk sign-in from a compliant device flows straight through. That per-sign-in contextual evaluation is exactly the verify explicitly principle, and MFA is only 1 of the controls it can require. I roll new policies out in report-only mode for about 2 weeks first so I can see who they would have blocked.

Frames it as a signal-driven if-then engine Names signals (device, location, risk) and controls Connects to Zero Trust / verify explicitly

Then they probe: Give an example Conditional Access policy.

Practise this one
Mid

An app running on Azure (a VM or AKS pod) needs to read a database password from Key Vault with no stored credentials. How do you set it up?

What most people say

I would store the Key Vault access key as an environment variable so the app can read the secret.

An access key in an env var is exactly the stored credential the question rules out, it just relocates the secret. The right pattern is a managed identity authenticating to Key Vault with no stored credential at all.

The structure behind a strong answer

  1. 1

    Give the workload an identity. Assign a managed identity to the workload: a system- or user-assigned identity on the VM, or workload identity (federated) for an AKS pod via its service account.

  2. 2

    Grant least-privilege access. Grant that identity a scoped role on the Key Vault, Key Vault Secrets User on that specific vault, nothing broader.

  3. 3

    Fetch at runtime. The app authenticates with the managed identity (for example DefaultAzureCredential) and reads the secret from Key Vault at startup or on demand, no secret in code or config.

  4. 4

    Operate it. Cache and refresh the secret sensibly, rotate the secret in Key Vault (the app picks up the new version), and rely on Key Vault audit logs.

What gets you hired

I wire identity end to end so there is no stored credential anywhere. First, give the workload a managed identity. On a VM that is a system-assigned or user-assigned managed identity, and for an AKS pod I use workload identity, which federates the pod's Kubernetes service account to an Entra identity so the pod gets tokens without a single secret on disk. Second, grant that identity least privilege on the vault, the Key Vault Secrets User role scoped to that 1 specific Key Vault, not a subscription-wide assignment. Third, the app authenticates with that identity, normally through DefaultAzureCredential in the SDK, which transparently fetches a token from the platform, and it reads the database password from Key Vault at startup, so there is no secret in code, config, or environment variables to leak. Operationally I cache the secret in memory and refresh it on an interval, every 5 minutes is a reasonable default, rather than calling the vault on every request, both because it is faster and because Key Vault throttles at around 2,000 reads per 10 seconds. When the password rotates I update it in Key Vault and the app picks up the new version on its next refresh, and I keep the vault's audit logs, ours were retained for 90 days, so I can prove who read what. The credential to reach Key Vault is the platform-managed identity itself, so there is nothing to bootstrap or store.

Managed identity / AKS workload identity, no stored cred Scoped Key Vault Secrets User role Runtime fetch via DefaultAzureCredential + rotation/caching

Then they probe: How does an AKS pod authenticate to Key Vault without a stored secret?

Practise this one
Mid

Explain Azure networking basics: VNets, subnets, NSGs, peering, and private endpoints.

What most people say

You create a VNet with subnets and use NSGs as firewalls to control traffic.

It covers the basics but misses peering (and its non-transitivity) and private endpoints, which are how you connect VNets and keep PaaS services like Storage and SQL off the public internet, a key Azure security pattern.

The structure behind a strong answer

  1. 1

    VNet and subnets. A virtual network is your isolated private network in Azure; you divide it into subnets to segment workloads (web, app, data tiers).

  2. 2

    NSGs (and ASGs). Network Security Groups are stateful firewall rule sets applied to subnets or NICs to allow/deny traffic; Application Security Groups group NICs so rules target app roles instead of IPs.

  3. 3

    Peering. VNet peering connects two VNets so resources communicate privately over the Azure backbone. Peering is non-transitive, so a hub-and-spoke topology is used at scale.

  4. 4

    Private endpoints. A private endpoint gives a PaaS service (Storage, SQL) a private IP inside your VNet, so traffic stays on the private network and the public endpoint can be disabled.

What gets you hired

A virtual network, VNet, is my isolated private network in Azure, and I carve it into subnets to segment workloads. On my last project the VNet was a 10.0.0.0/16 and I split it into three /24 subnets, web, app, and data. For traffic control, Network Security Groups are stateful rule sets I attach to a subnet or a NIC to allow or deny by source, destination, port and protocol, so I only had to allow 443 inbound on web and 1433 from app to data, and return traffic is allowed automatically because they are stateful. Application Security Groups let me group NICs by role, web or db, so rules reference the group instead of a brittle list of 20 IP addresses. VNet peering links two VNets so resources talk privately over the Azure backbone. The gotcha is that peering is non-transitive, so if A peers with B and B with C, A still cannot reach C through B, which is exactly why at scale you run hub and spoke with one central hub VNet. The piece people miss is private endpoints. A private endpoint projects a PaaS service like Azure Storage or Azure SQL into my VNet as a single private IP, so apps reach it over the private network and I can set public network access to disabled entirely. That is how sensitive PaaS traffic stops crossing the public internet. So VNet and subnets for isolation, NSGs and ASGs for control, peering for connectivity, private endpoints for private PaaS access.

VNet/subnets + NSG (stateful) + ASG Knows peering is non-transitive (hub-and-spoke) Knows private endpoints keep PaaS off the public internet

Then they probe: Why does peering being non-transitive matter?

Practise this one
Mid

How do you choose between Azure compute options: VMs, App Service, AKS, Functions, and Container Apps?

What most people say

I would use VMs since they can run anything, or AKS if it needs containers.

Defaulting to VMs or AKS ignores the managed options that are usually the better fit. App Service, Functions, and Container Apps remove huge operational burden; reaching for VMs/AKS by reflex means more ops than the workload needs.

The structure behind a strong answer

  1. 1

    VMs: maximum control. Infrastructure as a service: full control of the OS and stack, but you own patching, scaling, and availability. For legacy or special OS/software needs.

  2. 2

    App Service: managed web apps. PaaS for web apps and APIs: deploy code or a container, get scaling, TLS, and slots without managing servers. Great default for standard web workloads.

  3. 3

    AKS: managed Kubernetes. When you need Kubernetes, complex microservices, portability, fine-grained orchestration, accepting the operational complexity of running a cluster.

  4. 4

    Serverless: Functions and Container Apps. Functions for event-driven, short-lived code that scales to zero; Container Apps for serverless containers and microservices (KEDA-based autoscaling) without managing Kubernetes.

What gets you hired

I put the workload on a spectrum from most control to most managed, and pick the least operationally heavy option that fits. VMs are infrastructure as a service, full control of the OS, but I own patching, scaling and availability, so I keep them for legacy apps, a special OS or software requirement, or a lift and shift, never as a default. App Service is platform as a service for web apps and APIs. I push code or a container and get autoscale, managed TLS and deployment slots, and I can warm a slot then swap in under 30 seconds with no downtime, so it is my default for standard web workloads. AKS is managed Kubernetes, and I choose it when I genuinely need Kubernetes, complex microservices, fine grained orchestration, or portability across clouds, accepting the real operational cost of running a cluster and its node pools. Then the serverless end. Azure Functions is for event driven, short lived code that scales to zero and bills per execution, ideal for triggers and glue, and on Consumption it will happily fan out to 200 instances for a burst. Azure Container Apps is serverless containers and microservices with KEDA based autoscaling, including scale to zero, for when I want containers without running Kubernetes myself. So the question I ask is how much control the workload actually needs versus how much operations I want to own, and I default toward the managed options unless there is a concrete reason to drop lower.

Maps workload to service on control vs management Knows App Service/Functions/Container Apps, not just VMs/AKS Defaults toward managed options

Then they probe: When would you pick Container Apps over AKS?

Practise this one
Mid

What are the main Azure Storage options, and how do access tiers and redundancy work?

What most people say

You use Blob storage for files and pick how many copies you want.

It collapses several distinct services (Blob vs Files vs Disks) and treats redundancy as "number of copies" without the zone-versus-region distinction or access tiers, which are the levers for cost and resilience.

The structure behind a strong answer

  1. 1

    Storage types. Blob for object storage (images, backups, data lakes), Files for managed SMB/NFS file shares, Disks for VM block storage, plus Queue and Table for messaging and key-value.

  2. 2

    Access tiers (Blob). Hot for frequent access, Cool for infrequent, Cold colder still, and Archive for rarely accessed data, cheaper storage but higher access cost and retrieval latency as you go colder.

  3. 3

    Redundancy. LRS keeps three copies in one datacenter; ZRS spreads across availability zones in a region; GRS/GZRS also replicate to a paired region for geo-resilience.

  4. 4

    Match to need. Choose type by workload, tier by access frequency to control cost, and redundancy by how much failure you must survive (a rack, a zone, or a whole region).

What gets you hired

The first decision is the storage type. Blob is object storage for unstructured data, images, backups, logs, data lake content. Azure Files gives managed SMB or NFS shares for lift and shift or shared file access. Managed Disks are block storage attached to a VM. Queue and Table cover simple messaging and key value needs. For Blob, access tiers control cost by how often you read the data. Hot is for frequently accessed data, Cool is for data you touch maybe once a month with a 30 day minimum, Cold goes further at 90 days, and Archive is for data you almost never read. As you go colder the per GB storage price drops sharply, but access charges climb and Archive rehydration can take hours, so I tier by real access pattern and automate the moves with lifecycle policies, for example transitioning logs to Cool at 30 days and Archive at 180. For durability, redundancy is about which failure you survive. LRS keeps 3 copies in a single datacenter, cheapest, but a datacenter loss hurts. ZRS spreads copies across 3 availability zones in the region and survives a zone failure. GRS and GZRS additionally replicate to a paired region hundreds of miles away for geo resilience against a full region outage. So I match type to workload, tier to access frequency for cost, and redundancy to the failure domain I actually need to survive, rather than just maxing out the copy count.

Distinguishes Blob/Files/Disks/Queue/Table Knows access tiers trade storage vs access cost Knows LRS/ZRS/GRS by failure domain

Then they probe: What is the difference between ZRS and GRS?

Practise this one
Senior

How would you govern a large Azure estate across many subscriptions?

What most people say

I would set up RBAC carefully and make sure each team only has access to their resources.

Per-subscription RBAC alone does not govern an estate. It misses management-group structure, Azure Policy guardrails, landing zones for consistent baselines, and PIM for just-in-time admin, the structural controls that actually scale.

The structure behind a strong answer

  1. 1

    Structure with management groups. Organize subscriptions under management groups (by environment or business unit) so policy and RBAC apply broadly and consistently through inheritance.

  2. 2

    Enforce with Azure Policy. Use Azure Policy to audit or deny non-compliant resources at scale: allowed regions, required tags, no public IPs, mandatory encryption, applied at the management group level.

  3. 3

    Standardize with landing zones. Use Azure Landing Zones (the Cloud Adoption Framework blueprint) so every new subscription comes with a consistent, secure baseline of networking, identity, and policy.

  4. 4

    Least-privilege and just-in-time access. Apply RBAC least privilege, assign to groups, and use Privileged Identity Management (PIM) for just-in-time, time-bound, approved elevation to admin roles instead of standing access.

What gets you hired

I govern it structurally rather than resource by resource. First, structure: organize all the subscriptions under a management group hierarchy, typically by environment and business unit, so policy and RBAC set high up inherit down consistently, and Azure supports up to 6 levels of management groups below the root, which is plenty. Second, guardrails with Azure Policy. Instead of hoping teams comply, I apply policies at the management group level that audit or outright deny non-compliant resources: 2 or 3 allowed regions only, required tags like cost centre and owner, no public IP addresses, encryption required, enforced automatically across every subscription. Third, standardize provisioning with Azure Landing Zones, the Cloud Adoption Framework blueprint, so every new subscription is created from a consistent, secure baseline with networking, identity, logging and policy already wired in, rather than each team building from scratch and drifting. Fourth, identity: RBAC with least privilege assigned to groups, and crucially Privileged Identity Management for admin access, so high-privilege roles are not standing grants but just-in-time, time-bound, typically 8 hours with approval and MFA, which shrinks the standing-admin attack surface dramatically. I would also centralize logging and turn on Microsoft Defender for Cloud for posture management. The theme is the same as any large-cloud governance: hierarchy, automated guardrails, standardized baselines, and least-privilege just-in-time identity, not manual per-resource control.

Management groups + inheritance Azure Policy for guardrails + landing zones for baselines Least-privilege RBAC + PIM just-in-time

Then they probe: What does Azure Policy give you that RBAC does not?

Practise this one
Senior

How do you give users one identity across on-prem Active Directory and Azure (hybrid identity)?

What most people say

I would recreate all the users in Entra ID so they exist in both places.

Manually recreating users means two disconnected identity stores that drift and double the admin work. Hybrid identity is solved by syncing on-prem AD into Entra with Entra Connect, with a deliberate authentication method.

The structure behind a strong answer

  1. 1

    Sync with Entra Connect. Microsoft Entra Connect (or Connect cloud sync) synchronizes on-prem AD users and groups into Entra ID, so the same identities exist in both and users get single sign-on.

  2. 2

    Password hash sync. The simplest and recommended default: a hash of the password hash is synced to Entra, so authentication can happen in the cloud even if on-prem is down.

  3. 3

    Pass-through authentication. Validates the password against on-prem AD in real time via lightweight agents, so passwords never reside in the cloud, at the cost of depending on on-prem availability.

  4. 4

    Federation. Federate to an on-prem identity provider like ADFS for full control or special requirements, the most complex to run, so used only when sync options do not meet needs.

What gets you hired

The goal is one identity that works both on-prem and in the cloud, and you get it by synchronizing rather than recreating. Microsoft Entra Connect, or the newer Connect cloud sync, synchronizes on-prem Active Directory users and groups into Entra ID, by default every 30 minutes, so the same identities exist in both worlds and users get single sign-on with familiar credentials. The real decision is how authentication is handled, and there are 3 options with clear trade-offs. Password hash sync is the simplest and the recommended default: a hash of the on-prem password hash is synced to Entra, so cloud sign-in keeps working even if the on-prem domain controllers are down, which makes it the most resilient. Pass-through authentication validates the password against on-prem AD in real time through lightweight agents, and Microsoft recommends running at least 3 of them for high availability, so the password is never stored in the cloud, which some organizations require, but cloud sign-in now depends on on-prem availability. Federation, typically with ADFS, hands authentication entirely to an on-prem identity provider for maximum control or special cases like smart-card scenarios, but it is the most complex, often a farm of servers to patch and certificates to rotate. So I default to Entra Connect with password hash sync, and only move to pass-through or federation when there is a concrete policy or compliance reason, because each step up adds operational dependency and complexity.

Syncs with Entra Connect, not recreate Knows password hash sync / pass-through / federation trade-offs Defaults to hash sync, federation only when needed

Then they probe: Why is password hash sync usually preferred over federation?

Practise this one

Databases

14 questions · Foundation, Junior, Mid, Senior
Foundation

What is a database index, how does it speed up queries, and what does it cost?

What most people say

An index makes queries faster, so you add indexes to speed up your database.

It knows the benefit but not the cost. Indexes slow writes and consume storage, so indexing everything backfires. Knowing the trade-off and indexing selectively is what the question is really probing.

The structure behind a strong answer

  1. 1

    What it is. An index is a separate data structure (usually a B-tree) that lets the database find rows by a column value without scanning the whole table.

  2. 2

    Why it speeds reads. It turns a full table scan, O(n), into a much faster lookup, roughly O(log n), for queries that filter, join, or sort on the indexed column.

  3. 3

    The cost. Indexes take storage and slow down writes, every insert, update, or delete must also maintain every relevant index. Over-indexing hurts write performance.

  4. 4

    Index deliberately. Index the columns you actually filter, join, or sort on, use composite indexes for multi-column queries (mind the leftmost-prefix rule), and do not index everything.

What gets you hired

An index is a separate data structure, typically a B-tree, that lets the database find rows by a column value without reading the whole table. For a query that filters, joins, or sorts on that column, it turns a full table scan, which is O(n) in the number of rows, into roughly an O(log n) lookup. On a 10 million row table that is the difference between a few seconds and a couple of milliseconds. The cost is the part people forget. An index takes storage, and it slows down writes, because every insert, update, or delete also has to maintain every index covering the affected columns. I have seen a table carrying 8 indexes take a real hit on insert throughput, so over-indexing a write-heavy table can hurt more than it helps. The way I use them is deliberate. I index the columns I actually filter, join, or order by, I use composite indexes when a query spans multiple columns and I remember the leftmost-prefix rule there, and I avoid indexing low-selectivity columns like a boolean with 2 values or piling on indexes nobody needs. And I always verify with EXPLAIN that the planner is really using the index I expect, rather than assuming it is.

Knows it is a B-tree turning scans into log-n lookups Names the write/storage cost Indexes selectively + verifies with EXPLAIN

Then they probe: Why not just index every column?

Practise this one
Foundation

What are the ACID properties of a database transaction?

What most people say

ACID is atomicity, consistency, isolation, and durability, the properties that make databases reliable.

It expands the acronym but defines nothing. The interviewer wants each property explained, ideally with the money-transfer example, because that shows you understand what a transaction actually guarantees.

The structure behind a strong answer

  1. 1

    Atomicity. All-or-nothing: every statement in the transaction commits, or none does. A transfer that debits one account must credit the other or roll back entirely.

  2. 2

    Consistency. The transaction moves the database from one valid state to another, respecting constraints (foreign keys, uniqueness, checks). Invalid states are rejected.

  3. 3

    Isolation. Concurrent transactions do not interfere with each other; the result is as if they ran in some serial order (tunable via isolation levels).

  4. 4

    Durability. Once committed, the data survives crashes, it is persisted (typically via a write-ahead log) so a power loss does not lose a committed transaction.

What gets you hired

ACID describes the 4 guarantees a transaction provides, and the classic illustration is a money transfer of, say, 100 dollars between accounts. Atomicity is all-or-nothing: the debit from one account and the credit to the other either both happen or neither does, and if anything fails midway the whole transaction rolls back, so the 100 dollars never vanishes in between. Consistency means the transaction takes the database from one valid state to another, honoring every constraint, foreign keys, uniqueness, check constraints, so it cannot leave the data invalid. Isolation means concurrent transactions do not step on each other, and the outcome is as if they ran in some serial order. Databases let you tune how strict that is through isolation levels, and Postgres defaults to read committed while a stricter serializable is available when you need it. Durability means that once a transaction commits, its effects survive a crash or power loss, because the database has already persisted it, usually through a write-ahead log that gets replayed on recovery. Together these are why I trust a relational database with critical data like payments, and they are also exactly the guarantees that get relaxed in some distributed and NoSQL systems in exchange for scale and availability.

Defines all four with an example Knows isolation is tunable Knows durability uses a WAL / what gets relaxed in distributed DBs

Then they probe: Which ACID property do many distributed/NoSQL systems relax, and why?

Practise this one
Foundation

What is normalization, and what are primary and foreign keys?

What most people say

Normalization splits data into tables, and keys link the tables together.

It gestures at the idea but does not explain why (reducing redundancy and anomalies), what each key actually enforces, or that denormalization is a legitimate performance trade-off.

The structure behind a strong answer

  1. 1

    Normalization. Organizing tables to reduce redundancy, each fact is stored once, so you avoid update anomalies where the same data in many places drifts out of sync.

  2. 2

    Primary key. A column (or set) that uniquely identifies each row in a table. It enforces uniqueness and is what other tables reference.

  3. 3

    Foreign key. A column that references another table primary key, enforcing referential integrity, you cannot reference a row that does not exist, and deletes can be controlled.

  4. 4

    Denormalization is a trade-off. Sometimes you deliberately duplicate data to avoid expensive joins and speed reads, accepting the redundancy and the need to keep copies in sync. It is a choice, not always wrong.

What gets you hired

Normalization is organizing data into tables so each fact lives in exactly 1 place, which removes redundancy and the update anomalies that come with it. If a customer address is stored once, you update it once, rather than chasing 5 copies scattered across rows that drift out of sync. The keys are what hold a normalized schema together. A primary key is the column, or combination of columns, that uniquely identifies each row in a table. It enforces uniqueness and gives other tables a stable handle to point at. A foreign key is a column in one table that references another table's primary key, and it enforces referential integrity, so the database will not let you reference a row that does not exist, and you choose what happens on delete, cascade, restrict, or set null. The nuance I would add is that normalization is the default for transactional data because it keeps data correct, but denormalization is a deliberate and legitimate trade-off. Sometimes I duplicate a field to avoid a 4 table join on a hot read path, accepting the redundancy and taking on the job of keeping the copies in sync. So I normalize for integrity, and I denormalize consciously for read performance when the access pattern really justifies it.

Normalization = reduce redundancy/anomalies PK uniquely identifies, FK enforces referential integrity Knows denormalization is a deliberate trade-off

Then they probe: When would you deliberately denormalize?

Practise this one
Junior

A query is slow in production. How do you diagnose and fix it?

What most people say

I would add an index to make the query faster.

Adding an index without first reading the query plan is guessing, the problem might be a full scan, an N+1, SELECT *, or a query that cannot use an index. The method is EXPLAIN first, then the targeted fix.

The structure behind a strong answer

  1. 1

    Find the slow query. Identify it from the slow query log or APM/metrics, do not guess which query is the problem; measure.

  2. 2

    Read the plan. Run EXPLAIN or EXPLAIN ANALYZE to see how the database executes it: is it doing a full table scan where it should use an index, a huge sort, or a bad join order?

  3. 3

    Fix the cause. Usually add or fix an index on the filtered/joined columns, avoid SELECT * and fetch only needed columns, rewrite the query, or fix an N+1 pattern from the app.

  4. 4

    Verify and watch. Re-run EXPLAIN ANALYZE to confirm the plan improved, and keep monitoring, an index that helps reads also adds write cost, so confirm the net win.

What gets you hired

I diagnose before I change anything. First I confirm which query is actually slow, from the slow query log or from APM and metrics, rather than guessing, because the query I suspect is often not the real offender. Then the key step: I run EXPLAIN, or EXPLAIN ANALYZE for real timings, to see the execution plan. That tells me what the database is actually doing, a sequential full table scan where an index should be used, a large in-memory or disk sort, a poor join order, or scanning far more rows than the result needs. From the plan the fix usually becomes obvious: add or correct an index on the columns being filtered or joined, stop selecting columns I do not need (SELECT * pulls extra data and can prevent index-only scans), rewrite the query, or, very commonly, fix an N+1 pattern where the application is issuing one query per row instead of a single join or batched query. Then I verify by re-running EXPLAIN ANALYZE to confirm the plan actually improved, and I keep an eye on it, since an index that speeds reads also adds write overhead, so I make sure it is a net win. The discipline is measure, read the plan, targeted fix, verify, not reflexively add an index.

Measures first (slow query log/APM) Reads the plan with EXPLAIN/EXPLAIN ANALYZE Knows the usual fixes incl. N+1, verifies after

Then they probe: What does EXPLAIN tell you that guessing cannot?

Practise this one
Junior

Why is database connection pooling important, especially at scale?

What most people say

Pooling reuses connections so the app is faster.

It mentions reuse and speed but misses the real driver: database connections are a scarce, expensive resource, and without pooling you exhaust max_connections under load and take the database down. The scale/serverless angle is the point.

The structure behind a strong answer

  1. 1

    Connections are expensive. Each database connection costs memory and setup, in Postgres each is a separate backend process, and there is a hard max_connections limit.

  2. 2

    One-per-request exhausts it. Opening a fresh connection per request, and under load thousands of concurrent requests, quickly exhausts the limit and the database refuses or slows everything.

  3. 3

    A pool reuses connections. A connection pool keeps a fixed set of warm connections and hands them out and back, so many requests share a bounded number of connections without the open/close cost.

  4. 4

    Serverless needs a proxy. Serverless/many-instance setups multiply connections badly; an external pooler (PgBouncer, RDS Proxy) sits in front to bound total connections to the database.

What gets you hired

Database connections are not cheap. Each one costs memory and setup time, and in Postgres every connection is backed by its own server side process using several megabytes, and there is a hard cap, max_connections, which defaults to 100. If the application opens a connection per request, then under real load with a few thousand concurrent requests you blow past that limit in seconds, and the database starts refusing connections or grinds as it thrashes between too many backends, which can take the whole system down. A connection pool fixes this by keeping a small fixed set of warm, established connections, say 20 per instance, and lending them out and taking them back, so thousands of requests share a bounded number of connections and nobody pays the open and close cost each time. The problem gets acute in serverless or heavily horizontally scaled environments, because every function instance or pod wants its own pool. 50 pods times a pool of 20 is 1,000 connections, which is 10 times what the default Postgres will take. The fix there is an external pooler or proxy, PgBouncer in transaction mode or something like RDS Proxy, sitting between the app and the database and multiplexing many client connections onto a bounded number of real ones. Pooling is really about treating connections as a scarce resource and capping how many ever reach the database.

Knows connections are expensive/limited (per-process in PG) Knows one-per-request exhausts max_connections Knows external poolers for serverless/scale

Then they probe: Why is connection management especially hard in serverless?

Practise this one
Junior

How do read replicas help scale a database, and what is replication lag?

What most people say

You add read replicas to spread the load and make the database faster.

It captures read scaling but ignores that replicas do not help writes and, critically, replication lag, which causes stale reads and the read-after-write problem. Missing that leads to confusing user-facing bugs.

The structure behind a strong answer

  1. 1

    Reads to replicas. A read replica is a copy of the primary that serves read-only queries, so you offload read traffic from the primary and scale read capacity by adding replicas.

  2. 2

    Writes stay on the primary. All writes go to the single primary, which replicates changes out to the replicas. So replicas scale reads, not writes.

  3. 3

    Replication lag. Replication is usually asynchronous, so a replica is slightly behind the primary. That delay is replication lag, and it means a replica can return stale data.

  4. 4

    Read-after-write. A user who writes then immediately reads from a lagging replica may not see their own change. Route reads that need freshness to the primary, or read your own writes from the primary.

What gets you hired

A read replica is a copy of the primary that serves read only queries. Most workloads are read heavy, often something like a 10 to 1 read to write ratio, so I point reads, reports, list views, lookups, at one or more replicas and take that load off the primary, and I scale read capacity by adding more, typically up to about 5 replicas before the fan out itself becomes a burden. The important constraint is that all writes still go to the single primary, which replicates changes out, so replicas scale reads, not writes. If writes are the bottleneck, replicas do not help and I need sharding or a different approach. The catch is replication lag. Replication is usually asynchronous, the primary commits and acknowledges without waiting for replicas, so a replica sits a little behind, commonly under 100 milliseconds but seconds or worse under heavy write load, and that lag means it can hand back stale data. The concrete bug is read after write: a user updates their profile, the write lands on the primary, their next read hits a replica that has not caught up, and they do not see their own change, which just looks broken. So I handle it deliberately. Reads that must be fresh, especially read your own writes, go to the primary, and only lag tolerant reads go to replicas. And I alert when replication lag crosses about 5 seconds, because staleness only gets worse from there.

Reads to replicas, writes to primary Knows replicas do not scale writes Knows replication lag + read-after-write handling

Then they probe: Do read replicas help with write scaling?

Practise this one
Mid

Under load, your RDS instance is the bottleneck and the app is slowing down. Walk me through your options, in order.

What most people say

I would upgrade to a bigger RDS instance, and if that is not enough I would shard the database.

It skips diagnosis entirely and leads with the two most expensive levers. A bigger instance hides a missing index instead of fixing it, and sharding is a massive, hard-to-reverse change reached for far too early. No mention of caching, replicas, indexes, or pooling.

The structure behind a strong answer

  1. 1

    Diagnose before scaling. Is it CPU, memory, IOPS, connections, or slow queries? Use Performance Insights and slow query logs. Throwing hardware at a missing index wastes money and does not fix it.

  2. 2

    Fix the cheap, high-yield things first. Add the missing index, fix N+1 queries, and add a connection pooler (RDS Proxy) if connection churn is the issue. These often remove the bottleneck with no scaling at all.

  3. 3

    Offload reads. Cache hot reads (ElastiCache) to take load off the DB entirely, and add read replicas to spread read-heavy traffic. Note replicas help reads only, not writes, and add replication lag.

  4. 4

    Scale the instance. Vertical scaling (a bigger instance class or more provisioned IOPS) is simple and buys headroom, but it is a ceiling and a single point of pressure, so treat it as a runway, not the destination.

  5. 5

    Re-architect writes last. If writes are the real bottleneck, then partitioning/sharding or a different data store, but only after the cheaper steps, because it is the most invasive and hardest to reverse.

What gets you hired

First I diagnose: is it CPU, IOPS, connections, or slow queries? Performance Insights and the slow query log tell me, because scaling hardware to cover a missing index just burns money. Then the cheap high-yield fixes: add the missing index, kill N+1 queries, and put RDS Proxy in front if connection churn is the problem, these often remove the bottleneck with no scaling. Next I offload reads: cache hot reads in ElastiCache and add read replicas for read-heavy traffic, remembering replicas only help reads and bring replication lag. After that, vertical scaling, a bigger instance class or more provisioned IOPS, which is simple and buys headroom but is a ceiling, so it is runway, not the destination. Only if writes are genuinely the bottleneck do I reach for partitioning or sharding, last, because it is the most invasive and hardest to undo.

Diagnoses the specific resource before scaling Leads with indexes, query fixes, and pooling Knows replicas scale reads only and add lag

Then they probe: You added read replicas but users report stale data right after a write. Why, and what do you do?

Practise this one
Mid

What is the N+1 query problem, and how do you fix it?

What most people say

It is when you run too many queries; you fix it by optimizing the queries.

Vague. The N+1 pattern is specific, a query per row from lazy loading, and the fix is specific, eager loading or batching. "Optimize the queries" shows you do not actually recognize the pattern.

The structure behind a strong answer

  1. 1

    What it is. You run one query to fetch a list of N items, then one additional query per item to fetch a related record, so you issue N+1 queries instead of a constant few.

  2. 2

    Why it happens. It usually comes from an ORM with lazy loading: accessing a related object in a loop silently triggers a separate query each iteration, invisible in the code.

  3. 3

    The impact. Each query is a round trip; N+1 of them adds up to huge latency and database load, often fine with 10 rows in dev and catastrophic with 10,000 in production.

  4. 4

    The fixes. Eager-load the relation (a JOIN or the ORM include/preload), or batch the related lookups into a single IN query, turning N+1 into one or two queries.

What gets you hired

The N+1 problem is when you run one query to load a list of N items and then, for each item, run another query to load something related, so you end up issuing N+1 queries instead of a small constant number. It almost always comes from an ORM with lazy loading: you fetch a list of orders, then loop over them and access order.customer, and each access silently fires its own query to the database, which is invisible in the code because it just looks like a property access. The impact is brutal because each of those queries is a network round trip to the database, so 10 items means 11 queries, which is fine, but 10,000 items means 10,001 queries, which is why something that looks fast in development with a handful of rows falls over in production. The fix is to fetch the related data up front instead of per-row: eager-load the relationship, either with a JOIN or the ORM include or preload mechanism, so the related records come back with the original query, or batch the lookups, collect the IDs and do a single WHERE id IN (...) query. Either way you collapse N+1 into one or two queries. I catch these by watching query counts per request in APM or query logs, since the pattern hides in normal-looking ORM code.

Recognizes the pattern + lazy loading cause Knows the per-round-trip impact at scale Fixes with eager loading/JOIN/batching

Then they probe: Why does this often pass in development but fail in production?

Practise this one
Mid

What are transaction isolation levels, and what anomalies do they prevent?

What most people say

Isolation levels control how transactions see each other; serializable is the safest so use that.

It knows serializable is strongest but ignores the cost. Always using serializable kills concurrency. The point is matching the level to the anomalies you must prevent versus the throughput you need.

The structure behind a strong answer

  1. 1

    Why levels exist. Isolation levels let you trade strictness against concurrency: stronger isolation prevents more anomalies but uses more locking and reduces throughput.

  2. 2

    The anomalies. Dirty read (seeing another transaction uncommitted data), non-repeatable read (a row changes between two reads in your transaction), phantom read (new rows appear matching your query).

  3. 3

    The levels. Read uncommitted (allows dirty reads), read committed (no dirty reads, common default), repeatable read (no non-repeatable reads), serializable (full isolation, no phantoms, as if serial).

  4. 4

    The trade-off. Higher isolation means more locking/contention or more aborts/retries (in MVCC systems). Pick the lowest level that is correct for the workload, often read committed.

What gets you hired

Isolation levels exist because perfect isolation is expensive, so the database lets me choose how strict to be, trading correctness guarantees against concurrency. The clearest way in is the anomalies. A dirty read is seeing another transaction's uncommitted changes, which might roll back. A non repeatable read is reading the same row twice inside one transaction and getting 2 different values because someone committed a change in between. A phantom read is re-running a query and finding new rows that another transaction inserted. The 4 standard levels map onto those. Read uncommitted allows dirty reads and is almost never used. Read committed prevents dirty reads and is the common default, for example in Postgres and in SQL Server. Repeatable read additionally prevents non repeatable reads. Serializable is full isolation, the outcome is as if transactions ran one at a time, so phantoms are gone too. The trade off is real. Higher isolation means more locking and contention, or in an MVCC database more serialization failures that I have to catch and retry, and on a hot table that can visibly cut throughput. So my approach is to pick the lowest level that is still correct for the workload, which is usually read committed, and escalate only the handful of transactions that genuinely need the stronger guarantee, a month end financial rollup for instance, to repeatable read or serializable, rather than cranking everything to serializable and paying for it on every query.

Knows the anomalies + which level stops each Knows read committed is a common default Articulates the isolation/concurrency trade-off

Then they probe: What is the difference between a non-repeatable read and a phantom read?

Practise this one
Mid

How do you handle concurrent updates to the same row? Optimistic versus pessimistic locking.

What most people say

I would lock the row so only one update happens at a time.

Pessimistic locking is one valid option, but always locking hurts concurrency and risks deadlocks. The complete answer names the lost-update problem and weighs optimistic vs pessimistic by contention level.

The structure behind a strong answer

  1. 1

    The problem. Two transactions read the same row, both modify it, and one overwrites the other, a lost update. You need a strategy to prevent it.

  2. 2

    Pessimistic locking. Lock the row when you read it (SELECT ... FOR UPDATE) so others block until you commit. Safe under high contention, but holds locks and can hurt throughput or deadlock.

  3. 3

    Optimistic locking. No lock; you read a version (or timestamp) and on write check it has not changed (UPDATE ... WHERE version = X). If it changed, you detect the conflict and retry. Great under low contention.

  4. 4

    Choose by contention. Pessimistic when conflicts are frequent and retries would be wasteful; optimistic when conflicts are rare and you want maximum concurrency without holding locks.

What gets you hired

The underlying problem is the lost update. Two transactions read the same row, both compute a new value from it, and the second write clobbers the first, so one update silently disappears. That is the classic read modify write race. There are 2 strategies. Pessimistic locking takes a lock when you read, with SELECT ... FOR UPDATE, so any other transaction that wants that row blocks until you commit or roll back. It is safe and simple when contention is high, but it holds the lock for the whole transaction, which cuts concurrency and can deadlock if transactions grab rows in different orders, so I keep those transactions short, ideally under 100 milliseconds. Optimistic locking takes no lock. I read the row with a version column, then write with UPDATE ... WHERE id = ? AND version = ?, incrementing the version. If someone else changed the row in between, the version no longer matches, the update affects 0 rows, and I detect the conflict and retry with fresh data. Optimistic is excellent when conflicts are rare, say under 1 percent of writes, because it holds no locks and maximises throughput, and the cost is just the occasional retry. So I choose by contention. Pessimistic when many transactions genuinely fight over the same rows and retrying would only thrash, optimistic when conflicts are infrequent and I want high throughput. The one thing I never do is ignore it and let read modify write race.

Names the lost-update problem Explains both strategies + version check Chooses by contention level

Then they probe: How does optimistic locking actually detect a conflict?

Practise this one
Mid

What caching patterns do you use in front of a database, and how do you keep the cache consistent?

What most people say

I would put Redis in front of the database to cache query results.

It names a tool but no pattern and ignores the actual hard problem: invalidation and consistency. Caching reads is easy; keeping the cache correct on writes is what an interviewer is probing.

The structure behind a strong answer

  1. 1

    Cache-aside (lazy). The app checks the cache; on a miss it reads the database, populates the cache, and returns. The most common pattern; the cache only holds what is actually requested.

  2. 2

    Write-through / write-behind. Write-through writes to cache and database together (consistent, slower writes); write-behind writes to cache and asynchronously to the database (fast, risk of loss).

  3. 3

    Invalidation is the hard part. On a write, you must invalidate or update the cached entry, or readers get stale data. TTLs bound staleness as a safety net; explicit invalidation keeps it fresh.

  4. 4

    Accept a consistency window. Caching trades some consistency for performance, decide how much staleness is acceptable per data type, and use TTLs plus invalidation accordingly.

What gets you hired

The pattern I reach for most is cache aside, or lazy loading. The application checks the cache first, and on a miss it reads the database, writes the result back into the cache, and returns it, so the cache fills only with data that is actually requested. The alternatives are write through, where every write goes to the cache and the database synchronously, which keeps them consistent but adds maybe 2 to 5 milliseconds to each write, and write behind, where you write to the cache and flush to the database asynchronously, which is fast but risks losing data if the cache dies before the flush. The real engineering problem is not the read pattern though, it is invalidation and consistency. When data changes the cached copy is wrong, so on a write I either delete the cached entry so the next read repopulates it, or update it in place. And I always set a TTL as a safety net, so any entry I forget to invalidate still self heals inside a bounded window. The honest framing is that caching trades consistency for performance, there is always a staleness window, so I choose the TTL per data type by how much staleness the business can tolerate. An account balance gets near zero, a few seconds at most or no caching at all. A product catalogue can happily sit at a 10 minute TTL. So, cache aside by default, write through when I need stronger consistency, and a deliberate invalidation plus TTL strategy, because that is the part that actually bites.

Names cache-aside vs write-through/behind Focuses on invalidation/consistency as the hard part Uses TTL + per-data-type staleness tolerance

Then they probe: Why is cache invalidation considered one of the hard problems?

Practise this one
Senior

What is database sharding, when do you need it, and what are the trade-offs?

What most people say

Sharding splits the database across servers so it can handle more load.

Right definition but no judgment. A senior answer notes sharding is a last resort after replicas/caching, that it scales writes (not just load), and names the real costs: shard-key choice, cross-shard queries, hotspots, and resharding pain.

The structure behind a strong answer

  1. 1

    What it is. Sharding is horizontal partitioning: splitting the data across multiple database nodes by a shard key, so each node holds a subset and they scale together.

  2. 2

    When you need it. When a single primary can no longer handle the write throughput or the dataset no longer fits on one machine, the cases read replicas and caching cannot solve.

  3. 3

    Try simpler things first. Sharding adds major complexity, so first exhaust vertical scaling, read replicas, and caching. Shard only when writes or size genuinely exceed one node.

  4. 4

    The trade-offs. Choosing a shard key that spreads load without hotspots is hard; cross-shard queries and joins become expensive or impossible; transactions across shards are hard; and rebalancing/resharding is operationally painful.

What gets you hired

Sharding is horizontal partitioning of data across multiple database nodes by a shard key, for example user ID or tenant, so each shard holds a slice, maybe 8 shards each carrying an eighth of the rows, and the cluster scales past what 1 machine can do. It matters because it is the tool for scaling writes and storage beyond a single node. Read replicas scale reads and caching offloads them, which is often enough at a 10 to 1 read to write ratio, but neither helps when write throughput or raw dataset size outgrows the primary, and that is when sharding becomes necessary. I treat it as a last resort, because it adds a lot of complexity, so I first exhaust the simpler levers: scale up the instance, add read replicas, add caching, optimize queries, and only shard when writes or size genuinely exceed 1 node. The trade-offs are significant and worth naming. The shard key is the critical decision: a poor key creates hotspots where 1 shard takes 60 or 70 percent of the load, and the key is very hard to change later. Queries that span shards, joins or whole-dataset aggregations, become expensive or move into the application. Transactions across shards lose the easy ACID guarantees of a single node. And rebalancing, adding shards and redistributing data, is operationally painful and risky. So sharding is powerful and sometimes unavoidable, but it is a serious architectural commitment, and I put a lot of thought into the shard key.

Sharding = horizontal partition by key, scales writes/storage Treats it as last resort after replicas/caching Names shard-key/hotspots/cross-shard/resharding costs

Then they probe: What makes choosing a shard key so important?

Practise this one
Senior

Explain the CAP theorem and what it means when choosing a distributed datastore.

What most people say

CAP says you can only pick two of consistency, availability, and partition tolerance.

The "pick two" phrasing is the common oversimplification. Partition tolerance is mandatory in a distributed system, so the real choice is C vs A during a partition. A senior applies CP/AP to actual data needs, not recites the triangle.

The structure behind a strong answer

  1. 1

    The three properties. Consistency (every read sees the latest write), Availability (every request gets a response), Partition tolerance (the system keeps working despite network splits between nodes).

  2. 2

    The real trade-off. In a distributed system network partitions will happen, so P is not optional. The actual choice during a partition is between C and A: you cannot have both.

  3. 3

    CP vs AP. CP systems refuse or block requests during a partition to avoid serving stale/conflicting data (favor correctness). AP systems keep serving, possibly stale, and reconcile later (favor availability).

  4. 4

    Apply it. Choose by what the data needs: CP for things like balances or inventory where wrong is worse than unavailable; AP for things like feeds or carts where availability and eventual consistency are fine. (PACELC adds the latency trade-off when there is no partition.)

What gets you hired

CAP is about 3 properties of a distributed datastore: consistency, every read sees the most recent write; availability, every request gets a non-error response; and partition tolerance, the system keeps functioning when the network between nodes splits. The popular phrasing is pick 2, but that is misleading. In any real distributed system partitions are a fact of life, you will get dropped packets and split nodes, so partition tolerance is not something you can give up. The real choice happens during a partition, and it is consistency versus availability: you cannot have both while the network is split. A CP system refuses or blocks requests it cannot serve consistently, it would rather return errors for 30 seconds while a leader election completes than hand back stale or conflicting data. An AP system keeps responding, accepting that some nodes serve data that is a few seconds behind, and reconciles the divergence afterwards, eventual consistency. So when I pick a datastore I map it to what the data actually needs: for account balances, inventory counts, anything where wrong data is worse than a brief outage, I lean CP; for a social feed, product view counts, or a shopping cart, where staying available matters more and eventual consistency is fine, I lean AP. I would also mention PACELC as the more complete model, because even with no partition there is still a latency versus consistency trade-off, and that one you pay on every single request. The point is to reason about the specific data, not recite the triangle.

Knows P is mandatory, so the choice is C vs A during a partition Explains CP vs AP behavior Maps the choice to real data needs (+ PACELC)

Then they probe: Why is "pick two" a misleading way to state CAP?

Practise this one
Senior

How do you run a schema migration with zero downtime?

What most people say

I would run the ALTER TABLE during a low-traffic window so few users are affected.

A maintenance window is downtime, and a big ALTER can lock the table and break the running app. Zero downtime needs the expand/contract pattern with backward-compatible steps and a deploy strategy, not a quiet-hours ALTER.

The structure behind a strong answer

  1. 1

    Make changes backward-compatible. During a rolling deploy, old and new code run at the same time, so each schema change must work with both versions. Additive, backward-compatible changes first.

  2. 2

    Expand. Add the new structure without removing the old: add a new nullable column or new table, deploy code that writes to both old and new, and backfill existing data in batches.

  3. 3

    Migrate reads then contract. Once data is backfilled and consistent, switch reads to the new structure, and only after the old is fully unused do you contract: drop the old column/table.

  4. 4

    Avoid long locks. Beware operations that lock the table for a long time (some ALTERs, adding a NOT NULL with a default on old engines). Use online/safe migration techniques and batch backfills so you never block traffic.

What gets you hired

The key realization is that during a rolling deploy the old and new versions run side by side, sometimes for 10 or 15 minutes while pods roll, so every schema change has to be compatible with both at once. You cannot ship something the currently running old code would break on. The pattern for that is expand and contract, also called parallel change. First expand: add the new structure without touching the old, say a new nullable column or a new table, and deploy code that writes to both the old and new locations so new writes land in both. Then backfill the existing rows in batches, I usually do 1,000 to 5,000 rows per batch with a short pause between them, so I never hold a long lock or push replication lag past a second or two. Once the new structure is fully populated and consistent, I move reads over and verify. Only then, in a separate deploy days later, do I contract and drop the old column or table. Throughout I watch locking, because some operations take an exclusive lock that blocks every query, the classic example being adding a NOT NULL column with a default, which on older MySQL and Postgres rewrote the whole table. On a 50 million row table that is minutes of downtime, so I use online or safe migration techniques and cap any statement with a lock timeout of a few seconds. The contrast with a big ALTER in a maintenance window is that the window is itself downtime, and a long lock can still take the app down, whereas expand and contract ships the same change in small, reversible, always compatible steps.

Knows expand/contract (parallel change) Backward-compatible steps for rolling deploys Avoids long locks / batches backfills

Then they probe: Why can you not just rename a column in one migration?

Practise this one

IaC & Automation

13 questions · Foundation, Junior, Mid, Senior
Foundation

What is Infrastructure as Code, and why use it instead of clicking through the console?

What most people say

Infrastructure as Code means you automate creating your infrastructure with code.

It states what it is but not why it beats the console: versioning, review, repeatability, consistency across environments, and avoiding undocumented snowflakes. Those benefits are the actual answer.

The structure behind a strong answer

  1. 1

    Define infra in code. You describe your infrastructure (networks, servers, databases) in version-controlled configuration files instead of creating it by hand in a console.

  2. 2

    Repeatable and consistent. The same code produces the same environment every time, so dev, staging, and prod match and you can rebuild from scratch reliably.

  3. 3

    Versioned and reviewable. Infra changes go through git: history, code review, and the ability to roll back, just like application code.

  4. 4

    Avoids snowflakes. Manual console setup creates undocumented snowflake environments that drift and cannot be reproduced. IaC makes infrastructure auditable and automatable.

What gets you hired

Infrastructure as Code means defining your infrastructure, the networks, compute, databases, and load balancers, in version-controlled configuration files, and provisioning it from that code instead of clicking around a cloud console. The reasons it beats manual setup are concrete. Repeatability and consistency: the same code produces the same environment, so dev, staging, and prod actually match, and I can tear an environment down and rebuild it, which is disaster recovery as much as convenience. On my last team, standing up a fresh environment went from about 2 days of console work to a 20 minute terraform apply. Versioning and review: infra changes go through git like application code, so I get history, peer review, and a rollback path, instead of a console edit nobody can see. Automation: it plugs into CI/CD, so provisioning is hands-off rather than a 30 step manual runbook. And it kills snowflakes, the hand-built environments nobody fully understands, that drift over time, and that you cannot reproduce when they break. With IaC the code is both the documentation and the source of truth for what exists. So the short version is that the console is fine for exploring and learning, but anything that has to be reproducible, reviewable, and maintained should be code.

Names versioning/review/repeatability/automation Mentions consistency across environments Knows it avoids snowflakes / is the source of truth

Then they probe: What is a "snowflake" environment and why is it a problem?

Practise this one
Foundation

What is the difference between declarative and imperative infrastructure as code?

What most people say

Declarative is what you want and imperative is how to do it.

The slogan is right but undeveloped. The interviewer wants the consequence: declarative tools compute the diff and are idempotent/converge to desired state, which is why they dominate provisioning. The why is missing.

The structure behind a strong answer

  1. 1

    Declarative: describe the end state. You declare what you want the infrastructure to look like (Terraform, CloudFormation), and the tool figures out what to create, change, or destroy to reach it.

  2. 2

    Imperative: script the steps. You specify the sequence of commands to run (a shell script, or imperative use of a tool) to build the infrastructure step by step.

  3. 3

    Why declarative wins for infra. It is idempotent and converges to the desired state: running it again when things already match is a no-op, and it reconciles drift toward the target.

  4. 4

    The trade-off. Declarative is the norm for provisioning; imperative is still handy for one-off procedural tasks. Most IaC tools are declarative for exactly this reason.

What gets you hired

Declarative IaC means I describe the desired end state, one VPC, three subnets across 3 availability zones, one database, and the tool works out the actions needed to get there, what to create, update, or delete. Terraform and CloudFormation are declarative. Imperative IaC means I write the explicit sequence of steps, more like a script that says create this, then that, then configure this. The reason declarative dominates provisioning is idempotency and convergence. I describe the target, and running it repeatedly is safe. If reality already matches, a terraform plan comes back with 0 to add, 0 to change, 0 to destroy and nothing happens. If someone hand-edited a security group and reality has drifted, it computes the minimal set of changes, maybe 1 resource to update, to bring things back to the declared state. With a naive imperative script I have to write all the does-it-already-exist logic myself, and re-running it the second time throws errors or creates duplicates. So declarative lets me treat the config as the single source of truth and let the tool reconcile, which is exactly what I want for infrastructure that lives for years. Imperative still has a place for genuinely procedural one-off tasks, a data migration or a rotation script, but for provisioning and maintaining infrastructure, declarative is the right model, which is why every major IaC tool is built that way.

Desired state vs scripted steps Knows declarative is idempotent/converges Knows why it dominates provisioning

Then they probe: Why does declarative IaC make idempotency easier?

Practise this one
Junior

What is Terraform state, and why does it exist?

What most people say

State is a file Terraform uses to keep track of things.

Too vague. The point is that state maps your config to real resources so Terraform can compute diffs, and that it can hold sensitive data, which drives how you must store it. "Keeps track of things" misses both.

The structure behind a strong answer

  1. 1

    What it is. State is a file where Terraform records the real resources it created and maps them to your configuration, the link between code and the actual cloud objects.

  2. 2

    Why it exists. Terraform needs to know what it already manages to compute the diff on the next run, what to add, change, or destroy, without re-discovering everything from the provider each time.

  3. 3

    It tracks metadata. It stores resource IDs and attributes (and dependency info), which is how plan can show an accurate diff against reality.

  4. 4

    It is sensitive. State can contain secrets (passwords, keys) in plaintext, so it must be stored securely and access-controlled, never committed to a public repo.

What gets you hired

Terraform state is a file where Terraform records the real world resources it has created and maps each one back to the resource in your configuration. It exists because Terraform is declarative and diff based. To decide what to do on the next run, it needs to know what it already manages and the current attributes of those resources, so it compares your desired config against the state to compute the plan: what to create, update, or destroy. Without state it would have no memory of what it owns and could not reliably work out the delta. The state stores resource IDs, attributes, and dependency information, and on a modest stack of 200 or so resources that file is already several megabytes of JSON. Two consequences follow. First, state is the source of truth for what Terraform manages, so if it is lost or corrupted, Terraform loses track of live resources and may try to recreate them, which is why you protect it. Second, state often holds sensitive values, database passwords and access keys, in plaintext, so it must be encrypted at rest, access controlled, and never committed to git. Those two facts are why on any real team the state lives in a secured remote backend, an S3 bucket with versioning, encryption and a lock table, or Terraform Cloud, rather than on one person's laptop.

State maps config to real resources for diffs Knows why diffs need it Knows it holds secrets / must be secured

Then they probe: Why can you not just commit the state file to git?

Practise this one
Junior

Explain the Terraform workflow: init, plan, and apply.

What most people say

You run terraform apply to create your infrastructure.

Skipping plan is the tell. plan is the safety mechanism, a dry-run diff you review before apply, and ignoring it is how people accidentally destroy resources. init (providers/backend) is also part of the workflow.

The structure behind a strong answer

  1. 1

    init. terraform init sets up the working directory: downloads provider plugins and configures the backend (where state lives). You run it first and after adding providers/modules.

  2. 2

    plan. terraform plan computes the diff between your configuration and the current state, and shows exactly what it would create, change, or destroy, without making any changes. A dry run.

  3. 3

    apply. terraform apply executes the plan to make the real changes, after you review and confirm. You can save a plan and apply exactly that.

  4. 4

    Review the plan. The discipline is to always read the plan before applying, especially watching for unexpected destroys, since that is your chance to catch a dangerous change before it happens.

What gets you hired

There are 3 core steps. terraform init prepares the working directory: it downloads the provider plugins your config needs into .terraform and configures the backend where state is stored. You run it first and again any time you add a provider or module. terraform plan is the heart of the safety model. It refreshes state, compares your configuration against it, and prints a diff of exactly what it would create, change, or destroy, without touching anything. It is a dry run, and it ends with a line like 3 to add, 1 to change, 0 to destroy. terraform apply then executes those changes against real infrastructure, showing the plan and asking for confirmation first. You can also save a plan with -out and apply exactly that file, which is what CI does so the applied change is precisely the reviewed one. The discipline I always follow is to actually read the plan, and above all to check that destroy count. If it is not 0 and I did not expect it to be, I stop, because an innocent looking change to something like a subnet or an engine version can force replacement of a resource, and a forced replace on an RDS instance means deleting a database. The plan is your one chance to catch that. So plan, review, then apply is not a formality, it is the main guardrail Terraform gives you.

Knows plan is a dry-run diff reviewed before apply Knows init sets up providers/backend Watches for unexpected destroys

Then they probe: Why is reviewing the plan before apply so important?

Practise this one
Mid

Your team provisions infrastructure with code. Why does idempotency matter, and what breaks without it?

What most people say

Idempotency means the script runs cleanly each time, so it is just good practice to make scripts re-runnable.

It restates the word without the model. It misses the desired-state vs imperative distinction, says nothing about drift detection or state tracking, and gives no concrete failure mode, so it reads as vocabulary, not experience.

The structure behind a strong answer

  1. 1

    Define idempotency here. Applying the same configuration twice yields the same end state. The tool reconciles to a desired state rather than blindly re-running create steps.

  2. 2

    Why it matters for re-runs. Pipelines retry, applies get interrupted, and people run plan/apply repeatedly. Idempotent IaC means a second run is a safe no-op, not a duplicate resource or a hard failure.

  3. 3

    Why it matters for drift. When someone changes a resource by hand, the next apply detects the diff and reconciles back to the declared state, so the code stays the source of truth.

  4. 4

    What breaks without it. Imperative scripts that create-without-checking produce duplicates, fail on "already exists", or leave half-applied state. Recovery becomes manual cleanup instead of a re-apply.

  5. 5

    How tools achieve it. State tracking plus a plan/diff step: the tool compares declared config to recorded state and applies only the delta. Review the plan before apply so changes are intentional.

What gets you hired

Idempotency means applying the same config twice lands you in the same state, because the tool reconciles toward a declared desired state instead of re-running create steps. That matters first for re-runs. Pipelines retry, and an apply can be interrupted halfway, so the second run has to be a safe no-op that reports 0 to add, 0 to change, not a duplicate resource or an already exists error. It also matters for drift. If someone tweaks a security group in the console on a Friday, the next plan surfaces that one diff and reconciles it back, so the code stays the source of truth rather than a story about what prod used to look like. Without idempotency you are writing imperative scripts that duplicate resources or fail partway, and recovery turns into manual cleanup at 2am. The mechanism underneath is state tracking plus a plan step. The tool records what it created, diffs the declared config against that state, and applies only the delta. That is exactly why I read the plan before every apply, if a plan that should show 1 change is showing 12 destroys, something is wrong and I want to know before I touch production.

Frames IaC as desired-state reconciliation, not scripting Connects idempotency to safe retries Explains drift detection and code-as-source-of-truth

Then they probe: Someone edited a resource directly in the console. What happens on the next apply, and what should the team do?

Practise this one
Mid

Why do teams use remote state with locking instead of local state?

What most people say

Remote state lets the team access the state from anywhere.

Access is part of it but the critical reason is locking, without it, two concurrent applies corrupt the state, plus durability, encryption, and not losing state on a laptop. The corruption-prevention angle is the key point.

The structure behind a strong answer

  1. 1

    Local state does not scale to a team. Local state lives on one machine, so teammates cannot share it, it is easily lost, and it may end up in git with secrets. Fine for a solo experiment, not a team.

  2. 2

    Remote backend. A remote backend (S3, Terraform Cloud, GCS) stores state centrally so the whole team and CI use one shared, durable, encrypted copy.

  3. 3

    Locking prevents corruption. State locking ensures only one apply runs at a time. Without it, two simultaneous applies can race and corrupt the state. S3 backends pair with DynamoDB (or native lockfile) for the lock.

  4. 4

    Plus durability and security. Remote state adds encryption at rest, access control, versioning/backups, and a single source of truth, none of which a laptop file gives you.

What gets you hired

Local state is fine when one person is experimenting, but it breaks down immediately for a team. It lives on a single machine, so nobody else can use it, it is easy to lose, and it tends to end up committed to git with secrets in plaintext. So teams use a remote backend, S3, Terraform Cloud, GCS, that stores the state centrally, where every engineer and the CI pipeline read and write the same shared, durable, encrypted copy, a single source of truth. The most important addition is state locking. When you run apply, Terraform takes a lock on the state, so only one apply can run at a time. Without locking, two people, or a person and a CI job, applying at the same time can interleave their writes and corrupt the state file, which is a genuinely painful mess to recover from. With S3 you traditionally pair it with a DynamoDB table for the lock, and Terraform now also supports native S3 lockfiles, while Terraform Cloud handles locking for you. On top of locking, the remote backend gives encryption at rest, access control, and versioning so you can roll back or recover a previous state. So it is not just remote access, it is locking to prevent corruption plus durability and security, which is why no real team uses local state.

Knows local state is unshareable/loseable Centers on locking to prevent corruption Mentions durability/encryption/versioning

Then they probe: What specifically goes wrong without state locking?

Practise this one
Mid

What are Terraform modules, and how do they help?

What most people say

Modules let you reuse Terraform code.

True but thin. The interviewer wants how, encapsulating a pattern with inputs/outputs, calling it across environments to stay DRY and consistent, root vs child, and versioning, not just the word "reuse".

The structure behind a strong answer

  1. 1

    A reusable group of resources. A module is a parameterized, reusable collection of resources, for example a module that creates a complete VPC, or a standard app stack, with input variables and outputs.

  2. 2

    DRY and consistency. Instead of copy-pasting resource blocks across environments and projects, you call one module with different inputs, so a fix or a standard (tagging, naming) lives in one place.

  3. 3

    Root vs child modules. The root module is the config you run; it calls child modules. You compose infrastructure from modules rather than writing every resource flat.

  4. 4

    Sources and versioning. Modules come from local paths, a git repo, or a registry, and you pin module versions so changes are deliberate and reproducible.

What gets you hired

A Terraform module is a reusable, parameterised group of resources with defined inputs and outputs, for example one module that stands up a full VPC with its subnets and routing, or a standard application stack. Instead of copy pasting the same 20 resource blocks into dev, staging and prod, and again into every new project, I write the pattern once and call it 3 times with different input variables. That gives me two big wins. DRY, because there is no duplicated config to keep in sync, and consistency, because standards like naming, tagging, encryption and security defaults are baked into the module, so every environment inherits them and a single fix rolls out everywhere. Structurally, the root module is the configuration I actually run, and it calls child modules, so infrastructure is composed from modules rather than one giant flat main.tf. Modules can come from a local path, a git repository, or a registry, public or a private internal one. I always pin the version, so source with a version like 5.1.2 rather than a floating branch, because that keeps applies reproducible and makes upgrades a deliberate pull request instead of something that silently changes the next time upstream ships. So modules are how Terraform stays maintainable at any real scale. Encapsulate a pattern, parameterise it, version it, reuse it, instead of copy paste sprawl.

Encapsulate a pattern with inputs/outputs DRY + consistency (standards in one place) Knows root vs child + version pinning

Then they probe: How do modules pass data in and out?

Practise this one
Mid

How do you manage multiple environments (dev, staging, prod) in Terraform?

What most people say

I would use Terraform workspaces to switch between environments.

Workspaces are one option, but they share config and weaken isolation, which is risky for prod (easy to apply to the wrong workspace). A complete answer weighs workspaces vs directory-per-env and isolates prod.

The structure behind a strong answer

  1. 1

    Separate state per environment. Each environment should have its own state so a change to dev can never accidentally affect prod. The question is how you structure that.

  2. 2

    Workspaces. Terraform workspaces use the same configuration with separate state per workspace. Convenient, but the shared config makes it easy to apply to the wrong environment, and isolation is weaker.

  3. 3

    Directory (or repo) per environment. A separate directory/backend per environment, often with a shared module and per-environment variables. More isolation and a clearer blast radius, commonly preferred for prod.

  4. 4

    Keep config DRY. Either way, share the actual resource definitions through modules and vary only per-environment inputs (sizes, counts, names), so environments differ in values, not in copied code.

What gets you hired

The non negotiable is that every environment gets its own state file, so an apply to dev can never touch prod. The real question is how to structure that, and there are 2 main approaches with a clear trade off. Workspaces use one configuration with a separate state per workspace, which is convenient and low duplication, but because the config is shared it is easy to apply to the wrong environment if you forget which workspace you are in, and the isolation is weaker, since all 3 environments share a backend. The alternative, which I prefer for anything with a real production system, is a directory or even a repository and backend per environment. Each has its own state and backend, usually a thin root config that calls a shared module and supplies per environment variables. That gives stronger isolation, a clearer blast radius, and lets prod have its own credentials, its own approval gate, and stricter controls. Whichever way I go, I stay DRY by keeping the actual resource definitions in shared modules, so environments differ only in inputs. Dev might run 2 instances on a small size, prod runs 6 across 3 availability zones, but it is the same code with different values, not divergent copy pasted configs. So, workspaces for simple or low risk cases, directory per environment with isolated state for anything where prod safety matters.

Separate state per environment Knows workspaces vs directory-per-env trade-off Isolates prod + stays DRY via modules

Then they probe: What is the risk of using workspaces for prod?

Practise this one
Mid

How do you handle secrets in Terraform?

What most people say

I would mark the variables as sensitive so they are hidden.

sensitive only hides values from console output, the secret is still in the state file in plaintext. The real controls are not committing secrets, securing the state, and sourcing from a secrets manager. Relying on sensitive alone is a false sense of security.

The structure behind a strong answer

  1. 1

    Never hardcode or commit secrets. Do not put secrets in .tf files or commit .tfvars with secrets to git. That is the most common leak.

  2. 2

    Secrets land in state. Crucially, any secret Terraform handles is written to the state file in plaintext, so protecting the state, encryption, restricted access, is essential and unavoidable.

  3. 3

    Source from a secrets manager. Pull secrets at apply time from a secrets manager or Vault (via data sources or environment), or have Terraform create a secret and store it in the manager, rather than passing it in plaintext.

  4. 4

    sensitive helps but is not enough. Marking a variable or output sensitive hides it from CLI/log output, but it is still in state, so it does not replace securing the state.

What gets you hired

The first rule is never hardcode a secret in a .tf file and never commit a .tfvars with secrets to git, because that is the most common way they leak, and once it is in history it is in every clone. The subtler and more important point is that any secret Terraform touches, a generated database password, an API key it reads, ends up written into the state file in plaintext, no encryption at all. So securing state is not optional. Encryption at rest, a backend with strict access control, versioning turned on, and state never in a repo. Given that, the better pattern is to stop long lived secrets flowing through Terraform as inputs at all. I source them at apply time from a secrets manager or Vault, through a data source or an environment variable, or better, I let Terraform generate the secret and write it straight into the secrets manager so the application reads it from there and it never lives in a variable file. I do mark those variables sensitive, which stops Terraform printing the value in plan and apply output and CI logs, but I am clear about what that buys. Sensitive only hides it from the console, it does not encrypt the 1 place it really matters, the state. It is a nice to have on top, not a substitute. So the layered answer is keep secrets out of code and git, treat state as a secret and lock it down, pull from a real secrets manager, and use sensitive so nothing leaks into logs.

Knows secrets land in state in plaintext Secures state + sources from a secrets manager Knows sensitive only hides from logs

Then they probe: Why is marking a variable sensitive not enough on its own?

Practise this one
Mid

What is configuration drift, and how do you detect and handle it?

What most people say

Drift is when the infrastructure changes; you fix it by running Terraform again.

Partly right, but it does not explain the cause (manual out-of-band changes), how you detect it (plan), the choice between reverting and codifying, or the real prevention, enforcing IaC as the only change path.

The structure behind a strong answer

  1. 1

    What drift is. Drift is when the real infrastructure no longer matches your Terraform code, usually because someone made a manual change in the console (or another tool did).

  2. 2

    Detect it. terraform plan compares state and config against reality and surfaces unexpected differences. You can run plan on a schedule (or use drift detection) to catch it proactively.

  3. 3

    Handle it. Either re-apply to revert the manual change back to the declared state, or, if the change should stay, update the Terraform code to match and apply so code and reality agree again.

  4. 4

    Prevent it. The durable fix is process: make Terraform the only way infrastructure changes, restrict console write access, and run plan in CI so drift is caught and discouraged.

What gets you hired

Configuration drift is when the real infrastructure no longer matches what my Terraform code and state describe, and it almost always comes from out of band changes. Someone opens a security group in the console at 2am during an incident, or another tool edits a resource. I detect it with terraform plan, because plan diffs config and state against the live resources, so drift shows up as changes it wants to make that nobody asked for. On a real team I do not wait for the next deploy to find out. I run a plan on a schedule, typically once a day, and alert if it comes back with anything other than 0 changes. Handling it is a judgement call. If the manual change was unwanted, I apply and let Terraform revert reality back to the declared state. If the change was actually right and should stay, I update the code to reflect it and apply, so code and reality agree again and the change is now captured in version control and code review. The most important part is prevention, because chasing drift forever is a losing game. The durable fix is process and access control. Terraform becomes the only sanctioned way infrastructure changes, console write access is limited to a break glass role, and plan runs in CI on every pull request. Drift is a symptom of changes happening outside the source of truth, so the real cure is making sure they only happen through it.

Drift = reality diverges from code (manual changes) Detect with plan / scheduled checks Revert-or-codify + prevent by enforcing IaC

Then they probe: Someone fixed a setting manually in the console during an incident. How do you reconcile it?

Practise this one
Senior

How do you run Terraform safely in CI/CD?

What most people say

I would have the pipeline run terraform apply automatically when code is merged.

Auto-apply on merge is part of it, but the safe version adds plan-on-PR for review, remote state with locking, policy/security gates, and least-privilege short-lived CI credentials. Just auto-applying with no review or guardrails is risky.

The structure behind a strong answer

  1. 1

    Plan on PR. On a pull request, CI runs terraform plan and posts the diff for review, so changes are seen and approved before anything is applied, the infra equivalent of code review.

  2. 2

    Apply on merge. After approval and merge to the main branch, the pipeline runs terraform apply (ideally applying the exact saved plan), so applies are automated, audited, and tied to a reviewed commit.

  3. 3

    Remote state + locking. CI uses the shared remote backend with locking, so pipeline runs and humans cannot apply concurrently and corrupt state.

  4. 4

    Guardrails and least privilege. Add policy-as-code (OPA/Sentinel) and security scanning (tfsec/checkov) as gates, and give CI short-lived, least-privilege credentials via OIDC rather than stored long-lived keys.

What gets you hired

The model mirrors application CI/CD with infra-specific safeguards. On a pull request, the pipeline runs terraform plan and posts the diff into the PR, so a reviewer sees exactly what will change and approves it before anything happens, that is the review gate, and itself catches dangerous destroys. After approval and merge to main, the pipeline runs terraform apply, ideally applying the exact plan artifact that was reviewed, so what gets applied is precisely what was approved, and every apply is automated, logged, and traceable to a commit and an author. Underneath, CI uses the shared remote backend with state locking, so a pipeline run and a human, or two pipeline runs, can never apply concurrently and corrupt the state. Then the guardrails that make it genuinely safe: policy-as-code, OPA or Sentinel, to enforce rules like no public S3 buckets or only approved instance types, and security scanning like tfsec or checkov, both as gates that can fail the build. And critically, the pipeline authenticates with short-lived, least-privilege credentials via OIDC federation rather than long-lived cloud keys stored in CI, so there is no standing powerful credential to leak. The contrast with running apply from a laptop is that this gives review, audit, locking, policy enforcement, and no static admin keys, which is what you need for infrastructure changes.

Plan-on-PR review + apply-on-merge of the saved plan Remote state + locking in CI Policy-as-code/scanning gates + OIDC least-privilege creds

Then they probe: Why run plan on the PR rather than only applying on merge?

Practise this one
Senior

You have existing infrastructure created by hand. How do you bring it under Terraform management?

What most people say

I would write the Terraform code and run it to take over the resources.

Just applying new code would try to create duplicates or conflict with the existing resources, not adopt them. You must import them into state and write config that matches, then iterate to a zero-diff plan. The import step is the whole point.

The structure behind a strong answer

  1. 1

    Do not recreate, import. You bring existing resources under management with terraform import (or import blocks), which records the real resource in state, you do not delete and recreate production.

  2. 2

    Write matching config. Import only populates state; you still have to write Terraform configuration that matches the real resource attributes, or the next plan will want to change or destroy it.

  3. 3

    Iterate with plan to zero diff. After import, run plan and adjust the config until it shows no changes, that zero-diff plan confirms your code accurately reflects reality.

  4. 4

    Work incrementally. Do it resource by resource (or with helper tooling for bulk), starting with lower-risk pieces, since hand-writing config for a large estate is tedious and error-prone.

What gets you hired

The key thing is that you adopt the existing resources, you do not tear down and recreate production. So the process is import based. I use terraform import, or import blocks in Terraform 1.5 and newer, to bring each real resource into state, which tells Terraform that this thing is now managed at this config address. But import only populates state, it does not write your configuration, so the bigger step is authoring code whose attributes match reality. If the config does not match, the next plan will propose changing or even destroying the resource, which is exactly what you must avoid. So the loop is import, write config, run terraform plan, and adjust until the plan reports 0 to add, 0 to change, 0 to destroy. That clean zero diff plan is the signal that my code faithfully represents the real resource. I work incrementally, maybe 5 to 10 resources at a time, starting with lower risk pieces like security groups and leaving stateful things like RDS databases for last, where I also set prevent_destroy as a seatbelt. For a large estate, say a few hundred resources, I lean on helper tooling like Terraformer to generate a starting point, but I still verify every single one with plan, because generated config is a draft, not the truth. So brownfield adoption is import into state, write matching config, iterate to zero diff, never recreate.

Imports into state rather than recreating Knows import does not write config Iterates to a zero-diff plan, works incrementally

Then they probe: Why is a "zero-diff" plan the goal after importing?

Practise this one
Senior

How do you structure Terraform for a large organization to limit blast radius?

What most people say

I would keep all the infrastructure in one Terraform configuration so it is in one place.

One big state is the anti-pattern at scale: enormous slow plans, org-wide locking contention, and a blast radius covering everything. The senior approach splits state by layer and environment and shares outputs between them.

The structure behind a strong answer

  1. 1

    Do not use one giant state. A single state for the whole org is dangerous and slow: every plan is huge, applies are slow, locking serializes everyone, and one mistake can affect everything.

  2. 2

    Split state by layer and environment. Separate state per logical layer (networking, data, platform, app) and per environment, so each change touches a small, isolated state with a contained blast radius.

  3. 3

    Share between layers explicitly. Lower layers expose outputs (or you use data sources / remote state) so higher layers consume the network or cluster IDs they need, without sharing one state.

  4. 4

    Standardize and least-privilege. A shared, versioned internal module library for consistency, and per-state least-privilege credentials so the data team pipeline cannot touch networking, and vice versa.

What gets you hired

The mistake to avoid is one giant state for the whole organization. It does not scale. Every plan has to refresh and diff everything, and once you are past a few thousand resources a plan can take 10 or 15 minutes, the single lock means only one person or pipeline works at a time, and worst of all the blast radius is the entire estate. So the core principle is to split state to bound blast radius. I separate by logical layer, networking, shared data, the platform or cluster, and the applications, and by environment, so a typical state holds maybe 50 to 150 resources and a plan comes back in under a minute. An apply can only touch that one slice. Those layers still need to share information, so lower layers publish outputs and higher layers consume them through remote state or data sources. The app layer reads the VPC and subnet IDs that networking produced instead of everything living in one file. On top of that I standardize with a shared internal module library, versioned and pinned to a tag like v2.3.0, so all those separate states still build consistent, compliant infrastructure. And I scope credentials per state with least privilege, so the application pipeline literally cannot modify networking and the data pipeline cannot touch the platform. The rule I keep in mind is that the size of a state file is the size of its blast radius, so I keep states small, layered, and wired together through explicit outputs rather than one monolith.

Avoids one giant state Splits by layer + environment for blast radius Shares via outputs/remote state + module library + per-state least privilege

Then they probe: How do separate state files share data (like a VPC ID)?

Practise this one

Deployment

12 questions · Foundation, Junior, Mid, Senior
Foundation

What do CI and CD actually mean, and what is the difference between continuous delivery and continuous deployment?

What most people say

CI/CD means automating your build and deployment pipeline.

It collapses three distinct ideas into one phrase. The interviewer wants CI (integrate + test) separated from continuous delivery (always releasable) versus continuous deployment (auto-released), which is the actual question.

The structure behind a strong answer

  1. 1

    Continuous integration. CI is merging code into a shared mainline frequently, with an automated build and tests on every commit, so integration problems surface immediately rather than at a big merge.

  2. 2

    Continuous delivery. Every change that passes the pipeline is kept in an always-releasable state, the release to production is a deliberate (often one-click) decision.

  3. 3

    Continuous deployment. Goes one step further: every change that passes the pipeline is automatically deployed to production with no manual gate.

  4. 4

    The distinction. Delivery means always ready to ship; deployment means actually shipped automatically. The pipeline can be identical; the difference is whether a human approves the final push.

What gets you hired

These are three related but distinct ideas. Continuous integration means merging into a shared mainline frequently, ideally at least once a day per developer, and every commit triggers an automated build and test run. If that pipeline stays under about 10 minutes, integration problems and broken builds surface immediately instead of piling up until a painful big-bang merge. Continuous delivery builds on CI: every change that passes the full pipeline is kept in an always-releasable state, so we could ship at any moment, but the actual release is a deliberate human decision, usually a 1-click approval. Continuous deployment removes that gate entirely: every change that passes the pipeline goes straight to production, no manual approval, and a mature team might ship 20 or 30 times a day that way. The commonly confused point is the difference between the two CDs. Delivery means always ready to ship. Deployment means automatically shipped. The pipeline can be literally identical, and the only difference is whether a person presses the button at the end. So CI is the integrate-and-test foundation, and then you choose delivery or deployment based on how much you trust your automated tests and how comfortable you are auto-releasing. Regulated environments often stay at delivery with a manual gate, while teams with strong test suites go all the way to deployment.

Separates CI from the two CDs Delivery = always releasable, deployment = auto-released Knows the pipeline can be identical, the gate differs

Then they probe: When would a team choose continuous delivery over continuous deployment?

Practise this one
Foundation

Why should you build an artifact once and promote the same one through environments?

What most people say

It is faster to build once instead of building for each environment.

Speed is a minor benefit. The real reason is correctness: rebuilding per environment can produce different binaries, so you might test one thing and ship another. Build-once guarantees what you tested is what you ship.

The structure behind a strong answer

  1. 1

    Build once. Compile and package the application a single time into an immutable artifact (a container image, a versioned package), then deploy that same artifact everywhere.

  2. 2

    Promote, do not rebuild. Move the identical artifact through dev, staging, and prod. Rebuilding per environment can pull different dependency versions or build settings, breaking the guarantee.

  3. 3

    Why it matters. It eliminates a whole class of works-in-staging-breaks-in-prod bugs, because what you tested is byte-for-byte what you ship.

  4. 4

    Config is injected, not baked. Environment differences (URLs, secrets, sizes) come from configuration injected at deploy time, so the artifact stays identical across environments.

What gets you hired

The principle is build once, deploy many. I compile and package the application a single time into an immutable, versioned artifact, a container image tagged with the git SHA or version 1.4.2, and then I promote that exact same artifact through all 3 environments, dev, then staging, then production. This matters for correctness, not speed. If I rebuild separately for each environment, the builds can differ subtly. A dependency resolves to a newer patch version, a build flag changes, the base image gets a new digest overnight, and now the binary in production is not the one I tested in staging. That is exactly how you get the maddening works-in-staging-breaks-in-prod class of bug. Building once and promoting the identical artifact eliminates that whole category, because what I tested is byte for byte what ships. The complement is that environment-specific differences, database URLs, secrets, instance sizes, feature toggles, must come from configuration injected at deploy or run time, not baked into the image, so the artifact really is identical everywhere and only the surrounding config changes. So I version every artifact, push it to a registry, and promote that one immutable build. It also makes rollback trivial, since the previous 10 images are sitting right there and redeploying one is usually a 30 second operation.

Immutable build-once artifact promoted everywhere Knows rebuilding causes staging/prod drift Config injected, not baked

Then they probe: How do you handle environment differences if the artifact is identical everywhere?

Practise this one
Junior

What stages should a good CI/CD pipeline have?

What most people say

The pipeline runs the tests and then deploys the code.

Too coarse. A good pipeline has ordered stages, build, fast-then-slow tests, static/security scans, package, deploy, each gating the next, and is designed for fast feedback (fail fast). "Runs tests and deploys" misses the structure.

The structure behind a strong answer

  1. 1

    Build. Compile and build the application into an artifact, the first gate; if it does not build, stop immediately.

  2. 2

    Test. Run tests fastest-first: unit tests, then integration tests. Fail fast so developers get feedback in minutes, not after a long suite.

  3. 3

    Analyze and scan. Static analysis/linting and security scans (SAST, dependency/SCA, secret scanning), so quality and security gate the pipeline too.

  4. 4

    Package and publish. Package the immutable artifact and publish it to a registry, then deploy through environments (often with a manual gate before prod).

What gets you hired

I think of a pipeline as ordered stages, each gating the next, arranged so failures surface as early and cheaply as possible. It starts with build: compile and produce the artifact, and if that fails everything stops. Then tests, ordered fastest first. Unit tests run first because they finish in a couple of minutes and catch most issues, then the slower integration tests. The principle is fail fast, so a developer learns about a typo in 2 minutes rather than 20. Alongside or after tests come the quality and security gates: static analysis and linting, SAST on the code, SCA on dependencies for known CVEs, and secret scanning so credentials never slip through. I set those to fail the build on anything high or critical, and any one of them can stop the pipeline. Then package: build one immutable, versioned artifact and publish it to a registry. Finally the deploy stages, promoting that single artifact through dev, staging, and prod, usually with a manual approval gate before production if we are doing continuous delivery rather than full continuous deployment. My rough target is under 10 minutes from commit to a deployable artifact, because a pipeline that takes an hour is a pipeline people work around. The two principles threaded through all of it are fail fast, cheap checks before expensive ones, and gating, where each stage must pass before the next one runs.

Names build/test/scan/package/deploy stages Fastest-tests-first / fail fast Each stage gates the next + security gates

Then they probe: Why run unit tests before integration and e2e tests?

Practise this one
Junior

A deploy went bad in production. How do you handle rollback, and how do you make rollback easy?

What most people say

I would push a hotfix to production to fix the problem.

Forward-fixing live under incident pressure is risky and slow, you are coding against a fire. The safer move is to roll back to the last known-good version first to restore service, then fix calmly. Rollback should be the reflex.

The structure behind a strong answer

  1. 1

    Roll back first, debug later. Under a production incident, restore service by rolling back to the last known-good version, then investigate calmly. Do not try to forward-fix live under pressure.

  2. 2

    Redeploy the previous artifact. Because you promote immutable, versioned artifacts, rollback is just redeploying the prior known-good artifact, fast and exactly what ran before.

  3. 3

    Mind the database. Schema changes complicate rollback, you cannot un-migrate easily. Use backward-compatible migrations (expand/contract) so you can roll back the code without rolling back the schema.

  4. 4

    Automate it. Wire automatic rollback on failed health checks or canary analysis (atomic deploys), so a bad release is reverted without waiting for a human to notice.

What gets you hired

My first instinct in a production incident is to roll back, not forward fix. Restoring service beats finding root cause in the moment, and writing a hotfix live, under pressure, against a broken system is how you make things worse. So I roll back to the last known good version to stop the bleeding, aiming for a couple of minutes, then investigate calmly once users are fine. What makes that easy is build once, immutable artifacts. Because I promoted a versioned artifact and the previous builds are still in the registry, rollback is just redeploying the prior image tag. No rebuild, no surprises, and it is exactly what was running before. The one real complication is the database, because schema changes are not as easily reversed as code. That is why I make migrations backward compatible using expand and contract, so the old code still runs against the new schema, which means I can roll the app back without rolling back the database. And ideally no human has to notice at all. I wire automatic rollback into the deploy, so if health checks fail or canary analysis sees error rate go above about 1 percent over a 5 minute bake, the system reverts the release on its own. So the package is: roll back over forward fix, immutable artifacts to make it trivial, backward compatible migrations so the database does not block it, and automation so it happens in minutes and not in an hour.

Rolls back to known-good before forward-fixing Immutable artifacts make rollback trivial Backward-compatible DB changes + automated rollback

Then they probe: Why are database changes the hard part of rollback?

Practise this one
Junior

What are feature flags, and how do they decouple deployment from release?

What most people say

Feature flags let you turn features on and off in the app.

It states the mechanism but not the value: decoupling deploy from release, gradual rollout, an instant kill switch, and enabling trunk-based development, nor the cost (flag debt). The why is what the question is after.

The structure behind a strong answer

  1. 1

    Decouple deploy from release. A feature flag lets you deploy code to production with a feature turned off, then enable it separately, so deploying and releasing become two independent actions.

  2. 2

    Gradual rollout. Turn the feature on for a small cohort, then ramp up, watching metrics, so you expose a risky change progressively instead of all at once.

  3. 3

    Instant kill switch. If something goes wrong, flip the flag off immediately, no redeploy or rollback needed, which is much faster than shipping a fix.

  4. 4

    The cost. Flags add complexity and accumulate as flag debt (stale flags, conditional branches), so you must remove them once a feature is fully rolled out.

What gets you hired

A feature flag is a runtime toggle that turns a feature on or off without deploying new code, and its real power is decoupling deployment from release. Normally shipping code and exposing a feature are the same event. With flags they are independent. I deploy the code to production with the flag off, dark, so it is live but invisible, and then decide separately when and to whom it turns on. That unlocks a few things. Gradual rollout: I enable it for internal users, then 1 percent of traffic, then 10, then 50, watching error rate and latency at each step, so a risky change is exposed progressively instead of to everyone at once. An instant kill switch: if something breaks I flip the flag off and it takes effect in seconds, which is far faster than a rollback or a hotfix because no deploy is involved. And it enables trunk based development, since unfinished work can be merged to main behind an off flag without touching users, which keeps integration continuous. The honest cost is complexity. Every flag is a branch in the code, so 10 flags is a lot of untested combinations, and they pile up as flag debt. So I treat flags as temporary and remove them once a feature is at 100 percent and stable, usually within about 2 sprints. Feature flags turn release into a runtime decision separate from deployment, as long as you clean them up.

Decouples deploy from release Gradual rollout + instant kill switch + enables trunk-based Acknowledges flag debt / cleanup

Then they probe: Why is a feature flag faster than a rollback for disabling a broken feature?

Practise this one
Mid

Compare blue-green and canary deployments. When would you reach for each?

What most people say

Blue-green is two environments and canary is a gradual rollout, so I would use whichever the team already uses.

It defines the terms but ducks the actual question, the trade-off. No mention of blast radius, rollback speed, cost of parallel capacity, or the observability canary depends on. Deferring to "whatever the team uses" signals no independent judgment.

The structure behind a strong answer

  1. 1

    Define blue-green. Stand up a full second environment (green) with the new version, then switch all traffic at once. Rollback is an instant traffic switch back to blue.

  2. 2

    Define canary. Release the new version to a small slice of traffic (say 5 percent), watch metrics, then ramp gradually to 100 percent or roll back.

  3. 3

    Contrast the trade-offs. Blue-green gives fast, clean rollback but doubles capacity cost and exposes everyone the instant you switch. Canary limits blast radius and catches issues on real traffic but is slower and needs good metrics and routing.

  4. 4

    Match to the situation. Blue-green when you need atomic cutover and instant rollback and can afford parallel capacity; canary when you want to limit blast radius for a risky change and have the observability to judge the slice.

  5. 5

    Note the shared requirement. Both need backward-compatible changes (especially DB migrations) so old and new versions can run side by side during the switch or ramp.

What gets you hired

Blue-green stands up a full second environment on the new version and switches all traffic at once; rollback is just switching back, so it is fast and clean, but you pay for double capacity and everyone is exposed the moment you cut over. Canary releases to a small slice, maybe 5 percent, watches the metrics, then ramps; it limits blast radius and catches problems on real traffic, but it is slower and only works if you have solid metrics and traffic routing to judge the slice. So I reach for blue-green when I need an atomic cutover with instant rollback and can afford the parallel capacity, and canary for a risky change where I want to contain the blast radius and I trust my observability. Either way the change has to be backward compatible, especially DB migrations, so both versions can run side by side during the switch or ramp.

Frames it as a risk-vs-cost trade-off Names blast radius and rollback speed explicitly Calls out the parallel-capacity cost of blue-green

Then they probe: Your canary slice looks fine but the issue only appears at full load. How do you reduce that risk?

Practise this one
Mid

How does your branching strategy affect continuous delivery? Trunk-based versus long-lived branches.

What most people say

I would use GitFlow with feature, develop, and release branches to keep things organized.

GitFlow batches changes into long-lived branches and big merges, which works against continuous delivery. For frequent, low-risk deploys you want trunk-based development with short-lived branches and flags. The answer should weigh that.

The structure behind a strong answer

  1. 1

    Trunk-based development. Everyone integrates to main frequently via short-lived branches (or directly), merging at least daily, with unfinished work hidden behind feature flags.

  2. 2

    Why it enables CD. Frequent small merges keep integration continuous and conflicts tiny, so main is always releasable, which is the prerequisite for continuous delivery/deployment.

  3. 3

    Long-lived branches (GitFlow). Feature, develop, and release branches live for days or weeks, batching many changes that merge in big, risky chunks, with painful conflicts and delayed integration.

  4. 4

    The trade-off. Trunk-based suits fast, frequent delivery and is the modern norm for CD; GitFlow suits versioned/released software (libraries, multiple supported versions) but slows continuous delivery.

What gets you hired

Branching strategy directly affects continuous delivery, because it decides how often code actually integrates. Trunk-based development means everyone integrates into main very frequently, through branches that live hours to a day at most, merging at least once a day, and any unfinished work sits behind a feature flag so it can be merged safely without being exposed. That is what enables CD: small merges, often a few dozen lines each, keep conflicts tiny and keep main in an always-releasable state, which is the precondition for continuous delivery. The contrast is GitFlow and other long-lived branch models, with feature branches, a develop branch and release branches living for 2 or 3 weeks and accumulating hundreds of commits before merging. That batches work into big, infrequent, risky merges with painful conflicts, and it delays integration, so you find out things do not fit together late, which is the opposite of what CD needs. On the teams I have seen do this well, trunk-based took us from a release every 2 weeks to roughly 10 deploys a day, and each change was small enough to test and roll back in minutes. GitFlow still has a legitimate place for software with explicit versioned releases, a library or packaged product where you support 3 or 4 release lines in the field at once. But for a continuously deployed service, trunk-based with feature flags is what makes frequent, safe delivery possible.

Trunk-based = frequent small merges + flags Knows it keeps main always releasable (enables CD) Knows GitFlow batches/big merges + when it fits

Then they probe: How do you merge unfinished work to main without breaking things in trunk-based development?

Practise this one
Mid

How do you promote a build through environments, and why does staging need to match production?

What most people say

You test in staging and then deploy to production if it works.

It misses promoting the same artifact (not rebuilding) and, crucially, that staging must resemble production, if staging differs significantly, passing there tells you little. Parity is the point of the question.

The structure behind a strong answer

  1. 1

    Promote one artifact. Deploy the same immutable artifact through a sequence of environments, dev, staging, prod, gaining confidence at each stage rather than rebuilding.

  2. 2

    Each environment adds a check. Lower environments run cheaper/faster validation; higher ones run fuller integration, performance, or manual checks before production.

  3. 3

    Parity matters. Staging should mirror production (same topology, similar data shape, same config approach) so that passing in staging actually predicts success in prod.

  4. 4

    Config differs, artifact does not. Only environment configuration (endpoints, secrets, sizes) changes between environments; the artifact stays identical so the test is meaningful.

What gets you hired

Promotion means taking one immutable artifact and moving it through dev, then staging, then production, gaining confidence at each step, and critically not rebuilding along the way. The same image digest that passed dev is the one that reaches prod, so what runs in production is exactly what passed every earlier gate. Each environment adds validation matched to its cost. Dev runs the fast cheap checks, unit tests in under 5 minutes so developers can iterate, while staging runs the fuller integration suite, a load test at something like 2 times expected peak, and any manual acceptance testing before the production gate. Staging has to match production because the whole value of a pre-prod environment is predictive. Passing there only means something if staging is close enough to prod that success predicts success. If staging holds 10,000 rows and prod holds 50 million, or the topology differs, or a dependency is stubbed, a green run tells you almost nothing, and you get the classic surprise where everything passed in staging and then a query that took 20 milliseconds on the small dataset took 8 seconds in prod. So I push for parity: the same architecture and topology, a representative data shape, the same configuration approach, with only environment specific values differing, endpoints, secrets, instance sizes. Those differences come from injected configuration, never a different build, so the artifact stays identical and the test stays honest.

Promotes the same artifact, not rebuild Each env adds validation Insists on staging/prod parity, only config differs

Then they probe: What happens when staging diverges significantly from production?

Practise this one
Mid

How do you structure automated testing in a CI/CD pipeline?

What most people say

I would write end-to-end tests that test the whole system to make sure it works.

An e2e-heavy suite (the ice-cream cone) is slow, flaky, and expensive, which makes the pipeline painful and the results distrusted. The test pyramid, mostly fast unit tests, is what gives fast, reliable feedback.

The structure behind a strong answer

  1. 1

    The test pyramid. Many fast, isolated unit tests at the base, fewer integration tests in the middle, and a small number of end-to-end tests at the top.

  2. 2

    Why that shape. Unit tests are fast, cheap, and stable; e2e tests are slow, expensive, and flaky, so you want most coverage from the cheap layer and only critical-path coverage from e2e.

  3. 3

    Fast feedback, fail fast. Run the fast tests first so a break is caught in minutes, and only run slow integration/e2e suites once the cheap checks pass.

  4. 4

    Avoid the ice-cream cone. The anti-pattern is mostly slow e2e tests with few unit tests, slow pipelines, flaky failures, and developers ignoring the results.

What gets you hired

I structure it as the test pyramid. The base is a large number of fast, isolated unit tests that exercise individual functions and logic, the middle is a smaller set of integration tests that check components working together, a service and its database, two services talking, and the top is a small number of end-to-end tests that exercise critical user journeys through the whole system. The shape is deliberate because of the economics of each layer: unit tests are fast, cheap to run, and stable, so they give you a lot of confidence per second, whereas end-to-end tests are slow, expensive, and inherently flaky because they depend on the whole system being up and behaving, so you want the bulk of your coverage from the cheap, reliable layer and reserve e2e for verifying the handful of flows that absolutely must work. In the pipeline I order them for fast feedback and fail fast: run the fast unit tests first so a broken build is caught within a couple of minutes, and only spend time on the slow integration and e2e suites once the cheap checks have passed. The anti-pattern to avoid is the inverted pyramid, or ice-cream cone, mostly slow end-to-end tests with few unit tests, which gives you slow pipelines, frequent flaky failures, and the worst outcome of all, developers losing trust in the test results and starting to ignore or rerun them, which defeats the entire purpose. So the goal is high confidence with fast, reliable feedback, which the pyramid shape delivers and the ice-cream cone destroys.

Knows the test pyramid shape + rationale Fast-first / fail fast ordering Names the ice-cream-cone anti-pattern

Then they probe: Why not rely mostly on end-to-end tests?

Practise this one
Mid

How do you deploy a change that alters an API or contract without breaking consumers?

What most people say

I would update the API and tell the consumers to update their code.

Coordinating a simultaneous breaking change across producer and all consumers is fragile and causes outages during the rolling window where both versions run. The safe approach is backward-compatible changes and expand/contract, not a flag-day break.

The structure behind a strong answer

  1. 1

    Old and new run together. During a rolling deploy, and across services that deploy independently, old and new versions run simultaneously, so a change must work for both.

  2. 2

    Make changes additive. Prefer backward-compatible changes: add new optional fields or endpoints rather than removing or renaming, so existing consumers keep working.

  3. 3

    Expand and contract for breaking changes. To change a contract, expand (add the new alongside the old), migrate consumers over, then contract (remove the old) in a later deploy, never break in one step.

  4. 4

    Version and tolerate. Version APIs when you must support incompatible changes, and have consumers be tolerant readers (ignore unknown fields), so producers can evolve safely.

What gets you hired

The core realization is that you almost never have a clean cutover: during a rolling deploy the old and new versions of a service run at the same time, and across a microservices system, services deploy independently, so at any moment there are old and new callers and old and new servers coexisting. That means a change to an API or contract has to work for both old and new simultaneously, or you get errors during the transition window. So I default to backward-compatible, additive changes: add a new optional field or a new endpoint rather than removing, renaming, or changing the type of an existing one, so existing consumers are unaffected. When a change genuinely is breaking, I use expand and contract: first expand by introducing the new alongside the old, for example add the new field while keeping the old one populated, or add a v2 endpoint while v1 still works, then migrate the consumers over to the new version, and only once nothing uses the old form do I contract by removing it, in a separate later deploy. The key is never to break and remove in a single step while old code is still live. I support this with API versioning when I must carry incompatible versions in parallel, and by making consumers tolerant readers that ignore unknown fields, which lets the producer add fields freely without breaking anyone. So the principle is evolve contracts in compatible steps, because in a rolling, independently-deployed world there is always a window where both versions are live, and a flag-day breaking change in that window is an outage.

Knows old+new run together (rolling/independent deploys) Additive/backward-compatible + expand/contract API versioning + tolerant readers

Then they probe: Why is a simultaneous breaking change across producer and consumers so risky?

Practise this one
Senior

How do you measure the performance of a delivery process? Mention the DORA metrics.

What most people say

I would measure how often we deploy and how many bugs we have.

It gropes toward two metrics but misses the full DORA set (lead time, change failure rate, MTTR) and the central insight that speed and stability correlate, frequent small deploys are safer, not riskier, which is the senior point.

The structure behind a strong answer

  1. 1

    The four DORA metrics. Deployment frequency and lead time for changes (the speed/throughput pair), and change failure rate and time to restore service (the stability pair).

  2. 2

    Speed: frequency and lead time. How often you deploy, and how long from a commit to it running in production. Elite teams deploy on demand with lead times of hours.

  3. 3

    Stability: failure rate and MTTR. What fraction of deploys cause a failure, and how fast you recover when one does. Elite teams have low failure rates and recover in under an hour.

  4. 4

    Speed and stability go together. The key insight is they are not a trade-off, teams that deploy frequently in small batches are also more stable, because small changes are lower-risk and easier to fix. Use the metrics to drive improvement.

What gets you hired

The standard, research backed way to measure delivery performance is the four DORA metrics, and they split into two pairs. The speed pair is deployment frequency, how often you ship to production, and lead time for changes, the clock from commit to running in production. The stability pair is change failure rate, the percentage of deploys that cause a failure needing remediation, and time to restore service, essentially MTTR for deploys. Elite performers deploy on demand, many times a day, with lead times under an hour, change failure rates in the 0 to 15 percent band, and recovery in under an hour. Low performers are deploying once a month with lead times of weeks. The insight I would emphasize is that the research shows speed and stability are not a trade off, they correlate positively, the teams deploying most often are also the most stable. That sounds backwards if you think of deploys as risky, but it makes sense. Frequent deploys mean small batches, and a 50 line change is easier to test, easier to reason about, and easier to roll back than a release that bundles 300 commits into one high risk event nobody can debug at 2 in the morning. So I use DORA both to see where we are and to argue for the practices that improve all four at once, smaller batches, more automation, better testing and observability, instead of treating speed as something we trade against reliability.

Names all four DORA metrics in the two pairs Knows speed and stability correlate Links it to small batch size + uses metrics to improve

Then they probe: Why do frequent deploys tend to be more stable, not less?

Practise this one
Senior

How do you build the confidence to deploy to production frequently, even on a Friday?

What most people say

I would avoid deploying on Fridays to reduce the risk of a weekend incident.

A Friday freeze treats deployment as inherently dangerous and is a symptom of weak practices. With small batches, strong tests, progressive rollout, and automated rollback, any day is safe, which is the senior position.

The structure behind a strong answer

  1. 1

    Small batches. Deploy small, frequent changes. A small change is low-risk, easy to reason about, and easy to roll back, whereas big rare releases bundle risk.

  2. 2

    Strong automated safety net. A trustworthy automated test suite (the pyramid) plus security and quality gates, so a passing pipeline genuinely means the change is safe to ship.

  3. 3

    Progressive rollout with observability. Roll out gradually (canary) with health checks and good monitoring, so a bad change is caught on a small slice and is visible immediately.

  4. 4

    Fast, automated rollback. Automatic rollback on failed health checks/canary analysis, plus feature flags as a kill switch, so recovery is fast and does not depend on a human noticing.

What gets you hired

The honest answer is that a no-Friday-deploys rule is a symptom, not a solution, it says you do not trust your deployment process, and the right move is to fix the process so any day is safe, rather than to freeze. Confidence to deploy frequently comes from a few reinforcing practices. First, small batches: deploy small, frequent changes, because a small change is inherently low-risk, easy to understand, quick to test, and trivial to roll back, whereas the scary deploys are the big rare ones that bundle a month of changes into one high-risk event. Second, a strong automated safety net: a trustworthy test suite shaped as a pyramid plus security and quality gates, so that a green pipeline genuinely means the change is safe and I am not relying on manual heroics. Third, progressive rollout with observability: I release gradually, a canary to a small slice of traffic, with health checks and good monitoring of the golden signals, so if a change is bad it shows up on a tiny fraction of users and is immediately visible, not discovered by customers. Fourth, fast and automated recovery: automatic rollback when health checks or canary analysis fail, plus feature flags as an instant kill switch, so recovery does not depend on a human being awake to notice, which is exactly what makes Friday or 5pm irrelevant. Put together, these are also what the DORA research shows distinguishes elite teams, they deploy frequently and are more stable because of these practices. So my answer is that confidence is engineered, small batches, real automated testing, canary plus observability, and automated rollback, and once you have them, the day of the week stops mattering.

Rejects the freeze, fixes the process Small batches + strong tests + canary/observability + automated rollback Ties it to DORA / engineered confidence

Then they probe: What does a Friday-deploy freeze actually tell you about a team?

Practise this one

Storage

12 questions · Foundation, Junior, Mid, Senior
Foundation

What is the difference between durability and availability for storage?

What most people say

They both mean how reliable your storage is.

Conflating them is the mistake. Durability is about not losing data (eleven nines); availability is about being able to reach it now (often three nines). They are different guarantees with different numbers and mechanisms.

The structure behind a strong answer

  1. 1

    Durability. The probability that your data is not lost over time. Object stores quote very high durability, often eleven nines (99.999999999%), achieved through redundancy.

  2. 2

    Availability. The probability that you can access the data right now. Typically lower, for example 99.9 percent, because serving can be interrupted even when nothing is lost.

  3. 3

    They are independent. Data can be perfectly durable but temporarily unavailable (an outage in the serving path) and you have lost nothing, it comes back. Losing access is not the same as losing data.

  4. 4

    Different mechanisms. Durability comes from redundant copies across failure domains; availability comes from redundant, healthy serving paths and failover.

What gets you hired

They sound similar but are two distinct guarantees with very different numbers. Durability is the probability that your data is not lost over time, that the bytes survive, and cloud object stores quote extremely high durability, commonly eleven nines, 99.999999999 percent, which they achieve by storing redundant copies across many devices and availability zones. Availability is the probability that you can actually access the data at a given moment, and it is typically a lower number, like 99.9 percent, because the serving path can be interrupted, a regional issue, maintenance, by something that does not lose any data. The key insight is that they are independent: data can be perfectly durable but temporarily unavailable, an outage means you cannot read your objects for a while, but nothing is lost and access returns. So losing availability is not losing data. They also come from different mechanisms: durability is about redundancy of the stored bytes across failure domains so no single failure loses data, while availability is about having redundant, healthy serving paths and fast failover so requests can still be served. When I design or evaluate storage I treat them as separate requirements, how catastrophic is permanent loss versus how tolerant am I of a temporary inability to read, because the answers and the engineering for each are different.

Durability = not lost, availability = reachable now Knows the different magnitudes (11 nines vs ~3) Knows data can be durable but unavailable

Then they probe: Can data be durable but unavailable? Give an example.

Practise this one
Foundation

What is the difference between backup and replication, and why is replication not a backup?

What most people say

Replication makes copies, so it already serves as a backup.

This is the dangerous misconception. Replication copies your data in real time, including a bad delete or ransomware encryption, so every replica is corrupted too. Only a point-in-time backup lets you recover from logical errors.

The structure behind a strong answer

  1. 1

    Replication. Keeps near-real-time copies of data on other nodes/regions for availability, durability, and failover. The copies track the source continuously.

  2. 2

    Why replication is not a backup. Because it is real-time, it faithfully replicates mistakes too, an accidental delete, a corruption, a ransomware encryption is instantly copied to every replica. There is no earlier version to recover.

  3. 3

    Backup. A point-in-time copy you can restore from. It protects against logical errors and corruption precisely because it captures a prior state, not just the current one.

  4. 4

    You need both. Replication for availability and hardware failure; backups (ideally immutable/retained) for recovering from human error, corruption, and ransomware. They solve different problems.

What gets you hired

They solve genuinely different problems, and conflating them is dangerous. Replication keeps near real-time copies on other nodes or in other regions, often within a second or two of the primary, and it is great for availability, durability against hardware failure, and failover. If a disk or a whole availability zone dies, a replica takes over with little or no data loss. But precisely because it is fast and faithful, replication is not a backup. If I accidentally drop a table, or a bug corrupts rows, or ransomware encrypts the files, that change lands on every replica within seconds, so all 3 copies are now equally deleted, corrupted, or encrypted, and there is no earlier good version to go back to. A backup is different in kind: it is a point-in-time copy I can restore from, capturing the state as of some earlier moment, which is exactly what lets me recover from logical errors by restoring from before the damage. Good systems also keep continuous point-in-time recovery, so I can rewind to any second in the last 7 or 35 days. So my rule is that replication protects against losing the hardware, backups protect against losing the data to a mistake, and I want both. And good backups go further: retained over time, and ideally immutable or air-gapped, so an attacker with production credentials cannot delete the backups too, which is the failure mode that takes companies down.

Knows replication propagates mistakes in real time Backup = point-in-time recovery from logical errors Wants both + immutable backups

Then they probe: You have three replicas and someone runs a bad DELETE. What happens?

Practise this one
Junior

Explain the difference between object, block, and file storage, and give one good use case for each.

What most people say

Object is S3, block is a hard drive, file is for files. You mostly just use S3.

It names products without the access model and defaults to one type for everything. The whole point is matching the access pattern (HTTP key lookup vs an attached volume vs a shared mount) to the workload, which this skips.

The structure behind a strong answer

  1. 1

    Object storage. Data stored as objects (data plus metadata) accessed over an HTTP API by key, not mounted as a disk. Massively scalable and cheap, but you replace whole objects rather than editing in place. Examples: S3, Azure Blob.

  2. 2

    Block storage. Raw volumes you attach to one instance and format with a filesystem, read and written in blocks. Low latency, good for databases and boot disks. Examples: EBS, Azure managed disks.

  3. 3

    File storage. A shared filesystem accessed over NFS or SMB that many instances can mount at once and see the same paths. Good when several machines need shared files. Examples: EFS, Azure Files.

  4. 4

    Match to workload. Object for backups, images, static assets, and large unstructured data. Block for a database or an OS disk that needs one fast attached volume. File for a shared directory many servers read and write together.

What gets you hired

Object storage holds data as objects with metadata, accessed over an HTTP API by key, not mounted as a disk. It is cheap and scales huge but you replace whole objects, so it suits backups, images, and static assets, like S3 or Azure Blob. Block storage is a raw volume you attach to one instance and format yourself, low latency, ideal for a database or an OS boot disk, like EBS or Azure managed disks. File storage is a shared filesystem over NFS or SMB that many instances can mount and see the same paths at once, good for a shared directory across servers, like EFS or Azure Files. The deciding factor is the access pattern: key lookup over HTTP, one fast attached volume, or a shared mount.

Describes the access model, not just product names Gives a fitting use case for each type Knows block is single-attach and file is shared-mount

Then they probe: Your team wants to run a relational database directly on object storage to save money. What do you tell them?

Practise this one
Junior

How does object storage achieve such high durability?

What most people say

It keeps the data on reliable disks in the data center.

Reliable disks alone do not give eleven nines, disks fail constantly at scale. Durability comes from redundancy across failure domains (copies or erasure coding), checksums for corruption, and automatic self-healing. The mechanism is the answer.

The structure behind a strong answer

  1. 1

    Redundancy across failure domains. The data is stored in multiple copies spread across many devices and availability zones, so no single disk, server, or zone failure can lose it.

  2. 2

    Erasure coding. Instead of full copies, data is often split into shards plus parity shards, so the original can be reconstructed from any sufficient subset, durability with less storage overhead than full replication.

  3. 3

    Integrity checking. Checksums detect bit rot and silent corruption; the system continuously verifies and repairs from healthy copies/shards.

  4. 4

    Self-healing. When a device fails, the system automatically re-replicates or rebuilds the missing data from the remaining copies, restoring the redundancy level.

What gets you hired

High durability does not come from reliable hardware. At cloud scale disks are failing constantly, so it comes from redundancy and active integrity management. Data is stored redundantly across many independent devices and across at least 3 availability zones, so no single disk, server, rack, or even zone failure can lose the object, there is always another copy. Often that is not full replication but erasure coding: the object is split into data shards plus parity shards, say 10 data and 4 parity, spread across failure domains, and the original can be rebuilt from any sufficient subset. That gives equal or better durability than 3 full copies at around 1.4 times the raw size instead of 3 times. On top of redundancy the system checksums everything to catch bit rot and silent corruption, continuously scrubbing stored data and repairing any bad shard from the healthy ones. And it self heals: when a device dies, the system automatically rebuilds the missing shards from the survivors to get back to the target redundancy, so it is always converging back to full protection rather than quietly degrading. So the famous 11 nines, 99.999999999 percent durability, meaning you would expect to lose about 1 object in 10 million every 10,000 years, is the product of spreading data across failure domains, erasure coding for efficient redundancy, checksums to catch corruption, and automatic repair. It is not trusting a good disk.

Redundancy across failure domains Knows erasure coding Checksums + self-healing repair

Then they probe: What is erasure coding and why use it over full replication?

Practise this one
Junior

What consistency guarantees does object storage give, and why does it matter?

What most people say

Object storage is consistent, so a read always returns what you wrote.

Not universally true, object stores have historically been eventually consistent, where a read right after a write could be stale or a 404. Assuming strong consistency everywhere causes intermittent bugs. You must check the guarantee.

The structure behind a strong answer

  1. 1

    Strong vs eventual. Strong read-after-write consistency means a read immediately reflects the latest write. Eventual consistency means a read shortly after a write may briefly return stale data or a not-found.

  2. 2

    The historical gotcha. Some object stores were eventually consistent, so a workflow that wrote an object and immediately read or listed it could get a 404 or an old version, causing intermittent, hard-to-reproduce bugs.

  3. 3

    Modern state. Major object stores now provide strong read-after-write consistency for new objects (S3 has since 2020), but you should never assume, confirm the guarantee of the store you use.

  4. 4

    Design accordingly. Under eventual consistency, do not depend on immediately reading or listing what you just wrote; add retries, or pass data along directly rather than round-tripping through the store.

What gets you hired

It depends on the store, and getting this wrong causes nasty intermittent bugs, so I never assume. Strong read-after-write consistency means that the moment a write succeeds, any subsequent read sees the new data. Eventual consistency means that for a short window after a write, a read, or especially a list, might return stale data or even a not-found for an object you just created, because the change has not propagated everywhere yet. The classic gotcha is a pipeline that writes an object to the store and then immediately reads it back or lists the bucket expecting to see it, under eventual consistency that occasionally returns a 404 or an old version, and it fails intermittently in a way that is maddening to reproduce because it depends on timing. The good news is that the major object stores have largely moved to strong read-after-write consistency, S3 has provided it for all operations since late 2020, but the lesson stands: confirm the consistency guarantee of whatever store you are using rather than assuming. And if I am on an eventually-consistent store or path, I design for it: do not depend on immediately reading or listing what I just wrote, build in retries with backoff for the read, or better, pass the data along directly in the workflow instead of round-tripping it through the store and racing the propagation.

Distinguishes strong vs eventual Knows the write-then-read 404 gotcha Confirms the guarantee + designs for eventual

Then they probe: What bug does eventual consistency cause in a write-then-read workflow?

Practise this one
Junior

For block storage, what is the difference between IOPS and throughput, and how does it affect volume choice?

What most people say

They both measure how fast the storage is, so faster is better.

They measure different things, operations per second versus MB/s, and workloads are bound by one or the other. A database needs IOPS, a streaming/backup job needs throughput. Picking the wrong volume type wastes money or underperforms.

The structure behind a strong answer

  1. 1

    IOPS. Input/output operations per second, the count of individual reads/writes. It matters for small, random I/O, like a database doing many small lookups.

  2. 2

    Throughput. The data transfer rate (MB/s). It matters for large, sequential I/O, like streaming big files, log processing, or backups.

  3. 3

    They are different bottlenecks. A workload can be IOPS-bound (many tiny operations) or throughput-bound (moving lots of bytes). Optimizing the wrong one does not help.

  4. 4

    Match the volume type. Use high-IOPS (provisioned IOPS SSD) volumes for transactional databases; use throughput-optimized (HDD) volumes for big sequential workloads, to get performance without overpaying.

What gets you hired

They measure two different dimensions of storage performance. IOPS is input output operations per second, the count of individual reads or writes the volume can do, and it is what matters for small random I/O. The classic case is a transactional database doing lots of small lookups and updates in 8 or 16 kilobyte pages scattered across the disk, where each operation is tiny but there are tens of thousands per second. Throughput is the transfer rate in megabytes per second, and it matters for large sequential I/O: streaming media, chewing through log files, running backups, where you move a lot of bytes in big contiguous chunks. The distinction matters because a workload is bound by one or the other. A database can be IOPS bound, starved on operation count while barely moving 50 MB a second, and a backup job can be throughput bound, saturating the pipe with very few operations. Optimizing the wrong dimension does nothing. So I match the volume type to the bottleneck. On AWS, gp3 gives me 3,000 baseline IOPS and 125 MB per second and I can provision beyond that. For a busy transactional database I go to io2 and provision the IOPS I actually need, say 20,000. For big sequential work I use a throughput optimized HDD like st1, which is cheap per gigabyte and gives me up to 500 MB per second. I profile first, decide whether it is IOPS bound or throughput bound, then pick, so I get the performance I need without paying for the wrong characteristic.

IOPS = ops/sec (small random), throughput = MB/s (large sequential) Knows DB is IOPS-bound, streaming throughput-bound Matches volume type to bottleneck

Then they probe: Is a transactional database typically IOPS-bound or throughput-bound?

Practise this one
Mid

Design durable, highly available storage for user-uploaded images. Which service do you choose and why?

What most people say

I would store the images on the EC2 instance’s disk, or as blobs in the database.

Local disk dies with the instance and breaks horizontal scaling; blobs in a relational DB bloat it, wreck backups, and are expensive. Both miss that object storage is the purpose-built primitive.

The structure behind a strong answer

  1. 1

    Pick object storage. S3 (or Azure Blob): purpose-built for large unstructured blobs, 11 nines of durability, effectively unlimited, and it decouples storage from your compute so instances stay stateless.

  2. 2

    Serve efficiently. Put a CDN (CloudFront) in front for low-latency global delivery and to offload your origin; use pre-signed URLs so clients upload/download directly without proxying bytes through your app.

  3. 3

    Secure it. Bucket private by default, access via IAM/pre-signed URLs, encryption at rest (SSE), and block public access unless a path genuinely must be public.

  4. 4

    Manage lifecycle and cost. Lifecycle rules to tier cold objects to cheaper storage (S3-IA/Glacier), and versioning if you need to recover overwrites/deletes.

What gets you hired

Object storage, S3. It gives 11 nines of durability, is effectively unlimited, and keeps my compute stateless so I can scale horizontally. Clients upload and download directly via pre-signed URLs so I never proxy bytes through the app, and I put CloudFront in front for global low-latency delivery and origin offload. The bucket is private by default with encryption at rest and public access blocked; access is via IAM and short-lived pre-signed URLs. Then lifecycle rules tier cold images to cheaper storage, and versioning protects against accidental overwrites.

Chooses object storage and says why Direct client access via pre-signed URLs Adds a CDN; thinks about cost lifecycle

Then they probe: How do you stop users uploading 5GB files or malware through your pre-signed URLs?

Practise this one
Mid

What does a solid backup strategy look like?

What most people say

I would schedule regular backups of the database to cloud storage.

Scheduling backups is necessary but not sufficient. Without testing restores you may find they are unusable in a disaster; without offsite/immutable copies, one event (or ransomware) takes out both prod and backups. The 3-2-1 rule and restore testing are the point.

The structure behind a strong answer

  1. 1

    The 3-2-1 rule. Keep at least 3 copies of the data, on 2 different media or storage types, with 1 copy offsite (a different region/provider). Redundancy across independent failure modes.

  2. 2

    Test restores. An untested backup is not a backup. Regularly restore from backups to verify they actually work and to know your restore time, do not discover at disaster time that they are corrupt or incomplete.

  3. 3

    Immutable / air-gapped. Make backups immutable (WORM) or air-gapped so ransomware or a compromised account cannot delete or encrypt them, the failure mode that ruins companies.

  4. 4

    Automate and align to RPO/RTO. Automate backups and monitor that they succeed, set retention and frequency to meet your RPO (how much data you can lose) and RTO (how fast you must restore).

What gets you hired

A solid backup strategy is more than scheduling a dump. I start from the 3-2-1 rule: at least three copies of the data, on two different media or storage types, with at least one copy offsite, in a different region or even a different provider, so that no single failure, a disk, a data center, an account, takes out both production and the backups. The single most important and most neglected practice is testing restores: an untested backup is not a backup, it is a hope, because backups fail silently, corrupt, incomplete, missing a critical table, and you do not want to discover that during an actual disaster. So I regularly perform real restores to verify they work end to end and to measure how long a restore actually takes, which is my real RTO. I also make backups immutable, write-once-read-many, or air-gapped, so that ransomware or a compromised admin account cannot delete or encrypt the backups along with production, which is exactly the failure mode that has destroyed companies, their backups were reachable from the same access that got compromised. And I automate the whole thing and monitor that backups are actually succeeding, because a silently-failing backup job is worse than none since it gives false confidence, and I set the frequency and retention to meet the business RPO, how much data loss is acceptable, and RTO, how fast I must be back. So 3-2-1, tested restores, immutable copies, automation with monitoring, and alignment to RPO and RTO.

Knows 3-2-1 rule Insists on testing restores Immutable/offsite + automate + align to RPO/RTO

Then they probe: Why is testing restores the most important part?

Practise this one
Mid

How should you serve static assets (images, JS, downloads), and why not from your application servers?

What most people say

I would serve the files from my web server like any other request.

Serving static bytes from app servers wastes their resources, scales poorly, and is slow and expensive at distance. The standard pattern is object storage behind a CDN, which offloads the origin and serves from the edge.

The structure behind a strong answer

  1. 1

    Object storage + CDN. Store static assets in object storage and serve them through a CDN, which caches them at edge locations close to users.

  2. 2

    Do not serve bytes from app servers. App servers should handle dynamic work, not stream large static files, which ties up their resources and connections and scales poorly.

  3. 3

    Why it is better. A CDN is cheaper per GB, far faster (edge-cached near users), offloads traffic and egress from the origin, and scales essentially infinitely for static content.

  4. 4

    Cache and invalidate. Set cache headers/TTLs, and use content hashing in filenames (cache busting) so updates are picked up without serving stale assets.

What gets you hired

The pattern is to put static assets in object storage and serve them through a CDN, not to stream them from your application servers. App servers should be doing dynamic work, business logic and database calls, and a single 50 megabyte download can hold a connection open for 30 seconds, tying up CPU, memory and a worker slot that a real request needed. A CDN wins on every axis. It caches assets at edge locations near users, so a request that took 300 milliseconds from a single origin in one region drops to 20 or 30 milliseconds at the edge. It is cheaper per gigabyte than serving from compute, it offloads egress from your origin, and it absorbs spikes, so a launch that pushes 10,000 requests a second on a static bundle hits the edge, not your servers. With a good cache hit ratio, typically 95 percent or better, the origin object store only serves the misses. The operational details that matter are caching and invalidation. I set cache headers and TTLs so the edge holds assets as long as is safe, and I put a content hash in the filename, so a changed file gets a new name. That busts the cache instantly on deploy and lets me cache aggressively, Cache-Control max-age of 31536000, a full year, because the URL changes whenever the content does. So static goes to object storage plus CDN with hashed filenames, and app servers are reserved for dynamic requests.

Object storage + CDN, not app servers Knows the scale/cost/latency reasons Cache headers + content-hash cache busting

Then they probe: Why is serving static files from app servers a scaling problem?

Practise this one
Mid

How would you handle large file uploads to object storage from clients?

What most people say

The client uploads the file to my API, which saves it to object storage.

Proxying large uploads through your API consumes its bandwidth/memory and bottlenecks at scale. The pattern is a pre-signed URL so the client uploads directly to the store, with multipart upload for large files.

The structure behind a strong answer

  1. 1

    Do not proxy through the app. Routing large uploads through your application servers wastes their bandwidth and memory and creates a bottleneck. Have clients upload directly to object storage.

  2. 2

    Pre-signed URLs. The app generates a time-limited, scoped pre-signed URL granting permission to upload a specific object; the client uploads directly to the store using it, no app proxying, and no broad credentials on the client.

  3. 3

    Multipart upload. Split a large file into parts uploaded in parallel, which is faster and resumable, a failed part is retried without restarting the whole upload.

  4. 4

    Validate and finalize. Constrain the pre-signed URL (size, content type, expiry), and have the app react to completion (an event/notification) to validate and process the object.

What gets you hired

The key principle is to keep large file bytes out of your application servers. Proxying a 2 gigabyte upload eats their bandwidth, memory and connections, and becomes a bottleneck the moment you have real volume. Instead I have clients upload directly to object storage using a pre-signed URL. My application holds the credentials and generates a time-limited URL, usually valid for about 15 minutes, that grants permission to upload one specific object, and hands that to the client, who uploads straight to the store. The bytes never touch my app, and the client never gets broad storage credentials, just a narrow expiring permission for that one key. For large files I use multipart upload. The file is split into parts, commonly 8 or 16 megabytes each, uploaded in parallel, which is faster and resumable, so if part 37 fails on a flaky connection it is retried on its own rather than restarting the whole 2 gigabyte transfer. On S3 multipart is required past 5 gigabytes anyway. I constrain the pre-signed URL to limit risk, a short expiry, a maximum content length, an allowed content type, so it cannot be abused as open storage. Then I wire the app to react to completion, usually an event notification from the store, to validate the object, scan it if needed, and kick off processing. So it is direct to storage via pre-signed URLs, multipart for large and resumable transfers, constrained and validated, with the app orchestrating rather than carrying the bytes.

Direct-to-storage via pre-signed URLs, not proxying Multipart upload for large/resumable Constrains URL + validates on completion

Then they probe: What is a pre-signed URL and why is it safer than putting credentials on the client?

Practise this one
Senior

How do you handle data retention and compliance for stored data?

What most people say

I would keep all the data in case we need it later, storage is cheap.

Keep-everything is exactly wrong at a senior level. Retained data is a liability (breach exposure, compliance risk), and regulations like GDPR require you to delete data and keep it in-region. You need retention policies and the ability to erase, not hoard.

The structure behind a strong answer

  1. 1

    Retention policies per data type. Define how long each class of data must be kept (legal/business requirement) and delete it when no longer needed, automated via lifecycle rules, not left forever by default.

  2. 2

    Data is a liability. Beyond storage cost, retained data is risk: a breach exposes everything you kept. Minimize what you store and how long, keep only what has a purpose.

  3. 3

    Compliance controls. Support legal hold and WORM/immutability where regulation requires data be retained and unaltered, and audit access to sensitive data.

  4. 4

    Privacy regulations. Handle right-to-erasure (GDPR), being able to actually find and delete a users data, including in backups, and data residency, keeping data in required regions.

What gets you hired

The senior framing is that stored data is a liability, not just an asset, so I do not default to keeping everything. I define retention per data type from legal and business requirements, how long must this be kept and, just as important, when must it be deleted. Then I automate it with lifecycle rules, so debug logs might expire after 30 days, application logs after 90, and financial records sit for the 7 years the regulator wants. Data does not accumulate forever by inertia. The reason is risk as much as cost. Every record you retain is something a breach can expose and something you may have to account for, so data minimization, keeping only what has a purpose and only as long as needed, is a security and compliance posture, not just a storage bill optimization. Then the compliance controls. Where regulation says data must be retained unaltered, financial records for instance, I use legal hold and WORM immutability so nothing can be deleted or tampered with inside the retention window, and I audit access to sensitive data. Privacy law is a real engineering requirement too. Under GDPR right to erasure I typically have 30 days to actually find and delete a specific user, which is brutal if data is scattered across services and buried in backups, so it has to be designed for. And residency means keeping data in the regions the law requires, which constrains where I store and replicate. So, retention policies with automated deletion, data treated as a liability, WORM and legal hold where required, and erasure and residency built in, not hoarding because storage looks cheap.

Retention policies + automated deletion Treats data as a liability (minimization) Knows WORM/legal hold + GDPR erasure + residency

Then they probe: Why is keeping all data indefinitely a problem beyond cost?

Practise this one
Senior

How do you choose the right storage solution for a given workload?

What most people say

I would use object storage like S3 because it is cheap and scalable.

Defaulting to one option ignores the workload. Object storage is wrong for a low-latency transactional database (that needs block) or shared POSIX access (that needs file). A senior starts from access pattern and requirements, not a favorite.

The structure behind a strong answer

  1. 1

    Start from the access pattern. Ask how the data is accessed: random vs sequential, read- vs write-heavy, latency-sensitive or not, shared by many clients or owned by one.

  2. 2

    Match to a storage type. Object storage for large unstructured blobs, static assets, and massive scale; block storage for low-latency single-attached needs like databases and OS disks; file storage for shared POSIX access across many clients.

  3. 3

    Weigh the requirements. Factor in durability and availability needs, scale and growth, latency and throughput, and cost. There is no universally best store, only the best fit for these.

  4. 4

    Do not default. Resist reaching for a familiar option reflexively. Let the workload requirements drive the choice, and combine types when different data has different needs.

What gets you hired

I start from the workload, not from a favorite product, because there is no universally best storage, only the right fit for an access pattern. So first I characterize it. Is the I/O random or sequential, read heavy or write heavy, how latency sensitive is it, and does it need to be shared across many clients or attached to one. That largely points at the type. Object storage is for large unstructured blobs, images, video, backups, static assets, with 11 nines of durability, effectively unlimited scale, and a simple HTTP API, but first byte latency is tens of milliseconds and it is not a filesystem. Block storage is for low latency, high IOPS work attached to a single instance, the disk under a transactional database, where single digit millisecond random I/O and a few thousand IOPS matter. File storage is for when multiple clients need shared POSIX access at once. And if the data is structured and queried, that is a database question, relational or NoSQL, not raw storage. Then I weigh the cross cutting requirements, durability and availability targets, growth, throughput, and cost, which can vary by a factor of 10 between tiers. I deliberately avoid defaulting, and the classic trap is putting a latency sensitive database on object storage because it is cheap, then wondering why queries take 100 milliseconds instead of 2. Usually the answer is a combination, object storage for user uploads, block for the database, a CDN in front of static assets, because different data in one system has different needs.

Starts from access pattern + requirements Maps object/block/file/database to the right job Avoids defaulting, combines types when needed

Then they probe: Why is object storage a poor fit for a transactional database?

Practise this one

New Grad

10 questions · Foundation
Foundation

You have no professional experience. Why should we hire you?

What most people say

I am a hard worker and a fast learner, and I am really passionate, so I would be a great fit.

These are empty claims every candidate makes with nothing behind them. "Hard worker, fast learner, passionate" with no evidence is invisible. It also leans on adjectives instead of a single concrete thing you actually did.

The structure behind a strong answer

  1. 1

    Do not apologize for it. Never open with "I know I have no experience, but...". Reframe: you are early-career, and here is the concrete evidence you can do the work.

  2. 2

    Point to proof of work. Lead with something real you built, a project, a deployed app, a lab, and what it shows you can do hands-on, not just in theory.

  3. 3

    Show you learn fast. Give a concrete example of picking up a new tool or concept quickly. For an entry role, learning speed matters more than current knowledge.

  4. 4

    Match it to their need. Connect your evidence to what this specific role needs, and show genuine motivation for the work, which entry-level hiring weighs heavily.

What gets you hired

I would point you to what I have actually built rather than ask you to take my word for it. I built and deployed a full application end to end, a frontend, an API, and a database, set up the infrastructure, containerized it, and got it running through a real pipeline that goes from a git push to a live deploy in about 4 minutes. So I have already done a scaled-down version of the work this role involves, and I can walk you through every decision and what I would do differently now. That project also shows how I learn. Two of those tools, containers and the cloud provider, I had never touched before I started, and I had the first version deployed inside 3 weeks by building rather than just reading, which is exactly how I would ramp on your stack. I know I am early in my career, but I would frame that as upside. I am motivated, I learn by doing, and I have proof that I can ship. And I am genuinely excited about this kind of work, which is why I spent my own evenings and weekends building that in the first place rather than waiting for someone to assign it to me.

Leads with concrete proof of work Shows a real example of learning fast Confident without arrogance, ties it to the role

Then they probe: How do you handle it if you genuinely cannot answer a technical question in the interview?

Practise this one
Foundation

Walk me through a project you built.

What most people say

I built a web app using React and Node and a database, and it has login and a dashboard, and it works.

This is a feature list, not an engineering story. It shows no decisions, no problems solved, and no understanding of why anything was done. It reads exactly like a tutorial follow-along, which is what the interviewer is screening for.

The structure behind a strong answer

  1. 1

    Set the context fast. One or two sentences: what it does and why you built it. Do not spend three minutes on background.

  2. 2

    Explain the decisions, not just the stack. Do not just list technologies. Say why you chose them and what the alternatives were, that is what separates understanding from copying a tutorial.

  3. 3

    Show a problem you hit. Describe something that broke or was hard, and how you debugged and solved it. Struggle plus resolution is the most convincing part.

  4. 4

    Reflect honestly. End with what you would do differently now. Self-critique signals you actually understand it and keep growing.

What gets you hired

Sure. I built a task-tracking app because I wanted something I would actually use and that would force me to touch the whole stack. Quick context: a web frontend, an API, and a database, deployed first on a single cloud VM, then containerized into 2 images. The decisions are the interesting part. I containerized it not because the project needed it, but because I wanted repeatable deployments, and I learned the hard way why. My first manual deploy worked and the second one broke, because the server had drifted, a package had been upgraded underneath me, which is exactly the problem containers fix. The hardest problem was that the app worked locally but could not reach the database once deployed. Every request just hung for the full 30 second timeout with no error in the logs. Debugging that taught me real networking and security rules. The timeout rather than an instant refusal told me it was a firewall issue, not the app, and it turned out the database security group allowed nothing inbound, so I opened exactly port 5432 from the app's group and nothing wider. If I rebuilt it today, I would set up the pipeline in week 1 instead of last, because hand-deploying for the first month cost me hours I did not need to spend. I am happy to go deeper on any part of that.

Explains decisions and trade-offs Describes a real problem they debugged Reflects on what they would change

Then they probe: What would you do differently if you rebuilt it?

Practise this one
Foundation

Tell me about yourself.

What most people say

I was born in... I went to school at... I have always loved computers since I was a kid, and then I studied... and I like gaming and...

It is a chronological life story, unfocused and mostly irrelevant. It buries anything useful, wastes the most valuable minute of the interview, and signals you cannot prioritize what matters.

The structure behind a strong answer

  1. 1

    Keep it to about a minute. This is a headline, not your autobiography. Aim for 60 to 90 seconds, relevant to the job.

  2. 2

    Present, then path, then why-here. A clean structure: what you do/study now, the relevant things you have done (projects, focus), and why you are excited about this role.

  3. 3

    Steer toward your strongest material. Mention the project or skill you most want them to ask about, so you set up the rest of the interview.

  4. 4

    Keep it professional. Lead with what is relevant to the job, not hobbies or your whole personal history, you can be warm without being off-topic.

What gets you hired

I just finished my computer science degree, and for the last 12 months or so I have focused specifically on cloud and infrastructure, because I enjoy the building-and-operating side more than pure application code. The most relevant thing I have done is build and deploy a full application on cloud infrastructure end to end, a frontend, an API, and a database, containerized, with a real pipeline that takes a git push to a live deploy in about 4 minutes. That is where I learned how the pieces actually fit together in production rather than in a lecture, and hitting real problems, networking rules, drifted servers, broken deploys, is what turned cloud engineering from a class into something I want to do for a career. I have been deliberately leveling those skills up since, mostly by building small projects that force me to use tools I do not know yet. This role stood out to me because it is exactly that kind of hands-on infrastructure work, which is why I was keen to be in this conversation. I am happy to go deeper on that project or on anything else in my background.

Tight, relevant, ~1 minute Present/path/why-here structure Sets up their best material

Then they probe: How long should this answer be?

Practise this one
Foundation

Why do you want to work here, and why this field?

What most people say

You are a great company with a good reputation and lots of opportunities to grow, and I think it would be a great place to start my career.

Every word of this fits any company on earth, which tells the interviewer you did no research and have no specific interest. "Great reputation, opportunities to grow" is filler that signals you are mass-applying.

The structure behind a strong answer

  1. 1

    Be specific about them. Reference something real about the company, what they build, their product, their stack, their mission, that shows you actually looked, not a line that fits any employer.

  2. 2

    Make the field reason genuine. Say honestly why this kind of work appeals to you, ideally tied to a real experience (a project, a moment it clicked), not a textbook reason.

  3. 3

    Connect you to them. Link what you want to learn or do with what they offer, so it is a two-way fit, not just "I need a job".

  4. 4

    Be honest, not flattering. Genuine and specific beats over-the-top praise. You do not need to pretend it is your dream company, you need to show real, informed interest.

What gets you hired

Two parts. On the field: I got into cloud and infrastructure because when I built and deployed my own project, the part I enjoyed most was not writing the features, it was getting it to run reliably in production, the deployment, the networking, making it repeatable. I spent maybe 3 evenings chasing a connection timeout and I enjoyed those 3 evenings more than the 2 weeks of frontend work. That is a genuine pull, not a textbook reason, and it is why I have kept building in this area. On the company specifically: I read through your engineering blog and your careers page, and I am drawn to the fact that you run real infrastructure at a scale I have only read about. From what I saw, you use a stack I have been learning, containers and infrastructure as code, so it is a place where I could contribute early and grow fast. I would rather be honest with you than flatter you. This is the kind of work I actually want to do, and you are doing it in a way I find genuinely interesting, which is why I applied here specifically rather than sending out 50 applications and hoping.

References something specific about the company Genuine, experience-based field reason Two-way fit, not just needing a job

Then they probe: What if you do not know much about the company?

Practise this one
Foundation

Tell me about a time you faced a difficult problem or got stuck. What did you do?

What most people say

I do not really get stuck, I just keep working until I figure it out, I am pretty good at solving problems on my own.

"I never get stuck" is a red flag, everyone gets stuck, so it reads as either dishonest or inexperienced, and refusing to ask for help is a negative, not a strength. It also dodges the question by giving no actual example.

The structure behind a strong answer

  1. 1

    Use the STAR structure. Situation and Task (briefly set the scene), Action (what YOU specifically did), Result (the outcome and what you learned). Keep most of it on Action.

  2. 2

    A project or coursework counts. You do not need a workplace story. A bug in your project, a hard assignment, a group conflict, all are valid, the interviewer cares about the process, not the setting.

  3. 3

    Show the process, not just the win. Explain how you approached being stuck: how you broke it down, what you tried, where you looked, when you asked for help. The method is the point.

  4. 4

    End with the result and the lesson. Say how it resolved and what you took from it. A genuine lesson is more convincing than a flawless outcome.

What gets you hired

A good example is from the project I deployed. The situation was that the app worked perfectly on my machine but completely failed once it was in the cloud. It could not reach the database, and there was no obvious error, every request just hung for the full 30 second timeout. My task was to get it working with very little idea where the problem even lived. For the action, I made a point of not thrashing. I worked the layers: I confirmed the app process was up, then checked whether it could reach the database host at all, then noticed the connection was timing out rather than being refused, which pointed at a firewall or networking rule rather than the app itself. I read the cloud provider's docs on security rules, found the database group was allowing 0 inbound sources, and opened exactly the 1 port the app needed from the app's own security group, nothing wider. The result was that it worked, and the lesson was bigger than the fix. I learned to localize a problem by layer instead of guessing, and that a timeout and a refused connection tell you very different things, which has saved me hours since. And yes, when I am genuinely stuck I do ask for help or search, I just try to narrow the problem first so that the question I ask is a specific one.

Uses STAR with a real example Shows a problem-solving process Honest about asking for help + a genuine lesson

Then they probe: What if your only examples are from school or personal projects?

Practise this one
Foundation

What are your greatest strengths and weaknesses?

What most people say

My biggest strength is that I am a perfectionist, and honestly my biggest weakness is also that I am a perfectionist and care too much.

This is the textbook fake answer, a disguised brag instead of a real weakness, and it signals you are either not self-aware or unwilling to be honest. The strength is also a label with no evidence behind it.

The structure behind a strong answer

  1. 1

    Strength: relevant and backed by evidence. Pick a strength that matters for the role and immediately back it with a concrete example, not just the label.

  2. 2

    Weakness: real but not disqualifying. Name a genuine weakness that does not torpedo the job, and be honest, a real one shows self-awareness.

  3. 3

    Show you are working on it. The point of the weakness is growth: say what you are actively doing to improve it. That turns a negative into evidence of self-improvement.

  4. 4

    Avoid the cliche. Never use the fake humblebrag ("perfectionist", "I work too hard"). Interviewers have heard it a thousand times and it reads as evasive.

What gets you hired

For a strength, I would say I am good at learning unfamiliar tools quickly by building with them, and I can back that up. For my project I had used neither containers nor a cloud provider before, and I went from zero to a deployed, containerized app in about 3 weeks by working through it hands-on rather than reading first. That is exactly the ramp an entry-level role needs. For a weakness, an honest one is that because I am early in my career, my instinct has been to try to solve everything myself before asking, and that has cost me. On one bug I was stuck for 2 days on something a 10 minute conversation would have cleared up. I am actively working on it with a timebox. If I am stuck for more than about 45 minutes with no new information, I now deliberately write down what I have tried and ask a specific question, and I move noticeably faster for it. I would rather give you a real weakness I am improving than a fake one, because being able to see and fix my own gaps is exactly what makes me coachable, and for someone at my stage that is probably the thing that matters most.

Strength backed by a concrete example A genuine, non-disqualifying weakness Shows active steps to improve it

Then they probe: Why is "I am a perfectionist" a bad weakness to give?

Practise this one
Foundation

This field changes fast. How do you keep your skills current?

What most people say

I read articles and watch videos online to stay up to date with the latest technologies.

Vague and passive, "I read articles and watch videos" is what everyone says and proves nothing. There is no specific source, no recent example, and no hands-on learning, so it reads as a throwaway rather than a real habit.

The structure behind a strong answer

  1. 1

    Show a real habit. Name how you actually learn: building projects, specific docs or courses, following certain sources, hands-on labs, with enough specificity to be believable.

  2. 2

    Emphasize learning by doing. For technical work, building beats passive reading. Stress that you learn by getting hands on, which is also how you will ramp on their stack.

  3. 3

    Give a concrete recent example. Mention something specific you learned recently and how, that proves the habit is real, not aspirational.

  4. 4

    Tie it to the role. Connect your learning approach to being able to pick up their tools and grow into the role quickly.

What gets you hired

I learn mostly by building, because for this kind of work reading alone does not stick for me, I only really understand something once I have made it work and watched it break. Concretely, my method is to take something I do not know and build a small project that forces me to use it, and I try to have 1 of those running at any given time. That is exactly how I learned containers and cloud deployment, I did not just read about them, I deployed something and hit the real problems, the drifted server, the 30 second connection timeout, the security rule I had not opened. On top of that I read the official documentation for whatever I am currently working on, follow 2 or 3 specific sources rather than trying to drink from the firehose, and do hands-on labs to practice. A recent example: I wanted to understand how deployments get automated, so instead of just reading about CI/CD I set up a pipeline for my own project, and it now goes from git push to live in about 4 minutes. That habit is why I am confident I can ramp on your stack quickly, even the parts of it I have not used, because picking up unfamiliar tools by building with them is the thing I do most.

Specific, real learning habit Emphasizes learning by doing Has a concrete recent example

Then they probe: What is something you learned recently and how?

Practise this one
Foundation

This role uses a technology you have never worked with. How would you handle that?

What most people say

I would say I am familiar with it so I get the job, and then I would figure it out once I started.

Claiming familiarity you do not have is dishonest and easy to expose with one follow-up question, which can end the interview and would destroy trust on the job. It answers a question about handling gaps by proposing to lie about them.

The structure behind a strong answer

  1. 1

    Do not pretend you know it. Never bluff familiarity with something you have not used. Acknowledge the gap honestly, getting caught faking is far worse than admitting it.

  2. 2

    Show how you would close it. Lay out a concrete plan: read the docs, build a small thing with it, find the analogous concept you do know. Make closing the gap sound routine, because for you it is.

  3. 3

    Map it to something you know. Connect the unfamiliar tool to a concept you already understand, which shows the gap is smaller than it looks and that you transfer knowledge.

  4. 4

    Point to a track record. Note that you have learned unfamiliar tools before (your project) and would do the same here, so the gap is temporary, not a wall.

What gets you hired

I would be upfront that I have not used it yet, because pretending otherwise tends to fall apart on the first real question, and I would rather you trust what I tell you. Then I would show you it is not a problem. My approach to a new tool is to read the official docs and, within the first day or 2, build something small with it, because hands-on is how it sticks for me. I would also look for the concept I already understand that it maps to. Most tools in this space are variations on 3 or 4 ideas, deployment, networking, configuration, state, and I have worked with all of those, so it is rarely truly from scratch. I have done exactly this before. I picked up containers and a cloud provider from zero for my project and had a working deployment in about 3 weeks, so I know the ramp works for me. So my honest answer is: I do not know it today, here is specifically how I would know it within a few weeks, and I have a track record of doing that. For an entry-level role, I would hope a closeable gap plus a proven way to close it beats someone who overstates what they know and gets found out in month 2.

Honest about the gap Concrete plan to close it + maps to known concepts Cites a real track record of learning

Then they probe: Why not just say you know it to get the offer?

Practise this one
Foundation

Do you have any questions for us?

What most people say

No, I think you covered everything, thanks.

Having no questions reads as disengaged or uninterested, even if you are just nervous. It wastes a real chance to show enthusiasm and judgment, and it makes you forgettable compared to a candidate who asks something thoughtful.

The structure behind a strong answer

  1. 1

    Always have questions ready. Never say "no, I think you covered everything". Prepare three to five in advance so you always have some left after the conversation.

  2. 2

    Ask about the work and growth. Good areas: what a new hire works on first, what success looks like in the role, how juniors are mentored and grow, the team and how they work.

  3. 3

    Show you were listening. Ask at least one question that builds on something they said earlier in the interview, which signals genuine engagement.

  4. 4

    Avoid the wrong first questions. Do not lead with salary, time off, or "how soon can I be promoted". Those can come later, your questions here should be about the work and learning.

What gets you hired

Yes, I have three. First, for someone joining in this role, what would I be working on in the first 90 days, and what does doing well look like at that point, because I want to be sure I would be set up to actually contribute rather than guess. Second, since I am early in my career, I am curious how the team supports people who are growing. Is a mentor assigned, and roughly how many reviewers does a pull request go through before it merges, because a real code review culture is where I learn fastest. Third, building on what you mentioned earlier about containerizing your services, I would love to know where that is in the journey. Is it 2 services in, or most of the way through, and would someone in this role get to work on it. I have a couple more on the team and the tooling, but those 3 are what I am most curious about. The honest reason I am asking is that I want this to be the right fit on both sides, not just to walk out with an offer.

Comes prepared with thoughtful questions Asks about the work, growth, mentorship Builds on something said earlier

Then they probe: Is it okay to ask about salary or time off here?

Practise this one
Foundation

Tell me about a time you worked on a team or had a disagreement with someone.

What most people say

I usually just do the work myself because it is faster and I cannot rely on teammates to pull their weight.

This is a major red flag, it signals you are hard to work with, do not trust teammates, and cannot collaborate, which matters as much as technical skill. It turns a teamwork question into evidence you avoid teamwork.

The structure behind a strong answer

  1. 1

    Pick a real collaboration. A group project, hackathon, club, or team activity all count. You do not need workplace experience for this.

  2. 2

    Use STAR and focus on your role. Briefly set the scene, then focus on what you specifically did, especially how you communicated and contributed.

  3. 3

    Handle disagreement maturely. If it is a conflict story, show that you listened to the other view, focused on the goal not winning, and reached a reasonable resolution. Never trash the other person.

  4. 4

    End on the outcome and lesson. What the team achieved and what you learned about working with others.

What gets you hired

A good example is a university group project, 4 of us building something in 3 weeks. The situation was that we disagreed on the approach. One teammate wanted a framework none of us had used, and with 3 weeks on the clock I thought we should stick to what we already knew. Rather than just argue, my action was to actually hear out why they wanted it, and they had a fair point, it genuinely was the better long term choice. So we compromised. We used the familiar approach for the core so we would hit the deadline, and we tried the new tool on 1 isolated piece, a small reporting module, so they got to explore it without putting the other 90 percent of the project at risk. The result was that we shipped 2 days before the deadline and that module worked fine. The lesson for me was that disagreements are usually not about who is right, they are about a shared goal, and the fastest way through is to understand the other person's reason before I defend my own. I would much rather work that way than try to do everything myself, because a team gets far more done than I can alone.

Real collaboration example (project/club counts) Handles disagreement by listening + compromise Values teamwork over going solo

Then they probe: What if your only teamwork is from group projects or clubs?

Practise this one

Behavioural

40 questions · Foundation, Junior, Mid, Senior
Foundation

Tell me about a time you had to learn a new technology quickly. How did you go about it?

What most people say

I am a fast learner, so I just watched some tutorials and read the documentation and picked it up pretty quickly.

It claims a trait instead of showing a method, has no artefact, no timeline and nothing an interviewer can verify or follow up on.

The structure behind a strong answer

  1. 1

    Situation. Name the tech and the deadline in one sentence, twenty seconds maximum, no backstory.

  2. 2

    Task. State the smallest thing you personally had to make work, not the whole project.

  3. 3

    Action: shrink it. Say how you narrowed the surface area to the ten percent you actually needed.

  4. 4

    Action: prove it. Describe the tiny thing you built or ran to confirm you understood it.

  5. 5

    Result. Give a number: days taken, tickets closed, or the thing that shipped.

  6. 6

    What you learned. One sentence on the method you would reuse, spend ten seconds here.

What gets you hired

In my last role I was asked to add monitoring to a service, and the team used Terraform, which I had never touched. I had about a week before the sprint review. First I shrank the problem. I did not try to learn all of Terraform, I only needed to add one resource to an existing module, so I read just the parts about providers, resources and state, roughly two hours of docs. Then I proved it to myself in a sandbox: I wrote a five line config that created a single S3 bucket, ran plan, ran apply, then destroyed it. That one cycle taught me more than the reading, because I finally understood what state was actually for. Then I copied the pattern in the real repo, made my change, and read the plan output line by line before opening the pull request. I asked for review early, on day three, with a note saying which parts I was unsure about, so the reviewer knew where to look. The alarm shipped on day four, a day before the deadline, and my pull request went in with two comments, both style. What I took from it is the sequence I still use: shrink the surface, build the smallest thing that runs end to end, then ask a specific question rather than a vague one.

Names a specific technology and a specific deadline Describes narrowing scope rather than learning everything Built something small and ran it, rather than only reading

Then they probe: What did you do when you got stuck?

Practise this one
Foundation

So, why cloud? You have not worked in cloud before, so walk me through what actually pulled you here.

What most people say

I want to work in cloud because it is the future, there is huge demand, and I am passionate about technology.

It is a sentence anyone could say without ever touching a terminal, it contains no evidence, no story, and no cost you personally paid, so the interviewer learns nothing about you.

The structure behind a strong answer

  1. 1

    Situation. Name the real job or project you were in, with one line of context. Fifteen seconds, no more.

  2. 2

    The friction. Describe the specific pain that made you look up and want something different. This is your hook.

  3. 3

    Action you took. Say what you personally did about it, including the first small automation or script you wrote.

  4. 4

    Result with a number. Give the concrete outcome, a time saved, a failure rate dropped, tickets avoided. Numbers make it true.

  5. 5

    The bridge. Connect that feeling to cloud explicitly, then name what you have studied since, with a timeframe.

What gets you hired

I came to cloud from a support desk at a logistics company. For two years I was the person who got paged when our order tracking site went down, and it went down often, usually because a release meant hand copying files onto one server. I got tired of being the human in that loop. I asked my manager if I could automate the release. I taught myself Linux basics and Git at night, wrote a small script that pushed the build and restarted the service, and had our one senior engineer review it. The manual release used to take about 45 minutes and broke roughly one time in four. My script did it in 6 minutes, and we had two failed releases in the following six months instead of the usual dozen. That is when it clicked. Cloud is that same feeling at a much bigger scale, infrastructure you can describe in a file, rebuild in minutes, and test before it breaks. I moved toward cloud because I want to build the thing that stops the pager ringing, not just answer it. That is why I have spent the last nine months on AWS fundamentals and Terraform, and why I am sitting here.

There is a specific moment of friction, not a general interest in technology The candidate did something about it before anyone paid them to The motivation is connected to a concrete number they can defend

Then they probe: What is the part of cloud work you think you would enjoy least?

Practise this one
Foundation

Why are you leaving your current job?

What most people say

Honestly, the management is a mess, there is no growth, and I am underpaid for what I do.

Even when every word is true, it tells the interviewer how you will describe them in a year, and it makes your next move sound like an escape rather than a choice.

The structure behind a strong answer

  1. 1

    Situation. Say what your current role is and one genuinely good thing about it. Ten seconds.

  2. 2

    The ceiling. Name the specific limit you hit, a technology you cannot touch, a scope that will not grow.

  3. 3

    What you tried first. Show you tried to solve it in place before deciding to leave. This is the credibility step.

  4. 4

    Result and honest conclusion. Give the outcome with a figure, then say plainly what it told you about staying.

  5. 5

    The pull. End on what this role gives you that yours cannot. Forward looking, not bitter, ten seconds.

What gets you hired

I am a systems support engineer at a mid sized insurer, and I actually like the team, they trained me well. But everything we run sits on two physical servers in a rack, and the decision to stay there is made a long way above me. I did try to move from the inside first. Last year I volunteered to migrate our internal reporting tool to a small managed database, wrote the business case myself, and got it approved as a pilot. It worked, we cut the nightly report from about 50 minutes to 12, and we stopped losing a Saturday every month to disk maintenance. But that was the end of the runway. There is no cloud team to join, and the next migration is not funded for at least two years. So I am not leaving because something went wrong, I am leaving because I finished the biggest cloud thing my company had to offer, and I want the next one. This role is a cloud team doing this every day. I would rather be the most junior person on that team than the most senior person on a rack of two servers.

Speaks respectfully about the current employer while still being honest Shows an attempt to grow inside the role before deciding to leave The reason is a ceiling on scope, not a grievance about people

Then they probe: If your company offered you a cloud role tomorrow, would you stay?

Practise this one
Foundation

Tell me about a time you noticed something was broken, wasteful or risky, and nobody seemed to own it. What did you actually do?

What most people say

I always take ownership. If I see a problem I fix it, because I am a very proactive person and I do not wait to be told.

It is a personality claim with no story, no system, no number, and no evidence that anyone else noticed the change happened.

The structure behind a strong answer

  1. 1

    Situation. Two sentences on where you were, the team size, and the system involved.

  2. 2

    The gap. Say clearly why nobody owned it, for example a departed teammate or unclear boundary.

  3. 3

    Task. State what you decided your own responsibility was, in one honest sentence.

  4. 4

    Action. Spend most of your time here, list the concrete steps you took in order.

  5. 5

    Check-in. Show you told someone before changing anything, ownership is not secrecy.

  6. 6

    Result. Give one number, money saved, time saved, or incidents avoided.

  7. 7

    Learned. One line on what you would do earlier or differently next time.

What gets you hired

In my first cloud role I was on a three person platform team. I noticed our monthly bill had a line for about 40 dollars a day on a dev environment that nobody talked about in standup. It had been spun up by an intern who had left two months earlier. Nobody owned it, so it just sat there. I did not delete anything. I first tagged the resources and pulled the CloudTrail history to check if anything had touched them in 60 days. Nothing had. Then I wrote a short message in the team channel with the cost, the last-touched date, and a proposal: stop the instances for two weeks, and if nobody screams, delete them. My lead agreed in about ten minutes. Nobody screamed. We deleted them, which cut roughly 1,200 dollars a year of waste. More usefully, my lead asked me to make it a habit, so I set a weekly reminder to scan for untagged resources older than 30 days. What I learned is that the fix was easy, the hard part was making the problem visible with a number attached. Once I said 40 dollars a day out loud, the decision took minutes.

Named a real number before touching anything Chose a reversible step, stop before delete Told a human before acting, not after

Then they probe: What if your lead had ignored the message?

Practise this one
Junior

You join a team and inherit a service with no documentation and the original author has left. Walk me through your first week.

What most people say

I would read through all the code carefully from top to bottom until I understood the whole thing, and then I would start making changes.

Reading everything is not a plan, it does not scale past a few thousand lines, and it shows no sense of priority, risk or how to build a mental model from the outside in.

The structure behind a strong answer

  1. 1

    Situation. Set the scene in two sentences: what the service did and who depended on it.

  2. 2

    Task. Say what you were expected to deliver, which forces the exploration to have a purpose.

  3. 3

    Action: read the edges first. Explain how you traced entry points, deploy config, alerts and dashboards before internals.

  4. 4

    Action: write as you go. Describe the running notes or diagram you produced, which becomes the missing documentation.

  5. 5

    Action: verify safely. Say how you tested understanding without touching production, for example a read only replay.

  6. 6

    Result. Give a number: first change shipped by day X, or a doc that saved the next joiner time.

What gets you hired

This happened to me on a payments reconciliation job that ran nightly. The author had left, the README was 2 lines, and I was asked to fix a bug where a small number of records were being double counted. I did not start with the code. Day 1 I read the outside: the cron definition, the deploy pipeline, the IAM role it used, the 2 alarms attached to it and the last 30 days of logs. That told me the shape of the thing before I read a single function. Day 2 I drew the flow on one page, inputs, 3 transform steps, output table, and I put a question mark on every part I could not explain. There were 6 question marks. Day 3 I answered 4 of them by running the job locally against a copy of yesterday's input, which is where I found that a retry path re-inserted rows without checking the idempotency key. Day 4 I wrote a failing test that reproduced the double count, fixed it in about 20 lines, and shipped it. My first change was live on day 5, and the nightly duplicate count went from roughly 300 records to 0. I turned my one page diagram plus the 6 questions into the README, and the next person who touched the service told me it saved her a full day. The rule I took from it is that you learn a system faster from its edges, its config, alerts and logs, than from its middle.

Starts from entry points, config, alerts and logs rather than line by line reading Produces written artefacts that outlive the exploration Explicitly tracks what they do not yet understand

Then they probe: How did you avoid breaking things while exploring?

Practise this one
Junior

Describe a time you were blocked and there was genuinely nobody available to ask. What did you do?

What most people say

There was no one around so I waited until the next day when my team lead came online and then he explained it to me.

Waiting is not an action, it shows no attempt at self rescue and it tells the interviewer your throughput is capped by someone else's calendar.

The structure behind a strong answer

  1. 1

    Situation. Say why nobody was available, time zones, holiday, the only expert left the company.

  2. 2

    Task. State the concrete thing that had to move forward despite the block, with its deadline.

  3. 3

    Action: form a hypothesis. Show that you turned confusion into a testable guess rather than random poking.

  4. 4

    Action: find a substitute source. Name what you used instead of a person: git history, tests, logs, source code, a similar repo.

  5. 5

    Action: bound the risk. Explain what you did so a wrong guess would be cheap and reversible.

  6. 6

    Result and communication. Give the outcome with a figure, plus what you wrote down for the absent person.

What gets you hired

During a release week our only Kubernetes person was on leave for 2 weeks, and a deploy started failing with an image pull error on 1 cluster but not the other. I had about 4 hours before the release window closed. I could not ask anyone, so I made the question smaller and testable: same image, same manifest, different cluster, so the difference is almost certainly credentials or registry access, not the app. Then I went looking for substitutes for a person. Git history told me the node pool had been recreated 11 days earlier. The pod events said unauthorized rather than not found, which ruled out a bad tag. I compared the service accounts across both clusters and found the new node pool had been created without the pull secret attached. I did not just patch production. I reproduced it by deploying the same manifest into a throwaway namespace, confirmed the failure, added the secret there, and watched all 3 pods pull cleanly. Then I applied the same fix to the real namespace. The deploy went green about 90 minutes after I started, so we made the release window with 2 hours to spare. I wrote a short note in the channel with the evidence and the exact commands I ran, so when our Kubernetes engineer came back she could sanity check it in 5 minutes. She did, and she added the secret to the node pool bootstrap so it could not happen again.

Turns confusion into a testable hypothesis quickly Uses git history, logs, tests and source as substitutes for a person Makes the experiment cheap and reversible before touching production

Then they probe: How long do you thrash before you change approach?

Practise this one
Junior

Tell me about a time you helped a teammate who was stuck or blocked. What did you actually do?

What most people say

I am a team player, so whenever someone is stuck I always jump in and help them out. I like helping people.

It is a personality claim with no story, no specifics, and no evidence that anyone was actually unblocked.

The structure behind a strong answer

  1. 1

    Situation. Two sentences only. Name the team, the deadline, and who was stuck.

  2. 2

    The block. Say precisely what they were stuck on and how you noticed it.

  3. 3

    Action. Describe what you did, and separate teaching from doing it for them.

  4. 4

    Result. Give one number. Time saved, hours unblocked, tickets closed, deploy time.

  5. 5

    What changed after. Say what you wrote down or automated so it never blocks anyone again.

What gets you hired

On my last team we had a new joiner, Priya, who lost most of 2 days trying to get her Terraform plan to run. In standup she said the same thing twice, that she was still fighting credentials, so I asked if I could sit with her for 30 minutes after the call. It turned out her AWS profile was pointing at the wrong account, so every plan wanted to destroy 40 or so resources she could not even see. I did not take the keyboard. I asked her to run aws sts get-caller-identity and read the account number out loud, and she spotted the mismatch herself in under a minute. We fixed the profile, her plan came back clean, and she shipped her first change that same afternoon instead of at the end of the week. Then I did the part I care more about. I realised our onboarding doc never mentioned profiles at all, so I added a 10 line section with the exact commands and a screenshot of a correct output. The next 2 joiners had a working environment in under an hour each, where Priya and I had between us burned close to 16 hours. What I learned is that being stuck is almost never a knowledge gap in the person, it is usually a gap in what we wrote down, and the fix is to patch the document, not just the laptop.

Names the person and the exact technical block, not a vague description Separates teaching from taking over the work Fixes the system, for example the onboarding doc, not just the incident

Then they probe: What if the person does not want your help?

Practise this one
Junior

Tell me about a time you disagreed with a senior engineer. What did you do?

What most people say

I am pretty easy going, so I just went with what the senior engineer said. They had more experience than me, so they were probably right.

It reads junior because it treats seniority as evidence, gives the interviewer no thinking to look at, and quietly says you will stay silent when you see a problem.

The structure behind a strong answer

  1. 1

    Situation. Two sentences only. Name the system, the change on the table, and who the senior was.

  2. 2

    Stake. Say in one line what you thought would go wrong, in user or money terms.

  3. 3

    Action, how you raised it. Describe the channel you chose and the words you used, calmly and specifically.

  4. 4

    Action, the evidence. Show the small thing you built or measured to test who was right.

  5. 5

    Result with a number. Give the outcome with one hard figure, even if you were the one who was wrong.

  6. 6

    Learned. One sentence on what you would do earlier or differently next time.

What gets you hired

In my first cloud role we were adding a nightly job that copied data from a production database. A senior engineer wanted to run it straight against the primary instance because the read replica setup was, in his words, one more thing to babysit. I was uneasy, but I did not want to argue from a feeling, so I asked for one day to test it. I ran the same query shape against a staging copy with production-sized data and watched the primary. The job held a heavy read for about 40 seconds and pushed p95 API latency from roughly 120 milliseconds to just over 900 during that window. I brought that one chart to him privately, not in the team channel, and said, I think this is fine on most nights but not during our evening peak, here is what I measured. He looked at it for a minute and said, yes, point the job at the replica. We shipped it against the replica and the nightly job has run for over a year with no latency alerts. What I learned is that a number ends a disagreement much faster than an opinion does, and that raising it one to one first meant nobody had to defend a position in public.

Tested the claim instead of arguing about it Chose a private channel first to keep the disagreement cheap Framed the objection around user impact, not personal preference

Then they probe: What if he had said no even after seeing your chart?

Practise this one
Junior

Tell me about a time you broke production. What happened, and what did you do?

What most people say

Honestly I have never broken production. I am very careful, I always double check my work before I run anything.

It reads as either inexperienced or evasive, and it tells the interviewer nothing about how you behave when things go wrong, which is the only thing they are trying to learn.

The structure behind a strong answer

  1. 1

    Situation. Two sentences only. Name the system, the environment, and roughly how many users or services depended on it.

  2. 2

    Task. State what you were actually trying to do when it went wrong, in one plain sentence.

  3. 3

    Action: stop the bleeding. Spend most of your time here. Say how you noticed, who you told, and how you restored service.

  4. 4

    Action: find the cause. Explain what you checked after service was back, and what the real root cause turned out to be.

  5. 5

    Result. Give the blast radius with a number, plus the time to recovery. Numbers show you measured, not guessed.

  6. 6

    Learned. One guardrail you added so the same mistake cannot repeat. Fifteen seconds, no grovelling.

What gets you hired

In my second month at my first job I was cleaning up unused resources in our staging account. I had two terminal tabs open, one on staging and one on production, and I ran a terraform destroy on a small module in the wrong tab. It took down the internal admin API that our support team used, about 15 people. I noticed within about a minute because my own terminal printed the production account ID in the output. I did not try to quietly fix it. I posted in the team channel immediately, said exactly what I had run and what was gone, and the senior on call and I re-applied the module from the committed state file. The API was back in roughly 12 minutes and no customer traffic was affected, because that module only served internal tooling. Afterwards I wrote up what happened and proposed two changes. First, we set a red background and the account alias in the shell prompt for any production profile, so the tab you are in is impossible to miss. Second, we required a plan review on that repository before apply. Both went in that week. The honest lesson was not be more careful, because I already thought I was being careful. It was that a system which lets a tired person destroy production from the wrong tab is the thing that needed fixing.

Reported the incident before anyone else found it Restored service first and did the analysis after Fixed the system, not just their own habits

Then they probe: Why did you tell the team straight away instead of fixing it first?

Practise this one
Junior

I see a fourteen month gap on your CV. Talk me through that.

What most people say

Yeah, sorry about that, it was a difficult time, I was applying a lot but the market was really bad and nothing came through.

The apology and the passive framing hand the interviewer a problem to worry about, and it leaves fourteen months looking like fourteen months of nothing, which is almost never the truth.

The structure behind a strong answer

  1. 1

    Name it plainly. State the dates and the reason in one calm sentence. No apologising, no over explaining.

  2. 2

    What the period actually held. Say what was really happening, caring for family, health, redundancy, a failed startup, whatever is true.

  3. 3

    What you did with the time you had. Show the deliberate part, study, a project, freelance work, even ten hours a week counts.

  4. 4

    Result with a figure. Give something countable, a certification, a shipped project, a monthly bill you managed.

  5. 5

    Close the loop. Say clearly that the reason has ended and you are ready. One sentence, then stop talking.

What gets you hired

Sure. From March last year to May this year I was out of work. My father had a stroke and I was the only one nearby, so I took on his care while my sister finished her degree. I am not going to pretend that was a productive sabbatical, the first four months I got very little done. Once his care package came through in July I had my mornings back, and I used them. I put in roughly 15 hours a week on cloud, worked through the AWS Solutions Architect Associate material, passed it in November, and then built and ran a small project properly rather than just doing labs. I host a bookings site for my cousin's barbershop on ECS Fargate behind an Application Load Balancer, with Terraform for the infrastructure and GitHub Actions for deploys. It has been live for seven months, it costs me about 19 dollars a month, and I once took it down for 40 minutes with a bad security group change, which taught me more than the exam did. My father is stable now and has full time care. I am ready to work, and honestly I am impatient to.

States the gap calmly, in the first sentence, without apologising Distinguishes honestly between the hard months and the productive ones Has something countable to show for the time, not just intentions

Then they probe: Is your caring situation resolved, or could it pull you away again?

Practise this one
Junior

Everything on your CV is labs and personal projects. You have never run anything in production. Why should we take that risk?

What most people say

I know I have not worked in production but I am a fast learner and I am very passionate, so I am sure I would pick it up quickly.

It concedes the weakness and then offers only enthusiasm to cover it, so the interviewer is left with exactly the doubt they started with and no evidence to argue against it.

The structure behind a strong answer

  1. 1

    Agree with the premise. Do not fight it. Name honestly what production has that your projects do not.

  2. 2

    Situation. Pick the one project where real people depended on it, even a handful of them.

  3. 3

    Action under pressure. Describe a moment it broke and what you did, in order, with your actual reasoning.

  4. 4

    Result with a figure. Give downtime, users affected, cost, or recovery time. Real numbers, even small ones.

  5. 5

    What you carry forward. Name the habit it created, the alarm, the runbook, the rollback path you now build first.

What gets you hired

You are right, and I will not pretend otherwise. What I have not had is an on call rota, a change board, or a bill that someone else signs off. But I would push back gently on the word labs, because one of my projects has real users and it has hurt me. I run the booking site for a local yoga studio, about 60 bookings a week, on ECS with an RDS Postgres database. In March I ran a schema migration straight against production at nine in the evening because I assumed nobody books at that hour. Two people did. The migration held a lock, bookings timed out for around 11 minutes, and the owner texted me. I rolled back, apologised, and the same week I built the thing I should have had first, a staging environment with a copy of the schema and a migration step in the pipeline that runs there before production. It costs me about 8 dollars a month extra and I have not repeated the mistake. So my honest pitch is this. I know what a bad change feels like when someone is depending on you, I just have not felt it at your scale yet. Give me the pager with a mentor beside me and I will not treat it lightly.

Concedes the gap honestly instead of arguing with the premise Has one project where real users felt a real failure Describes a fix that outlived the incident, such as staging or a rollback path

Then they probe: What do you think would surprise you most about our production environment?

Practise this one
Junior

Walk me through something manual and painful that you automated. Why did you pick that particular task, and how did you know it worked?

What most people say

I wrote a script to automate a boring task and it saved a lot of time. It was mostly Python and it worked pretty well.

No task, no numbers, no safety rails, and no evidence anyone but the candidate ever ran the thing again.

The structure behind a strong answer

  1. 1

    Situation. Name the manual task and who was doing it, including how often.

  2. 2

    The cost. Quantify the pain before you touched it, minutes per run times runs per week.

  3. 3

    Task. Say why you chose this one over other candidates, frequency plus error rate.

  4. 4

    Action. Describe the tool you used and the safety rails, dry run, logging, rollback.

  5. 5

    Adoption. Explain how you got teammates to use it, docs, demo, or running it for them first.

  6. 6

    Result. Give the before and after number, and any errors it removed.

  7. 7

    Learned. One line on the maintenance cost you took on by owning the script.

What gets you hired

On my team, onboarding a new developer meant someone manually creating an IAM user, attaching four policies by hand, and mailing the keys. It took about 45 minutes, and we did it maybe twice a month, so it was not huge, but it went wrong often. Twice we over-granted permissions because someone copied the wrong policy. I picked it because the error rate mattered more than the time. I wrote a small Terraform module plus a wrapper script that took a name and a role, created the user in the right group, and produced a temporary console password instead of long-lived keys. I made the default a plan-only dry run, so you had to pass a flag to actually apply. I did not just drop it in the repo. I ran it for the next two hires myself, screen-shared once in standup, and wrote a ten line README with the exact command. Onboarding went from about 45 minutes to under 5, and more importantly the over-permission mistakes stopped, we had zero in the next six months. We also got a free audit trail because everything was in git. The honest lesson is that I now own that module. When AWS changed a policy name I had to fix it. Automation is not free, you inherit it.

Chose the task using error rate, not just time saved Built in a dry run and safe defaults Drove adoption instead of shipping and leaving

Then they probe: How did you decide this was worth automating rather than just doing it faster?

Practise this one
Junior

Tell me about a time you had more work assigned than you could realistically finish. What did you do?

What most people say

I just worked late and pushed through until everything was done. I do not like letting the team down, so I made it work.

It rewards heroics over planning, hides the trade off from the manager, and gives no evidence you can rank work or communicate risk. It also quietly signals you will burn out and then surprise everyone.

The structure behind a strong answer

  1. 1

    Situation. Two sentences only. Name the week, the team size, and the number of tasks on your plate so the scale is real.

  2. 2

    Task. Say what you personally owned and what date somebody else was depending on you for.

  3. 3

    Action. Describe how you ranked the work, who you told, and what you explicitly proposed dropping or delaying.

  4. 4

    Result. Give a number. What shipped on time, what slipped, and by how long it slipped.

  5. 5

    Learned. One line on the habit you kept, for example raising a risk flag at the halfway point.

What gets you hired

In my first cloud support role I picked up a sprint with 9 tickets, and about three days in I could see maybe five of them would land. Instead of guessing, I spent twenty minutes listing them with two columns, who is blocked if this is late, and how long I actually think it takes. Three tickets had a real downstream dependency, the release engineer needed a Terraform module updated by Thursday. The rest were cleanup that nobody was waiting on. I messaged my lead the same morning with that list and a proposal, I finish the three blocking items plus one quick win, and I move the other five to next sprint. I did not ask him to decide from scratch, I gave him a default and asked him to correct it. He moved four out and pulled one to another engineer. I delivered the three blocking tickets, the Terraform module landed on Wednesday, a full 24 hours before the release, and nothing slipped for anyone downstream. What I kept from that is the halfway check. Around the middle of any block of work I stop and honestly compare remaining hours to remaining tasks, and if the maths does not work I say so that day rather than on the deadline.

Raised the risk early and with a concrete list rather than a vague sorry Ranked work by who is blocked downstream, not by what was most fun Came with a proposed plan and let the manager correct it

Then they probe: What if your manager had said no, everything still has to ship?

Practise this one
Junior

Tell me about a time you had to explain a technical constraint to a product manager or a non-engineer, and they did not want to hear it.

What most people say

I told the PM it was not technically possible and that they did not really understand how the database works.

It centres blame on the other person, gives no translation, no options, and no outcome, which reads as an engineer who cannot be trusted in front of stakeholders.

The structure behind a strong answer

  1. 1

    Situation. Two sentences, no more. Name the product goal and who wanted it, so the constraint has stakes.

  2. 2

    The constraint in plain words. State the limit once, in business terms, with a number attached to it.

  3. 3

    Action: translate, do not lecture. Describe how you swapped jargon for cost, time, or user impact language they already care about.

  4. 4

    Action: offer options, not a wall. Show that you brought at least two paths with different trade-offs instead of a flat no.

  5. 5

    Result with a figure. Say what they chose and quantify the outcome, latency, cost, or a shipped date.

  6. 6

    What you learned. One line on how you now open these conversations earlier and with a picture.

What gets you hired

On my last team the PM wanted a live analytics dashboard on the customer portal, refreshing every second. Our reporting data sat in a nightly warehouse dump, so per second was simply not available without a streaming pipeline. Rather than saying it was impossible, I spent an hour measuring the real numbers. I found the freshest we could get from the existing pipeline was about 15 minutes, and getting to near real time would mean roughly six weeks of work on a streaming setup plus around 300 euros a month extra in infrastructure. I drew a two box diagram on a whiteboard, one box for where the data lives, one for where the screen lives, and showed the gap. Then I gave the PM three options, 15 minute freshness now for zero extra cost, 1 minute freshness in about two weeks by scheduling more frequent loads, or true live in six weeks with the new pipeline. She picked the 1 minute option because customers were happy with near live but not willing to wait a quarter. We shipped it in nine working days. The lesson for me was that the constraint was never the argument. The missing piece was that she could not see the shape of the system, and once she could, she chose well without me needing to win anything.

Measured the constraint before arguing about it Translated the limit into cost and time, not schema talk Offered several options with different trade-offs

Then they probe: What if the PM had insisted on the one second refresh anyway?

Practise this one
Mid

You are three months into a new team that owns infrastructure you have never worked with. What did your first 90 days look like, and what did you change?

What most people say

I spent the first few months just learning the systems and getting up to speed, and after that I started picking up tickets like everyone else.

It has no plan, no evidence of judgement about what to learn first, and no change at the end, so three months produced nothing the team could point at.

The structure behind a strong answer

  1. 1

    Situation. One sentence on the team, the stack and what was unfamiliar to you specifically.

  2. 2

    Task: the 90 day contract. State what you agreed with your manager the success criteria would be.

  3. 3

    Action: days 1 to 30, learn. Say how you built the map: on call shadowing, tickets, dashboards, pairing, reading incidents.

  4. 4

    Action: days 31 to 60, earn trust. Describe the small unglamorous fix you shipped that proved you understood the system.

  5. 5

    Action: days 61 to 90, change something. Describe the one improvement you proposed with evidence, and how you got buy in.

  6. 6

    Result. Quantify the change: minutes saved, alerts reduced, cost cut, incidents avoided.

  7. 7

    What you learned. Say what you would sequence differently, ten seconds, honest not performative.

What gets you hired

I joined a team running a Kubernetes platform on Azure, and I had come from a mostly AWS and virtual machine background. With my manager I agreed a simple contract for 90 days: be useful on call by day 60, and land one improvement by day 90. First 30 days I shadowed on call, which is the fastest map of any system because alerts show you what actually breaks. I read every postmortem from the previous year, eight of them, and I noticed that four mentioned the same noisy alert. I also paired twice a week rather than asking questions ad hoc, so I did not drain anyone. Days 31 to 60 I took the boring work on purpose. I fixed a broken runbook link, then I rewrote the on call handover doc, then I took my first primary shift with a buddy. Nothing heroic, but it proved I could be trusted with the pager. Days 61 to 90 I went back to that noisy alert. I pulled the data: it had fired 214 times in three months and exactly zero of those had been real. It was a memory threshold set against the wrong metric. I rewrote it, added a fifteen minute duration clause, and it fired twice in the next quarter, both real. Our page volume for the team dropped by about a third. What I would change is the sequence: I would shadow on call in week one, not week two, because I lost time reading code before I knew what actually hurt.

Agreed explicit success criteria with the manager up front Used on call and postmortems as the fastest map of what really breaks Earned trust with small unglamorous work before proposing change

Then they probe: How did you decide what NOT to change in the first 90 days?

Practise this one
Mid

Walk me through how you onboarded a new engineer onto your team. What did you own, and how did you know it worked?

What most people say

I showed him around the codebase, gave him access to everything, and told him to ask me if he had any questions.

Open door with no structure means the new person carries all the cost of asking, and nothing is measured.

The structure behind a strong answer

  1. 1

    Situation. One sentence on the team, the stack, and who joined.

  2. 2

    Task. State what you owned explicitly, for example first commit inside five days.

  3. 3

    Action, week one. Describe the environment setup, the buddy rhythm, and the first safe ticket.

  4. 4

    Action, week two to four. Describe how you widened scope and how you handed the on call context over.

  5. 5

    Result. Give the measurable, days to first merge or days to first on call shift.

  6. 6

    Reflection. Say what you changed in the onboarding for the next person.

What gets you hired

When we hired a cloud engineer from a support background, I owned his first 30 days. I set 1 measurable goal up front, a merged production change inside 5 working days, because I have seen people sit in read only mode for a month and lose all their confidence. Day 1 was access only, so I made a checklist of the 11 things he needed, AWS SSO, the VPN, the repo, PagerDuty, and we walked it together instead of me mailing him a wiki link. Day 2 I gave him a deliberately small but real ticket, adding a CloudWatch alarm for our queue depth, something that mattered but could not take production down. I paired for the first hour, then left him alone and told him I would check in at 4pm. He merged on day 3. Then we widened it. Weeks 2 and 3 he shadowed my on call, and I made him write the incident notes so he had to actually follow the reasoning. He took his first supported on call shift in week 4. The measurable was that his first merge landed on day 3 against a team average of about 18 days, and I know that because I went back and checked the previous 3 hires. The thing I changed afterwards was the access checklist. I turned it into a Terraform module, so the next hire had every account provisioned before she walked in.

Sets an explicit outcome for onboarding, not just a feeling Gives a small but real first ticket rather than a toy task Turns the manual setup into automation for the next person

Then they probe: What if the new hire is more senior than you?

Practise this one
Mid

Tell me about a time you had to give a peer feedback they did not want to hear. How did you deliver it and what happened?

What most people say

I used the feedback sandwich. I said something nice, then the criticism, then something nice again, and it went fine.

Formulaic, hides the message, and gives no evidence that any behaviour actually changed afterwards.

The structure behind a strong answer

  1. 1

    Situation. Set the stakes fast. Say what the behaviour was costing the team.

  2. 2

    Evidence. Bring specific instances, not impressions. Two or three concrete examples.

  3. 3

    The conversation. Private, soon, and framed around impact rather than character.

  4. 4

    Their reaction. Say honestly how they responded, including if it went badly at first.

  5. 5

    Result. Give a number showing the behaviour changed, or admit it did not and why.

  6. 6

    What you learned. One line on what you would do earlier next time.

What gets you hired

A peer of mine was approving pull requests within about 2 minutes of them being opened. I noticed because I checked the timestamps on a week of merges, and 11 of his 14 approvals were under 3 minutes on changes over 200 lines. It came to a head when a bad IAM change he had approved got through and gave a Lambda far wider read access than intended. Nothing was breached, but security opened a finding. I did not raise it in the retro, because doing that in front of 8 people would have made him defensive and made me look like I was scoring a point. I asked him for 15 minutes the next morning. I said 1 specific thing, that the 2 minute approvals were putting the risk back on the author, and I showed him the timestamps rather than telling him he was careless. He pushed back at first and said he was drowning in review requests, which was actually true. He had 23 open at that moment. So the fix was partly mine to make. We split the review rota so nobody carried more than 4 open reviews at a time, and we added a required second approver on anything touching IAM. Over the next month his median review time went from under 3 minutes to about 22, and we had 0 security findings from merged changes that quarter. What I learned is that behaviour I read as sloppiness was mostly load, and I should have asked about load before I built my case.

Brings evidence rather than impressions Chooses a private setting and goes early Discovers the root cause was systemic load, not character

Then they probe: What if they had refused to change?

Practise this one
Mid

Describe a time you pushed back on a decision your manager had already made.

What most people say

My manager made a bad call about our infrastructure, so I told him it was a bad call and eventually he came around because I was right.

It reads as ego rather than judgement, it gives no cost, no alternative and no numbers, and it makes the interviewer wonder how you talk about them behind their back.

The structure behind a strong answer

  1. 1

    Situation. Set up the decision your manager made and the pressure they were under.

  2. 2

    Task, your read. State the specific risk you saw and why it mattered commercially.

  3. 3

    Action, the ask. Describe how you asked for room to challenge it, not permission to disobey.

  4. 4

    Action, the alternative. Show that you brought an option, not just a complaint, with a cost or timeline.

  5. 5

    Result with a number. Give the decision that landed and a concrete figure for its impact.

  6. 6

    Learned. Say how you now make disagreement easy for a manager to accept.

What gets you hired

My manager committed us to a hard date for moving our API onto Kubernetes because a partner launch depended on it. I thought the date was reachable, but the plan was to migrate all 14 services in one weekend cutover. I asked for 20 minutes with him and said, I am not asking you to move the date, I want to show you a cheaper way to hit it. I had costed two paths. The big-bang cutover needed roughly 30 hours of on-call attention in one weekend and gave us one rollback lever for everything. The alternative was to move 3 low-traffic services first, keep the old platform running behind a weighted DNS record, and shift the remaining 11 across two weeks. That second path hit the same partner date with a rollback that took 90 seconds per service instead of a full weekend restore. He picked the phased path. We landed the migration two days before the partner launch, and one service did roll back, for about 11 minutes, affecting under 1 percent of traffic. What I learned is that managers rarely defend the plan, they defend the outcome, so if you protect the outcome you are allowed to change the plan.

Protected the manager's outcome while changing the method Arrived with a costed alternative, not just an objection Would have committed cleanly if overruled

Then they probe: What would you have done if he had kept the big-bang plan?

Practise this one
Mid

Tell me about a code review that turned into an argument. How did it end?

What most people say

The other engineer was being really defensive about my comments, so I just approved the pull request to keep the peace and moved on.

Approving to avoid conflict tells the interviewer that your review signal is worthless under pressure, and it hides a problem in the codebase rather than resolving it.

The structure behind a strong answer

  1. 1

    Situation. Name the pull request, whose it was, and where the thread turned sour.

  2. 2

    Task, the real issue. Separate the technical point from the tone problem in one line each.

  3. 3

    Action, stop the thread. Describe how you moved it out of comments and into a call or a doc.

  4. 4

    Action, decide the rule. Show how you turned a one-off fight into a written standard people can point at.

  5. 5

    Result with a number. Give a concrete figure, review time, defect count, or how many later PRs used the rule.

  6. 6

    Learned. Say what you now do differently in the first two comments of a review.

What gets you hired

I reviewed a Terraform pull request that hardcoded IAM policies with wildcard actions on S3. I left 9 comments, and by comment 4 the tone had gone from technical to sharp on both sides. I recognised that we were now arguing about who was right, not about the policy. I stopped commenting and wrote, I think we are burning time in text, can we take 15 minutes on a call. On the call it turned out we disagreed about one thing only. He thought least privilege was a nice-to-have for an internal tool, and I thought the tool's role could be assumed by anything in the account. We split it. I accepted his wildcard for read actions, he narrowed the write actions to two buckets, and we agreed the whole thing was a security question, not a taste question, so we wrote it down as a two-line rule in the repo's contributing guide. That rule has been cited in at least 12 pull requests since, and reviews on IAM changes dropped from around 2 days to under half a day because nobody re-argues it. What I learned is that once a review thread hits about four rounds, the format is the problem, so I move to voice early now.

Noticed the thread had stopped being technical Changed medium instead of escalating volume Converted the fight into a written standard

Then they probe: How do you decide when a review comment is a blocker versus a nit?

Practise this one
Mid

Tell me about a mistake of yours that cost the company real money. How much, and how did you handle it?

What most people say

I once left an environment running over a weekend, but it was only a small amount and my manager said not to worry about it.

It minimises the cost, hides behind the manager's reassurance, and skips the only part that matters, which is what you changed so the money stops leaking next time.

The structure behind a strong answer

  1. 1

    Situation. Set the scene in two sentences, the workload, the account, and why cost mattered there.

  2. 2

    Task. Say what you owned and what decision was yours to make at the time.

  3. 3

    Action: the mistake. Name the wrong call directly, in one sentence, with no hedging language around it.

  4. 4

    Action: the detection and the fix. Explain how it surfaced, how fast you stopped the bleed, and who you told.

  5. 5

    Result. Give the actual money figure and the time window. A real number here is what makes this credible.

  6. 6

    Learned. Describe the control you added, an alarm, a budget, a tag policy, so it cannot silently recur.

What gets you hired

I was building a data pipeline and I turned on VPC flow logs plus verbose application logging to debug a networking issue, sending everything into CloudWatch Logs. I fixed the issue in two days and forgot to turn the verbose logging back down. Nobody noticed because the account had no budget alarm. Three weeks later our finance partner flagged that the account had jumped from roughly 900 dollars a month to 3,100 dollars, and about 1,800 dollars of that was log ingestion I had caused. I did not wait to be asked. I confirmed the source in Cost Explorer within an hour, dropped the log level back, set a 7 day retention on those groups instead of never expire, and posted the numbers in the team channel with my name on them. Cost was back to baseline within a day. Then I fixed the reason it went unseen for three weeks. I added an AWS Budgets alarm at 20 percent above the trailing monthly average per account, routed to our on call channel, and I made debug logging in that pipeline an environment variable with a default of off, so the lazy path is the cheap path. My manager's reaction was useful. He said the 1,800 dollars was fine, the three weeks of silence was not. That is the framing I have carried since. A cost mistake is a monitoring gap, and monitoring is my job.

States the actual money figure without flinching Reported it themselves rather than waiting to be caught Treats an unnoticed cost spike as a monitoring failure

Then they probe: What should the alarm threshold actually be, and why not just a fixed dollar amount?

Practise this one
Mid

Walk me through an incident you personally caused. Focus on what you communicated, to whom, and when.

What most people say

I found the bug quickly and fixed it, then I told everyone once it was already resolved so nobody had to panic about it.

Sitting on an active incident to avoid alarming people is the exact behaviour that erodes trust, and it leaves support answering angry customers with no information.

The structure behind a strong answer

  1. 1

    Situation. One or two sentences. The service, the users affected, and the time of day it hit.

  2. 2

    Task. Say what role you took, were you the cause, the responder, or both at once.

  3. 3

    Action: first 5 minutes. Who did you page, what did you say, and did you declare an incident or hesitate.

  4. 4

    Action: during. Describe your update cadence, what support and the customer facing team were told, and who owned comms while you fixed.

  5. 5

    Result. Impact percentage, duration, and how the customer facing message landed. Use real numbers here.

  6. 6

    Learned. One change to the comms path, not just the code path. Runbook, template, or clearer incident roles.

What gets you hired

I pushed a config change that shortened a connection pool timeout on our checkout service. It looked harmless in staging. In production, under real traffic, it started dropping connections during a database failover window and roughly 3 percent of checkout requests failed. Our error rate alert fired about 4 minutes after the deploy. I knew immediately it was mine, because nothing else had shipped. So I did three things in the first 5 minutes. I declared an incident rather than treating it as a blip, I said in the channel that my config change was the likely cause and I was rolling it back, and I asked a teammate to own comms so I could focus on the rollback. She told support within 2 minutes that checkout was degraded and gave them a line they could use with customers. The rollback took 6 minutes to propagate. Total impact was about 3 percent of checkout traffic for 11 minutes, around 340 failed attempts, and support had already been briefed before the first ticket arrived. In the postmortem we changed two things. The config value moved behind a staged rollout instead of going to 100 percent at once, and we wrote a two line customer impact template so the comms owner does not have to draft prose under pressure. The technical fix mattered. Honestly, the comms template has saved us more pain since.

Named themselves as the cause in the channel unprompted Separated the fixing role from the comms role Briefed support before customers complained

Then they probe: Why hand comms to someone else instead of doing both?

Practise this one
Mid

Where do you want to be in three years?

What most people say

In three years I would like to be in a senior or lead position, ideally moving toward architecture or management.

It names titles instead of work, it is indistinguishable from every other candidate's answer, and it quietly tells the interviewer you are measuring the job by how fast you can leave it.

The structure behind a strong answer

  1. 1

    Situation, where you are now. One honest line about your current level and what you can and cannot yet do.

  2. 2

    Task, the direction. Name the kind of engineer you want to be, in behaviour terms, not in job title terms.

  3. 3

    Action, the evidence. Show the trajectory is already running, with something you have done in the last year.

  4. 4

    Result with a figure. Prove the direction with a real outcome, a cost saved, a time cut, a person you unblocked.

  5. 5

    Fit, tie it to this role. Say what this specific team gives you that makes the three years plausible here.

What gets you hired

In three years I want to be the person on the team who is trusted with the changes nobody else wants to touch. Concretely, that means owning a platform area end to end, the pipeline, the infrastructure code, the cost, and the pager for it. Right now I am not that person, I am still slower than I want to be at reading unfamiliar systems under pressure. But the direction is already visible. Over the last year at my current job I took over our Terraform modules, which nobody owned, and got them from a state where a plan took 9 minutes and half the team was scared to run it, to modules with a CI check and a plan under 90 seconds. Two colleagues who previously raised tickets for environments now create them themselves. That is the shape of the work I want more of, making other people faster, not just shipping my own tickets. What I need for that is a team where I can see a real system carry real load, and mentors who have already broken things at that scale. That is what this role has and my last one did not. If I am here in three years I want to be the one onboarding the next person into this platform.

Describes work and responsibility rather than job titles Backs the ambition with something already done in the last year Names a concrete measurable improvement they drove

Then they probe: That sounds like it could point at management. Is that where you are heading?

Practise this one
Mid

Tell me about a time you went well beyond your assigned scope. How did you decide it was worth doing, and how did you keep your actual work on track?

What most people say

I saw the pipeline was a mess so I rewrote it over a weekend and surprised the team on Monday. Everyone was really happy about it.

A surprise rewrite with no mandate, no review, and no time box reads as a risk to hiring managers, not as ownership.

The structure behind a strong answer

  1. 1

    Situation. Set the scene in two sentences, your assigned ticket and the thing you noticed.

  2. 2

    The judgment call. Explain the trade-off you weighed, cost of ignoring it versus cost of the detour.

  3. 3

    Task. State the extra work you took on and the time box you gave yourself.

  4. 4

    Action. Describe how you told your lead, what you delivered, and what you deliberately left out.

  5. 5

    Protecting the day job. Say concretely how your committed work still shipped on time.

  6. 6

    Result. Give a hard number for the improvement and for your original ticket.

  7. 7

    Learned. One line on when going out of scope would have been the wrong call.

What gets you hired

I was assigned a small feature on an Azure Functions app. While tracing a bug I noticed our deployment pipeline was rebuilding every function app on every merge, even untouched ones, so a one line change took about 22 minutes to reach staging. Nobody owned the pipeline, it was inherited from a contractor. I thought about ignoring it. The reason I did not is that the team merged roughly 15 times a week, so we were burning close to 5 hours a week just waiting, and the slow feedback was making people batch up risky changes. I asked my lead for a one day time box, explicitly framed as: if I cannot get it under 10 minutes in a day, I stop and we live with it. I added path filters and caching in the YAML pipeline, and I deliberately did not touch the release approvals, which were out of my depth. It came in at about 6 minutes for a single app change. My feature ticket still shipped on the Thursday it was due, because I took the pipeline day from my own slack, not from the estimate. What I learned is that the time box was the thing that made it safe. It gave my lead a cheap way to say yes and a cheap way to stop me.

Asked for a time box before starting Quantified the cost of doing nothing Named what they deliberately did not touch

Then they probe: What if your lead had said no?

Practise this one
Mid

Describe something you improved that nobody asked you to improve. How did you get other people to care about it?

What most people say

I refactored a lot of our old code because it was really messy and nobody had cleaned it up. It is much nicer now.

Nicer is not a result, and a refactor with no user, no metric, and no supporter is indistinguishable from personal preference.

The structure behind a strong answer

  1. 1

    Situation. Name the thing that was quietly bad and who was silently paying for it.

  2. 2

    Why nobody asked. Explain the reason it stayed invisible, no dashboard, no complaint channel, normalised pain.

  3. 3

    Making the case. Describe how you turned the pain into evidence somebody senior could act on.

  4. 4

    Action. Walk through the smallest version you shipped first, not the full dream.

  5. 5

    Getting buy-in. Name the ally you found and how you made saying yes cheap for them.

  6. 6

    Result. Give a measured before and after, plus how you knew it stuck.

  7. 7

    Learned. One line on the difference between a good idea and an adopted one.

What gets you hired

Our on-call runbook was a wiki page that had not been touched in over a year. Nobody complained, because the two people who knew the system just fixed things from memory. The people paying for it were the new joiners, and I was one of them. Instead of arguing that documentation matters, I collected evidence. Over six weeks I logged every alert I took and how long I spent working out what to even check. The median was about 18 minutes before I did anything useful, and three of the alerts I could not resolve at all without paging a senior at night. I took that to our team lead as a single slide: 18 minutes of pure lookup per alert, and two night pages a month that we were causing ourselves. That made it his problem, not my preference. I shipped the smallest version first, one page per alert name, three lines each: what it means, first thing to check, who owns it. I did the top eight alerts by volume, which covered about 70 percent of pages, and left the long tail. Median time to first useful action fell to under 5 minutes, and we went two months with no wake-up escalation. The lesson is that the writing was the easy half. The evidence is what got it adopted.

Gathered evidence before pitching Shipped the smallest useful slice, not the dream version Found an ally and made yes cheap

Then they probe: How do you stop it going stale again in a year?

Practise this one
Mid

You are getting urgent, conflicting priorities from two different managers. Walk me through a time that happened and how you resolved it.

What most people say

I tried to do both. I worked on the platform migration during the day and the reporting dashboard in the evenings so nobody was disappointed.

Serving both means committing to a schedule nobody agreed to, and it usually ends with two half done things and one burnt out engineer. It also removes the information the managers needed to make a real call about their own priorities.

The structure behind a strong answer

  1. 1

    Situation. Name both asks in one line each, and say clearly why they could not both be first.

  2. 2

    Task. State that your job was to force a decision, not to secretly pick a winner yourself.

  3. 3

    Action. Show the move, you made the conflict visible in one place with the cost of each option attached.

  4. 4

    Result. Say who decided, what actually got done, and give the timing figure for the deferred item.

  5. 5

    Learned. One line on the standing habit you built, for example a shared visible queue of your committed work.

What gets you hired

Last year I was shared between a platform team and a product team. My platform lead wanted the CI runners moved off the old self hosted pool that week, my product lead wanted a checkout metrics pipeline live for a launch on the same Friday. Both were genuinely urgent, and each of them assumed they had all of me. My first move was to stop replying in two separate DM threads, because that is what let the conflict stay invisible. I wrote one short message with both of them on it. It listed my capacity honestly, roughly 4 working days that week, what each task actually costs, 3 days for the runner migration and 2 for the pipeline, and what breaks in each case if it is deferred. Then I said, I will start on whichever you two agree comes first, and I will start it this afternoon. They talked for about fifteen minutes and decided the launch pipeline came first because a public date was attached to it. The runner migration moved out by 9 days and the platform lead pulled in another engineer to start it. The pipeline was live on Thursday, one day before the launch. The habit I kept is a public queue. My committed work for the week now sits in one channel both leads can see, so when something new arrives the question is always what comes off the list, not whether I can absorb it.

Put both stakeholders in one thread instead of managing the conflict privately Attached honest capacity and cost figures to each option rather than vibes Proposed a default so the decision could be made in minutes, not days

Then they probe: What if the two managers refuse to agree and both keep pushing?

Practise this one
Mid

Tell me about your worst on-call week. What actually happened, and what did you change afterwards?

What most people say

It was brutal, I barely slept, but I got through it. That is just part of being on call, you have to be able to take it.

It frames suffering as a badge instead of a signal, and it gives the interviewer no evidence that anything improved. A team that hires this mindset keeps the same broken rotation forever, and pages stay noisy.

The structure behind a strong answer

  1. 1

    Situation. Set the scale with numbers, how many pages, over how many nights, and what the service was.

  2. 2

    Task. Separate the immediate job, keep the service up, from the real job, stop the week repeating.

  3. 3

    Action. Describe triage under fatigue, what you handed off, and the follow up work you actually got funded.

  4. 4

    Result. Give the before and after page count or noise reduction, with a real number.

  5. 5

    Learned. One line on sustainability, for example an alert quality rule the team adopted.

What gets you hired

The worst one was a week where I took 41 pages in 7 days, and eleven of them were between midnight and five in the morning. The service was a payments ingestion pipeline. By Wednesday I was making bad decisions, I misread a dashboard and restarted the wrong consumer group, which added about four minutes of lag to a queue that was already behind. That was my signal that fatigue was now the biggest risk in the room, bigger than the alerts. So I did two things. I asked my lead for a co pilot for the rest of the rotation, we split nights, and I wrote down every page as it came in with one column, would a human have needed to act on this. By Sunday the answer was no for twenty six of the forty one. I brought that list to the retro instead of bringing feelings, and it was hard to argue with. We deleted nine alerts outright, we moved seven from paging to a daily digest, and we fixed one flapping disk threshold that alone had fired fourteen times. The next rotation took 9 pages in the week, and one at night. What I took from it is that a page you ignore is worse than no page, because it trains you to ignore the next one. Our rule now is simple, if an alert fires and no human action follows, it either gets fixed or it stops paging.

Counted the pages and classified them instead of describing feelings Recognised fatigue itself as an operational risk and escalated for help Turned the bad week into a durable alerting rule the team kept

Then they probe: How did you convince the team to actually delete alerts, given the fear of missing something?

Practise this one
Mid

Describe a time a stakeholder gave you a deadline you knew was not achievable. What did you do?

What most people say

I told them it was not possible in that time, but they said it was fixed, so we worked weekends and just about made it.

It rewards heroics over planning, shows no evidence based negotiation, and quietly signals that the person will always fold under pressure.

The structure behind a strong answer

  1. 1

    Situation. Set the scene in about 20 seconds, the date demanded and why it mattered commercially.

  2. 2

    Task: what you owed them. Say what you were actually accountable for delivering, so the scope is clear.

  3. 3

    Action: break the work down. Describe how you decomposed it and estimated, so the pushback rests on evidence not on vibes.

  4. 4

    Action: negotiate scope, not effort. Show you offered a smaller thing on the date rather than the same thing later.

  5. 5

    Result with a figure. Give the date you actually hit and what shipped, with a number.

  6. 6

    Learning. One line about raising the flag early rather than at the last standup.

What gets you hired

Sales committed a migration of a customer facing API from a single virtual machine to a managed container platform in three weeks, because the client had a contract renewal on that date. I did not argue in the meeting. I spent half a day breaking the work into pieces, and my estimate came out around six weeks, mostly because we had 14 services sharing one host and no automated tests on 4 of them. So I went back with a written breakdown, not an opinion. Then I did the thing that actually unlocked it. I asked which part of the migration the client would actually notice. It turned out they only cared about the two services in their integration path, and about the uptime guarantee. So I proposed shipping those two services onto the platform in three weeks, keeping the other 12 on the old host behind the same load balancer, and finishing the rest over the next quarter. My manager took that to the client and they accepted. We migrated the two services in 17 days with zero downtime, and finished the remaining 12 about ten weeks later. What I learned is that the deadline is usually not the negotiable thing. The scope is. If I show up with a breakdown and a smaller version that still lands on their date, the conversation stops being a fight.

Estimated before pushing back Negotiated scope rather than asking for more time Kept the client's actual outcome in view

Then they probe: What if there was no way to cut scope?

Practise this one
Mid

Tell me about a time you had to tell a client or a business owner that a delivery was going to slip.

What most people say

We were behind, so I kept pushing hard and told them the day before launch that we needed another two weeks. They were annoyed but it was fine.

Telling them the day before removes every option they had, which is the actual damage, and calling it fine shows no awareness of the trust cost.

The structure behind a strong answer

  1. 1

    Situation. Name the commitment and the audience in about two sentences, keep it tight.

  2. 2

    The moment you knew. Be precise about when you realised, and how many days that was before the deadline.

  3. 3

    Action: tell them fast and once. Describe how you told them, in what channel, with what new date attached.

  4. 4

    Action: bring the recovery plan. Show you arrived with mitigation and a partial delivery, not just an apology.

  5. 5

    Result with a figure. State the real revised date you hit and the impact you avoided, with a number.

  6. 6

    Learning. One line about the signal you now watch so you can flag earlier next time.

What gets you hired

We were moving a client's file processing workload onto AWS, with a hard cutover date because their old data centre contract ended. About 18 days out I hit a wall. Files over roughly 200 megabytes were timing out through our Lambda based pipeline, and reworking it to use Fargate for the large files was going to cost me around two weeks I did not have. I did not sit on it. I told the account manager and the client the same afternoon, on a call, not by email. I said three things clearly. Here is what has changed, here is the new date, and here is what I can still give you by the original one. The recovery plan was to ship the small file path on time, which covered about 92 percent of their daily volume, and to keep the last 8 percent running on the old data centre for a three week extension while I moved the large file path to Fargate. The client got the extension for around 4000 euros, which was far cheaper than a failed cutover. We finished the large file path 16 days later. What I took away is that a slip you flag with two weeks of runway is a planning problem, and the same slip flagged the day before is a trust problem. The engineering was identical. The relationship was not.

Flagged the slip as soon as it was real Arrived with a revised date and a partial delivery Quantified the impact in volume and cost

Then they probe: Why did you not just try to hit the date and see?

Practise this one
Senior

Tell me about the most ambiguous problem you have owned, where nobody could even tell you what success looked like. How did you create clarity?

What most people say

It was really unclear so I asked the stakeholders for requirements and once they gave me the requirements I built what they asked for.

It outsources the hard part, which was the ambiguity itself, and a senior engineer is hired precisely because the requirements do not exist yet.

The structure behind a strong answer

  1. 1

    Situation. Describe the ambiguity precisely, unclear goal, unclear owner, or conflicting stakeholders.

  2. 2

    Task. Say what you took on, and be explicit that you chose to own it rather than being assigned.

  3. 3

    Action: define success in writing. Explain how you wrote a one page definition of done and got it disagreed with.

  4. 4

    Action: reduce uncertainty cheaply. Describe the smallest experiment or spike that killed the biggest unknown first.

  5. 5

    Action: decide and set a reversal point. State the decision you made and the condition under which you would undo it.

  6. 6

    Result. Quantify: time, cost, incident rate, or the number of teams unblocked.

  7. 7

    What you learned. Say what you would now do earlier, and name the cost of the delay honestly.

What gets you hired

We were told to reduce cloud spend, and that was the whole brief. No target, no owner, two clouds, and finance and engineering meaning different things by the word waste. I took it on because nobody else would. First I made success concrete and put it in writing: a one page memo saying we would cut monthly spend by fifteen percent within one quarter without any reduction in availability, and I circulated it explicitly asking people to disagree with it. Two people did, and that argument was the most useful hour of the whole project, because it surfaced that finance cared about the forecast, not the absolute number. Then I attacked the biggest unknown cheaply. Rather than a full tagging programme, which would have taken months, I spent three days pulling cost and usage data and found that 62 percent of spend sat in eleven resources. That killed the idea that this was a broad hygiene problem. It was a handful of things. I made a call I could reverse: rightsize those eleven, starting with two non production clusters, with a written rollback if p99 latency moved more than ten percent. It did not move. Over the quarter we took monthly spend down by about 19 percent, roughly 24,000 euros a month, with no availability regression. What I would do differently is write the definition of done in week one rather than week three. Those two weeks of politely gathering opinions produced nothing that the memo did not produce in a day.

Wrote the definition of success themselves and invited disagreement Killed the biggest unknown with a cheap short experiment Named an explicit rollback condition before making the change

Then they probe: How do you commit when you might be wrong?

Practise this one
Senior

Tell me about a crunch period your team went through. What did you do to get people through it without burning them out?

What most people say

We all pulled together, worked late for a few weeks, and got it over the line. Everyone stepped up.

Heroics with no scope negotiation is a leadership failure dressed up as commitment, and it hides the cost.

The structure behind a strong answer

  1. 1

    Situation. Name the deadline, why it was immovable, and how many people you had.

  2. 2

    Task. State the two things you owned, delivery and the humans doing it.

  3. 3

    Action, scope. Say exactly what you cut or deferred, and who you negotiated that with.

  4. 4

    Action, people. Describe rotation, hard stops, and what you absorbed yourself.

  5. 5

    Result. Give both numbers, the delivery outcome and the human outcome.

  6. 6

    What you would change. Be honest about the cost, and what you fixed to avoid a repeat.

What gets you hired

We had a hard date to exit a colocated datacentre because the contract ended, and we had 5 engineers and about 9 weeks to migrate roughly 40 services into AWS. The first thing I did was refuse to treat all 40 as equal. I sat with the product owner for 2 hours and we sorted them into 31 that had to move, 6 that could be retired outright because nobody had called them in a year, and 3 that could stay on a short bridge line. That cut about a quarter of the work before anyone wrote a line of Terraform. On the people side I did 3 specific things. I made a rule that nobody worked 2 weekends in a row, so we rotated 1 person on a Saturday and I took 2 of the 9 myself. I cancelled every meeting that was not the 15 minute morning sync, which gave everyone back about 4 hours a week. And I made the migration boring, 1 Terraform module reused across services, so the 10th migration took 40 minutes rather than the full day the first one took. We finished with 6 days to spare, we had 1 rollback and 0 customer facing outages, and nobody on the team resigned in the 6 months after, which for me is the number that actually matters. What I would change is that I should have pushed for the retire list in week 1 of discovery, not week 3. Those 2 weeks of panic were self inflicted.

Cuts scope before asking for hours Protects people with concrete rules like no two weekends in a row Reports a human outcome as well as a delivery outcome

Then they probe: What if the business refused to cut scope?

Practise this one
Senior

You have a teammate who is not pulling their weight and the rest of the team has started routing around them. What do you do?

What most people say

Honestly, I would just pick up the slack myself. It is faster than dealing with it and the work still gets done.

Absorbing the gap hides the problem, punishes the reliable people, and guarantees it repeats next quarter.

The structure behind a strong answer

  1. 1

    Situation. Describe the visible symptom, not your verdict on the person.

  2. 2

    Check yourself first. Ask what they are actually blocked by before you assume laziness.

  3. 3

    Direct conversation. Private, specific, and about observable output over a stated period.

  4. 4

    Support plus a line. Offer real help, and state clearly what has to change and by when.

  5. 5

    Escalate with evidence. If nothing changes, bring the manager facts and dates, never adjectives.

  6. 6

    Result. Give the outcome with a number, and say what happened to the person.

What gets you hired

This happened on a platform team of 6. One engineer had shipped almost nothing in about 2 months, and I noticed the team had quietly stopped assigning him anything on the critical path, which is the real warning sign. Before I decided he was coasting I checked the boring explanations. I looked at his ticket history and saw he had been pulled into 3 separate compliance audits that nobody had counted as work, and he was also the only person who understood our old on premise DNS, so he was getting interrupted maybe 5 times a day. So the first hour of the conversation was not a performance talk, it was me asking what his week actually looked like. Part of it was genuinely load, and part of it was that he had stopped asking for help because he felt behind. I did 2 things. I got the audit work made visible in the sprint so it stopped being invisible tax, and I paired him with a junior for 2 weeks to document the DNS knowledge, which took the interrupt load off him and gave him a win the team could see. I also said plainly that I needed to see him back on the critical path within 1 sprint. Within 6 weeks he was carrying 2 of the 8 epics and his review turnaround was under a day. If it had not moved, my next step was the manager with dates and evidence, not with the word lazy. The lesson is that routing around someone is a team decision nobody ever makes out loud, and it needs to be surfaced.

Looks for invisible work before assuming laziness Goes to the person before going to the manager Sets a clear, dated expectation instead of hinting

Then they probe: What if it turned out to be genuine underperformance?

Practise this one
Senior

Tell me about a time you were overruled on a technical decision and it later went wrong. What did you do next?

What most people say

I told them it would not scale, they ignored me, and then it fell over exactly like I said it would. I had already warned everyone.

The whole answer is about being vindicated, which tells the interviewer you would rather be right than have the system stay up, and that you are unpleasant to lose an argument to.

The structure behind a strong answer

  1. 1

    Situation. Set the decision, the two options, and who made the final call.

  2. 2

    Task, your dissent on record. Show that you wrote the risk down before the decision, not after the incident.

  3. 3

    Action, commit anyway. Describe how you made the chosen path succeed as hard as you could.

  4. 4

    Action, when it broke. Explain what you did in the incident and, crucially, what you did not say.

  5. 5

    Result with a number. Give the blast radius and the fix with a real figure and a real duration.

  6. 6

    Learned. Say how you now make a dissent cheap to revisit without a told-you-so.

What gets you hired

We were choosing how to run a bursty image pipeline. I argued for SQS plus autoscaled workers. The principal on the team wanted Lambda directly off S3 events because it was simpler, and he made the call. I disagreed, and I wrote my concern in the design doc in two lines. If a burst exceeds our concurrency limit, we will throttle and lose events silently. Then I committed. I built the Lambda path properly, including a dead-letter queue, because if we were going to do it that way I wanted the failure mode visible. Four months later a customer bulk-uploaded and we hit the account concurrency ceiling. About 6 percent of images failed for roughly 25 minutes. Because of the dead-letter queue we lost nothing, we replayed 3,100 messages the same afternoon. In the postmortem I did not mention the design doc. I let the timeline speak, and someone else found the line. We moved to SQS in front of the function that week and the change took two days because the handler was already idempotent. What I learned is that the useful thing is not winning the argument, it is making the chosen path fail loudly instead of silently, so the data can win the argument for you later.

Recorded the risk before the decision, not after the outage Committed fully and made the chosen path safer Did not claim vindication in the postmortem

Then they probe: Why did you build the dead-letter queue if you disagreed with the design?

Practise this one
Senior

Tell me about a conflict between two teams that you had to resolve, where you did not have authority over either of them.

What most people say

The two teams could not agree, so I escalated it to the directors and let them decide who was right. It was above my pay grade.

Escalating as a first move, with no attempt to find common ground or frame the trade-off, tells the interviewer you will hand every hard conversation upward and add no judgement of your own.

The structure behind a strong answer

  1. 1

    Situation. Name the two teams, what each wanted, and why both positions were reasonable.

  2. 2

    Task, your standing. Be explicit that you had no authority, and say who did.

  3. 3

    Action, find the shared number. Show how you replaced two opinions with one metric both teams cared about.

  4. 4

    Action, make it cheap to agree. Describe the small reversible experiment or the time-boxed trial you proposed.

  5. 5

    Result with a number. Give the outcome with a real figure, cost, latency, or a deadline that was met.

  6. 6

    Learned. One line on how you now open a cross-team disagreement.

What gets you hired

Our platform team wanted every service behind a shared ingress with mTLS. The payments team refused, because the extra hop had added around 18 milliseconds in a spike test and they had a hard 200 millisecond p99 budget from the card processor. Both were right, and it had been stuck for about three weeks in a mail thread. I had authority over neither team. I asked both leads for one 45 minute session and opened it with, we are not going to decide policy today, we are going to agree what number decides this. We landed on payments p99 measured at the client, with a ceiling of 200 milliseconds. Then I proposed a small reversible trial. Put one non-critical payments endpoint behind the ingress for two weeks and measure. The real cost came in at 4 milliseconds at p99, not 18, because the earlier test had run without connection reuse. Payments moved all 6 endpoints across in the next sprint and the platform team got their mTLS coverage from 60 percent to 100 percent of services. What I learned is that most cross-team standoffs are not a values fight, they are two teams arguing from different measurements, so my first move now is to agree the metric before anyone argues the answer.

Agreed the deciding metric before arguing the answer Proposed a small reversible trial instead of a policy fight Treated both positions as reasonable and evidence-based

Then they probe: What if the measurement had gone the other way and the ingress really cost 18 milliseconds?

Practise this one
Senior

Tell me about an architecture or design decision you made that turned out to be wrong. How long did it take you to admit it, and what did it cost?

What most people say

I picked Kubernetes and it was overkill, but the team learned a lot from it so I do not really regret the decision at all.

It admits the error then immediately takes it back, so the interviewer hears someone who cannot hold a mistake in their hands for a full sentence.

The structure behind a strong answer

  1. 1

    Situation. Set the constraints you had at the time. A senior story must show the decision was reasonable then.

  2. 2

    Task. State the decision you owned and who else was affected by it downstream.

  3. 3

    Action: the signal. Describe the evidence that told you it was wrong, and be honest about how long you ignored it.

  4. 4

    Action: the reversal. Explain how you told the team, how you got buy-in to unwind, and how you sequenced the migration safely.

  5. 5

    Result. Quantify both the cost of the mistake and the improvement after. Two numbers, one sentence each.

  6. 6

    Learned. Name the reasoning flaw, not the technology. What will you check earlier next time.

What gets you hired

Two years ago I chose to split a modest internal platform into 6 microservices on Kubernetes. The reasoning was not stupid at the time. We expected the team to grow to about 20 people and we wanted independent deploys. The team never grew past 5. Within six months the cost was obvious in the numbers. A one line change often touched 3 repositories, our median lead time from commit to production had gone from about 2 days to 9, and two engineers were spending roughly a day a week on cluster upkeep instead of product work. I sat on it for about a quarter, because it was my call and I did not want to look like I had wasted the migration. What broke the deadlock was a retro where a junior said out loud that she was scared to ship. I wrote a short document, four pages, that said plainly the split was mine and it was wrong for a team of 5, and proposed collapsing back to one deployable with clear internal module boundaries. We did it over about 10 weeks, service by service, keeping the boundaries as packages so we could re-split later if we ever needed to. Lead time came back to under a day and we cut the platform bill by roughly 40 percent. The real lesson was not that Kubernetes is bad. It is that I designed for the org chart I hoped for instead of the one I had, and I now write down the headcount assumption a design depends on, so the assumption can be checked instead of quietly believed.

Explains why the decision was defensible at the time Admits the delay in facing it, without theatrics Quantifies both the damage and the recovery

Then they probe: How did you get the team to trust another migration from the person who caused the first one?

Practise this one
Senior

Tell me about a failure on your team that was not technically your fault, but that you owned anyway.

What most people say

One of my engineers deployed without testing and took down the API. I coached him on it and it has not happened since.

It hangs a teammate out in an interview room, and it implies the fix was a person rather than a missing gate the leader was responsible for.

The structure behind a strong answer

  1. 1

    Situation. Describe the failure and be clear whose hands were on the keyboard, without naming or shaming.

  2. 2

    Task. Explain why it landed on you anyway. Your review, your standard, your runbook, your hiring.

  3. 3

    Action: upward. Say exactly what you told leadership and how you framed it, in first person plural or singular, never they.

  4. 4

    Action: inward. Describe how you handled the engineer involved, protecting them while still holding a standard.

  5. 5

    Result. Give the impact numbers and the measurable improvement after the fix. Concrete figures, not adjectives.

  6. 6

    Learned. Name the systemic gap you closed, and state clearly why blaming the individual would have fixed nothing.

What gets you hired

An engineer on my team ran a schema migration against our production Azure SQL database during business hours. It locked a hot table and our API error rate went to roughly 40 percent for 18 minutes before we rolled forward with an index hint. His hands were on the keyboard. It was still my failure, for three reasons I said out loud in the postmortem. We had no migration runbook. We had no rule that schema changes go out in the low traffic window, which for us is between 2am and 4am and carries about 4 percent of daily traffic. And I had approved his pull request myself without asking how the migration would behave under load. When I briefed our director I used the word I, not he. In the postmortem I made sure the engineer presented the timeline rather than being discussed in the third person, because the fastest way to guarantee the next person hides an incident is to let this one be a public trial. What changed: migrations now run through a pipeline stage that refuses to execute outside the window unless a second approver signs off, and every migration pull request has a three question template about lock behaviour, row count and rollback. We have shipped around 60 migrations since with zero production impact. That engineer is still on the team and is now the person who reviews other people's migrations, which is the outcome I wanted. Blaming him would have cost me an engineer and left the same hole open for whoever went next.

Uses I when reporting the failure upward Protects the individual while raising the standard Points to a missing gate rather than a careless person

Then they probe: What if the same engineer did it again?

Practise this one
Senior

Tell me about a system or process that had no clear owner and was quietly hurting the organisation. How did you take it on, and how did you hand it off?

What most people say

Nobody was looking after it so I just took it over. I basically became the go to person for that system and I still am.

Becoming the permanent single point of failure is the failure mode, not the win, and senior interviewers hear it immediately.

The structure behind a strong answer

  1. 1

    Situation. Describe the orphaned system, its blast radius, and how many teams depended on it.

  2. 2

    Why it was orphaned. Give the organisational reason, reorg, attrition, or a team boundary nobody redrew.

  3. 3

    Task. State the outcome you were aiming at, not the tasks, for example stop the bleeding then rehome it.

  4. 4

    Action, stabilise. Explain the fastest safe steps you took to reduce harm in week one.

  5. 5

    Action, rehome. Describe how you found a permanent owner and what you gave them to accept it.

  6. 6

    Result. Give hard numbers, incidents, cost, or hours, before and after.

  7. 7

    Learned. One line on avoiding becoming the hero the system depends on.

What gets you hired

After a reorg, an internal certificate rotation service ended up with no owning team. It served about 30 services across AWS and Azure. Nobody owned it, so certs got rotated by whoever happened to notice, and we had two outages in a quarter, one of them 40 minutes of failed internal traffic. I did not adopt it. My goal was to stop the bleeding, then rehome it. Week one I did the cheap stuff. I added expiry alerts at 30, 14 and 7 days, which took a day and immediately removed the class of surprise outage. I also wrote a one page inventory of every consumer, because most of the fear around the system was that nobody knew who would break. Then I made it adoptable. I cut the operational surface, deleted two unused code paths, wrote a runbook, and put a real cost on it: about 2 hours a month of steady state work. That number was what let me negotiate. The platform team said yes because I could show it was 2 hours, not a swamp. We handed it over in six weeks. Zero cert related incidents in the following nine months, and I deliberately stayed off the on-call rota for it so the new owners built the muscle. The lesson is that the hard part was not the code. It was making the thing cheap enough that a team could say yes.

Separated stabilising from owning Reduced the operational cost to make handover possible Quantified steady state effort to negotiate with

Then they probe: What would you have done if no team would take it?

Practise this one
Senior

Describe a time you had to deliberately drop or cut committed work. How did you decide what to cut, and how did you communicate it?

What most people say

We cut the nice-to-haves and kept the important stuff. The team pulled together and we got the critical things out.

It gives no mechanism, no axis, and no numbers, so it could describe any project on earth. An interviewer cannot tell whether you made a good call or a lucky one, and a senior candidate is hired precisely for the mechanism.

The structure behind a strong answer

  1. 1

    Situation. Give the shape of the crunch, the scope committed, the people you had, and the fixed date.

  2. 2

    Task. Say plainly that your job was to choose what would not happen, not to find extra hours.

  3. 3

    Action. Name the axis you cut on, for example irreversibility, blast radius, or external commitments, and show it applied.

  4. 4

    Result. Give the numbers, how much scope was cut, what shipped, and what the deferred work cost later.

  5. 5

    Learned. One line on how you now make the cut earlier and with less drama, for example a standing must, should, and can wait split.

What gets you hired

We had six weeks to migrate about 40 services off a retiring Azure subscription, with 4 engineers. Two weeks in it was obvious that the full plan, migrate everything and modernise the deployment pipeline at the same time, needed roughly ten weeks. The date was fixed, the subscription was actually being switched off, so scope was the only variable. I cut on one axis, reversibility. Anything that is hard or impossible to do after the shutdown stays. Anything we can still do next quarter goes. That put the twenty two services with live customer traffic and the data stores in the must group, and it pushed the pipeline modernisation and the eleven internal tools out entirely, because those we could rebuild later from source at leisure. I made the cut public in one page, with the deferred list named, an owner, and a target quarter, so nobody could claim it had been quietly abandoned. We migrated all twenty two customer facing services with 9 days of buffer, and the total customer visible downtime across the whole programme was about 11 minutes, in one planned window. The deferred pipeline work took six weeks the following quarter and cost us nothing but patience. The thing I do differently now is make the cut in week one, not week two. Waiting for certainty is expensive, because every day you wait, you burn team hours on work you already know you are going to throw away.

Names an explicit axis for the cut rather than a general sense of importance Makes the deferred list public with owners and dates instead of letting it vanish Cuts early and says explicitly that late cuts waste team hours

Then they probe: How do you handle a stakeholder who says everything is critical?

Practise this one
Senior

Tell me about a time you or someone on your team was close to burnout. What did you actually change?

What most people say

I told them to take a few days off and to look after themselves. Work life balance is really important to me.

Time off with the same broken system waiting on the other side just moves the crash by a week. It shows sympathy but no diagnosis, and no structural change that an interviewer can evaluate.

The structure behind a strong answer

  1. 1

    Situation. Describe the signal you noticed, and be concrete, for example commits at 2am for weeks or a person going quiet.

  2. 2

    Task. State the two jobs, protect the person now, and remove the structural cause so it does not recur.

  3. 3

    Action. Show what you actually took off the plate and what you changed in how work arrived.

  4. 4

    Result. Use a number, hours reclaimed, interrupts per week, retention, or a delivery figure that held anyway.

  5. 5

    Learned. One line on the guardrail you keep now, for example a hard rule about unplanned work in a sprint.

What gets you hired

One of my engineers, our strongest on Kubernetes, started going quiet in standup and I noticed his commits were landing after 11pm most nights for about three weeks. When I asked, he was the default answer for every cluster question in the company, so his day was interrupts and his real work happened at night. Telling him to rest would have fixed nothing, he would come back to the same inbox. So I attacked the interrupt load. First I measured it, we counted his Slack asks for one week, 47 of them, and I read every one. Thirty one were answerable from a runbook that did not exist yet. We spent two days writing eight short runbooks and pinned them. Then we put a rotating cluster support duty in place, one engineer per week owns the questions, so he was the front door only one week in four. And I moved two of his projects out to buy back the time we had just spent. Within a month his direct asks dropped from 47 a week to about 9, and his commits moved back into working hours. He is still on that team two years later, which for me is the real result. The guardrail I keep now is that a single point of failure in a person is an incident, not a compliment. If only one human can answer a class of question, that is a gap to close, and it is on me to close it before the person breaks.

Measured the interrupt load before trying to fix it Removed the structural cause rather than only offering time off Treats a single point of failure in a person as a risk to close

Then they probe: How do you spot burnout in someone who insists they are fine?

Practise this one
Senior

Walk me through a time you had to communicate with customers or executives during a live outage. What did you actually say and when?

What most people say

I was heads down fixing it, so I let the manager handle the customers. Once it was resolved I wrote up what happened.

Going silent during an incident is the failure mode customers remember longest, and delegating all comms without a cadence leaves the business guessing for hours.

The structure behind a strong answer

  1. 1

    Situation. Set the outage in two sentences, what broke and who was affected, with a rough blast radius.

  2. 2

    Task: your role in the room. Be clear whether you were fixing, coordinating, or communicating, senior answers usually split these.

  3. 3

    Action: the comms cadence. Describe the update rhythm you set and what each update contained, even when nothing changed.

  4. 4

    Action: what you refused to promise. Show restraint, explain how you gave impact and next update time instead of a fake ETA.

  5. 5

    Result with a figure. Give duration, percentage of traffic or users affected, and the recovery time.

  6. 6

    Follow through. One line on the postmortem and the one change that actually shipped from it.

What gets you hired

We had a regional failure that took out the write path on our payments API. It affected around 12 percent of requests for 41 minutes. I was the incident lead, which meant I explicitly did not touch the fix. I put two engineers on the failover and took comms myself, because trying to do both is how you get a bad fix and a silent status page. I set a 15 minute cadence and I held it, even in the two updates where I had nothing new. Each update had exactly three parts. What is broken, in customer language, so card payments failing rather than the primary node is unreachable. Who is affected, with the number. And when the next update comes. I deliberately gave no ETA for the fix for the first 30 minutes, because I did not have one, and a guessed ETA that slips does more damage than saying we do not know yet. When we cut over to the secondary region, error rates came back under 1 percent within 4 minutes. The client facing summary went out within the hour, and the full postmortem 48 hours later. The concrete change from it was a scripted regional failover, which took our recovery from 41 minutes down to about 6 in the next drill. What I learned is that during an incident, the business is not asking you to be fast. It is asking you to be predictable.

Separated incident command from hands on fixing Held a fixed update cadence even with no news Refused to invent an ETA under pressure

Then they probe: An executive is pressing you for an ETA in minute ten. What do you say?

Practise this one
Senior

Tell me about a time you said no to a senior stakeholder. How did you make it stick without becoming the person who blocks everything?

What most people say

It was a bad idea and against best practice, so I refused and told them we do not do things that way here.

Best practice is not an argument to a business owner, and a refusal with no alternative just teaches people to route around you next time.

The structure behind a strong answer

  1. 1

    Situation. Name who asked, what they asked for, and why it was reasonable from their side.

  2. 2

    Why it was a no. Ground the refusal in risk, cost, or compliance with a number, never in personal preference.

  3. 3

    Action: agree on the goal first. Show you restated their objective before disagreeing on the method, this is what makes a no land.

  4. 4

    Action: give them a yes shaped path. Describe the alternative you offered that got them most of what they wanted, safely.

  5. 5

    Result with a figure. State what happened, the cost avoided or the risk closed, with a real number.

  6. 6

    Learning. One line on how you keep a reputation for saying yes to the goal, not to every request.

What gets you hired

A director asked for direct public access to an Azure SQL database so an external analytics vendor could query it directly. From his side it was a sensible ask, the vendor needed data and the pipeline my team was proposing looked slow. I did not open with no. I started by agreeing on the goal out loud, that the vendor gets fresh data within a day, and that his quarterly board deck stops being hand assembled. Then I put a number on the risk. Opening that firewall would have exposed a table holding personal data for around 40000 customers, and under our GDPR posture that was a reportable exposure with fines that start in the tens of thousands, not a style disagreement. Then I gave him a path to yes. I offered a read only replica with the personal columns masked, exposed through Private Link to the vendor, which I could have running in eight working days. He took it. The vendor was querying in nine days, we never opened a public endpoint, and the masked replica cost about 180 euros a month, which was under his budget line. The reason it stuck is that I never said no to what he wanted. I said no to one specific mechanism, and I owned delivering the alternative on a date. That is the difference between being the person who protects the platform and the person everyone learns to work around.

Agreed on the stakeholder's goal before disagreeing on the method Quantified the risk instead of citing best practice Delivered an alternative with a date and a cost

Then they probe: What if he had escalated over your head and got it approved anyway?

Practise this one

IAM & Security

2 questions · Junior, Mid
Junior

A service needs to read objects from exactly one storage bucket. How do you grant it access?

What most people say

I would create an access key with S3 permissions and put it in the service config.

Two failures: long-lived static keys (a leak risk that should be a role) and unscoped "S3 permissions" (likely s3:* on all buckets). It is the answer that causes real breaches.

The structure behind a strong answer

  1. 1

    Identity, not keys. Attach a role (AWS IAM role / Azure managed identity) to the service so it gets short-lived, auto-rotated credentials. Never embed static access keys.

  2. 2

    Scope the action. Grant only the read actions needed (e.g. s3:GetObject, and s3:ListBucket if it must list), not s3:* and not write/delete.

  3. 3

    Scope the resource. Limit the policy to that one bucket’s ARN (and its objects, bucket-arn/*), never Resource: *.

  4. 4

    Verify and prove. Test that it can read that bucket and is denied on another, and on writes. Least privilege is only real once you have confirmed the denials.

What gets you hired

I would attach a role to the service, an IAM role or, on Azure, a managed identity, so it gets short-lived rotating credentials and no static keys live anywhere. The policy grants only s3:GetObject (plus s3:ListBucket if it needs to list), scoped to that one bucket’s ARN and its objects, not Resource: *. Then I would verify it can read that bucket but is denied on another bucket and on any write, because least privilege is not done until you have confirmed what it cannot do.

Reaches for a role/managed identity over static keys Scopes by action AND resource Verifies the denials, not just the allow

Then they probe: The service runs on-prem, not in the cloud, so it cannot use a role. Now what?

Practise this one
Mid

How would you manage application secrets (DB passwords, API keys) across environments? Walk me through the options and their trade-offs.

What most people say

I would put them in a .env file and add it to gitignore so it does not get committed.

gitignore is a foot-gun, not a control: the secret is still plaintext on every machine, has no rotation, no audit, and no per-environment isolation. It also says nothing about how the running workload gets the value safely.

The structure behind a strong answer

  1. 1

    State the goal. Secrets should never live in source control or a built image. They need controlled access, an audit trail, and a path to rotation without a redeploy.

  2. 2

    Rank the options. Plaintext in repo (never) < env vars injected at deploy (better, but flat and unrotatable) < a managed secret store (Secrets Manager, Azure Key Vault, Vault) that gives versioning, access policies, and audit.

  3. 3

    Control access with identity. Grant the workload an identity (IAM role, managed identity) that is authorized to read only the secrets it needs, so no human and no static credential is in the loop.

  4. 4

    Plan rotation. Prefer short-lived/dynamic credentials where you can; otherwise schedule rotation and make the app re-read the secret on a cache TTL rather than only at boot.

  5. 5

    Audit and scope per environment. Separate stores or paths per environment so a staging leak cannot touch prod, and turn on access logging so you can answer "who read this and when".

What gets you hired

The rule is no secret in source control or a baked image. I rank the options. Plaintext is out. Env vars injected at deploy are an improvement but flat, hard to rotate, and easy to leak in logs, I have seen a full DB password land in a stack trace. The real answer is a managed store, Secrets Manager or Azure Key Vault, which gives versioning, access policies, and an audit log of every read. The workload reads secrets through its own identity, an IAM role or managed identity scoped to just the 2 or 3 secret ARNs it actually needs, so there is no static credential to leak. Where the store supports dynamic credentials I take them, a database secret that lives 60 minutes and is issued per pod is far better than one shared password. Otherwise I schedule rotation, typically every 30 days, and have the app re-read on a cache TTL of about 5 minutes so a rotation takes effect without a redeploy. And I separate stores per environment, a distinct vault and distinct policy for dev, staging, and prod, so a staging leak cannot reach production data.

Rules out repo and image plaintext immediately Names a managed store and what it adds (audit, versioning, rotation) Uses a workload identity instead of a static credential

Then they probe: Your secret store is now a single point of failure for every app at boot. How do you handle that?

Practise this one

Architecture & Design

11 questions · Junior, Senior, Principal
Junior

How does putting a load balancer in front of your app improve availability, and what does it NOT solve?

What most people say

It makes the app faster and handles more traffic, so it is more available.

It conflates throughput with availability and never mentions health checks or taking unhealthy instances out of rotation, which is the actual availability mechanism. It also claims a benefit the question is explicitly probing the limits of.

The structure behind a strong answer

  1. 1

    Name the core job. A load balancer spreads requests across multiple backend instances, so no single instance is a hard dependency for serving traffic.

  2. 2

    Explain the availability mechanism. It runs health checks and stops routing to instances that fail them, so one dead or unhealthy instance is taken out of rotation automatically instead of serving errors.

  3. 3

    Require more than one healthy target. Availability only improves if there are at least two healthy instances behind it. One instance behind a load balancer is still a single point of failure.

  4. 4

    State what it does NOT solve. It does not fix a shared database that is down, a bad deploy rolled out to every instance, a region outage, or application bugs that return 500s on healthy instances. It moves traffic away from sick instances, it does not make the system correct.

What gets you hired

A load balancer distributes requests across multiple instances and runs health checks against each one, typically hitting a health endpoint every 10 seconds. When an instance fails, say 3 checks in a row, it gets pulled from rotation within about 30 seconds and traffic shifts to the healthy ones automatically, with no human involved. That only helps if I actually have 2 or more healthy instances behind it, ideally spread across 3 availability zones, otherwise it is still a single point of failure with a nicer name. What it does not solve is a shared dependency failing. If the 1 database everything talks to is down, all 6 of my instances are perfectly healthy and all 6 return errors. It also does not save me from a bad deploy that ships the same bug to every instance, or from a whole region outage. A load balancer routes around sick instances, it does not make a broken system healthy.

Names health checks as the availability mechanism Notes you need two or more healthy instances Lists failure modes the load balancer cannot fix

Then they probe: Your load balancer shows all targets as unhealthy but the app works fine when you curl it directly. Where do you look?

Practise this one
Junior

What is autoscaling, and how does it decide when to add or remove instances?

What most people say

It just adds more servers when the site gets busy so it does not crash.

It gestures at the idea but never names the metric, the threshold, or the cooldown, and ignores that scaling is not instantaneous and that the app must be stateless. That gap is exactly where real autoscaling configs go wrong.

The structure behind a strong answer

  1. 1

    Define the goal. Autoscaling automatically changes how many instances are running so capacity tracks demand, instead of someone manually adding servers.

  2. 2

    Describe the decision loop. A scaling policy watches a metric (CPU, request count, queue depth) against a threshold. Cross it for a sustained period and the group adds instances; drop below and it removes them.

  3. 3

    Scale out vs scale up. Autoscaling here means scaling out (more instances), not scaling up (a bigger instance). Out adds redundancy and avoids a restart; up has a ceiling and downtime.

  4. 4

    Note the limits. New instances take time to boot and warm up, so autoscaling lags sudden spikes. It only works if the app is stateless enough that any instance can serve any request, and you set sensible min and max bounds.

What gets you hired

Autoscaling automatically adjusts the number of running instances so capacity follows demand. A scaling policy watches a metric, CPU, request count per target, or queue depth, and acts when it crosses a threshold for a sustained window. A typical policy I would set is target tracking at 60 percent average CPU, or scale out when CPU stays above 70 percent for 3 consecutive minutes and scale in when it drops below 30 percent for 10 minutes. The longer scale in window is deliberate, it stops the group flapping. That is scaling out, adding more instances, not scaling up to a bigger box, which has a hard ceiling and needs a restart to change. The catch is that scaling lags. A new instance can take 2 to 3 minutes to boot, pull the image, and warm up, so a spike that arrives in 20 seconds will hurt before capacity shows up. It also only works if the app is stateless, so any instance can serve any request. I would set bounds as well, a minimum of 2 so I always have redundancy, and a maximum, say 20, so a traffic anomaly or a retry storm cannot run away with the bill.

Names a concrete metric and threshold Distinguishes scaling out from scaling up Knows scaling lags and needs a stateless app

Then they probe: Traffic spikes for 30 seconds and your app falls over before autoscaling reacts. Why, and what would you change?

Practise this one
Senior

Design a highly available web application on AWS. Walk me through the architecture and your trade-offs.

What most people say

I would put it on a big EC2 instance with a database and use a load balancer.

A load balancer in front of one instance and one DB is HA theatre, both are still single points of failure. It shows no per-tier failure analysis and no scaling or state story.

The structure behind a strong answer

  1. 1

    Clarify requirements first. Ask: expected load, latency/SLA target, read/write mix, budget, stateful or stateless? A senior scopes before drawing boxes.

  2. 2

    Remove SPOFs tier by tier. Edge: Route 53 + CloudFront. Compute: an Auto Scaling Group across ≥3 AZs behind an ALB. Data: a managed DB (RDS/Aurora) multi-AZ with a standby, read replicas for read scaling.

  3. 3

    Keep compute stateless. Session/state in a shared store (ElastiCache/DynamoDB) and uploads in S3, so any instance serves any request and scaling is trivial.

  4. 4

    Add elasticity and protection. Auto Scaling on a real signal (CPU/RPS/queue depth), health checks that drain bad instances, and a WAF/rate limiting at the edge.

  5. 5

    State the trade-offs. Multi-AZ for availability vs cost; Aurora vs RDS; sync standby (durability) vs async replica (lag); where you chose simplicity over theoretical perfection.

What gets you hired

First I’d pin requirements, load, SLA, read/write mix, budget. Then remove single points of failure tier by tier. At the edge, Route 53 and CloudFront. Compute is an Auto Scaling Group spread across at least three AZs behind an ALB, and I keep it stateless, sessions in ElastiCache or DynamoDB, uploads in S3, so any instance serves any request. The database is Aurora or RDS multi-AZ with a synchronous standby for failover, plus read replicas if reads dominate. Auto Scaling reacts to a real signal like RPS or queue depth, health checks drain bad nodes, and a WAF sits at the edge. The trade-offs I’d call out: multi-AZ roughly doubles DB cost for availability; a sync standby gives durability but a read replica only gives eventual consistency; and I’d keep it single-region unless DR or global latency justifies the multi-region jump.

Clarifies requirements before designing Removes SPOFs at every tier Keeps compute stateless

Then they probe: Traffic 10x overnight from a launch. What breaks first and how did your design prepare?

Practise this one
Senior

Design a disaster recovery strategy for a revenue-critical workload on AWS. How do you pick between backup-and-restore, pilot light, warm standby, and active-active?

What most people say

We take nightly backups and we could restore them to another region if something goes wrong.

It picks a pattern with no RPO/RTO target behind it, so it cannot say whether hours of data loss and a manual restore are acceptable. There is no automation, no rehearsal, and no awareness that the data tier sets the recovery floor.

The structure behind a strong answer

  1. 1

    Pin RPO and RTO from the business. Ask: how much data can we lose (RPO) and how long can we be down (RTO)? Quantify the cost of an hour of downtime. Those three numbers, not taste, choose the pattern.

  2. 2

    Map targets onto the four patterns. Backup-and-restore (hours of RTO, cheap), pilot light (core data replicated, scale up on failover), warm standby (a small always-on copy you scale up), active-active (near-zero RPO/RTO, highest cost and complexity).

  3. 3

    Get the data tier right first. Cross-region replication for the database (RDS/Aurora cross-region replica or snapshot copy) and S3 cross-region replication. RPO is governed by replication lag, so this tier sets the floor.

  4. 4

    Automate and rehearse failover. Route 53 health checks plus an infrastructure-as-code runbook so the standby region is reproducible. A DR plan you have never executed is a hope, not a plan.

  5. 5

    State the cost-versus-recovery trade-off. Each step up the ladder cuts RPO/RTO and multiplies cost. Name where the workload sits and why the next tier up is not justified.

What gets you hired

I start by getting RPO and RTO from the business and pricing an hour of downtime, because those numbers pick the pattern. For a revenue-critical workload, hours of RTO from backup-and-restore is usually unacceptable, but full active-active doubles the bill and adds conflict-resolution complexity I may not need. The middle of the ladder is where most such workloads land: pilot light if minutes-to-low-tens-of-minutes RTO is fine, warm standby if I need faster cutover. The data tier sets the floor, so I would use Aurora cross-region replication and S3 cross-region replication, and my RPO is bounded by replication lag. Failover is automated with Route 53 health checks and the standby region is defined in infrastructure-as-code so it is reproducible, and I rehearse it on a schedule. The trade-off I would say out loud: I picked warm standby over active-active because the RTO target did not justify the cost and the operational burden of multi-master data.

Derives the pattern from RPO/RTO and downtime cost Knows all four patterns and their cost/recovery curve Treats data replication as the floor on RPO

Then they probe: Your RPO target is near zero but your RTO can be an hour. What does that combination let you build?

Practise this one
Senior

Design a scalable async processing pipeline for spiky, long-running work (think video transcoding or report generation). Walk me through the queue-based architecture and its failure modes.

What most people say

The API call does the processing and returns the result when it is done. If it gets slow we add more servers.

Doing long work inside the request couples the caller to the slowest job, times out under spikes, and loses work on a crash. There is no decoupling, no retry story, and no handling of duplicate or poison messages, which are the actual operational realities of a pipeline.

The structure behind a strong answer

  1. 1

    Clarify the work and the SLA. Ask: how spiky is the load, how long does one job take, is ordering required, and is a result needed synchronously or can the caller poll/get notified? This decides queue choice and the API shape.

  2. 2

    Decouple with a queue. Producers enqueue a job and return immediately; a fleet of workers pulls from the queue and scales on queue depth. The queue absorbs spikes so producers never block on slow work.

  3. 3

    Make consumers safe to retry. Workers must be idempotent because at-least-once delivery means a job can be delivered twice. Use an idempotency key or a dedupe check so reprocessing does not double-charge or double-write.

  4. 4

    Plan for failure explicitly. Visibility timeout so a crashed worker releases its job, a dead-letter queue for poison messages after N retries, and an alarm on DLQ depth and queue age so a stuck pipeline is visible.

  5. 5

    State the trade-offs. At-least-once plus idempotency (simple, common) vs exactly-once (expensive, rarely truly needed); a plain queue vs FIFO ordering (which caps throughput); polling for results vs push notification.

What gets you hired

First I clarify the shape: how spiky the load is, whether a job takes 10 seconds or 40 minutes, whether ordering matters, and whether the caller needs the result synchronously. Then I decouple with a queue. The producer enqueues a job and returns a job id in under 100 milliseconds, and a worker fleet pulls from the queue and autoscales on queue depth, say a target of 10 messages per worker, so a spike to 50,000 jobs piles up harmlessly in the queue instead of timing out the API. The part that matters most is failure handling. Delivery is at-least-once, so workers must be idempotent via an idempotency key, otherwise a redelivered job transcodes the same video twice. I set the visibility timeout longer than the worst realistic job, so if p99 runtime is 8 minutes I set it around 12, and a crashed worker releases its job automatically. I add a dead-letter queue after 5 failed receives so one poison message cannot wedge the fleet, and I alarm on DLQ depth above 0 and on oldest-message age crossing something like 15 minutes, which is the real signal that consumers cannot keep up. Trade-offs I would name: I default to at-least-once plus idempotency rather than chasing exactly-once, because true exactly-once is expensive and rarely needed, and I only use FIFO ordering if the work genuinely requires it, since strict ordering caps throughput hard. The caller gets the result by polling the job id or via a push notification.

Decouples producers from consumers with a queue Makes workers idempotent for at-least-once delivery Uses a DLQ and visibility timeout for poison/crash handling

Then they probe: One malformed job keeps crashing your workers and they keep retrying it. What happens to the rest of the queue?

Practise this one
Senior

You own a system of many services and an incident took too long to diagnose. Design an observability strategy across the whole estate.

What most people say

We will add CloudWatch dashboards and set alerts on CPU and memory for every service.

Dashboards plus resource-level alerts give you a wall of graphs and a flood of pages that do not tell you whether users are affected. There is no tracing to follow a request across services, no correlation, and no SLO, so the next incident is just as slow to diagnose.

The structure behind a strong answer

  1. 1

    Anchor on the questions you need to answer. Observability exists to answer "is it broken, where, and why" fast. Start from the diagnostic questions and the user-facing symptoms, not from which tool to buy.

  2. 2

    Cover the three pillars deliberately. Metrics for aggregate health and trends, structured logs for detail on a specific event, and distributed traces to follow one request across service boundaries. Each answers a different question; you need all three.

  3. 3

    Correlate with a request/trace id. Propagate a trace id across every hop and stamp it into logs and metrics, so you can pivot from a metric spike to the exact traces to the relevant logs without guessing. Correlation is what turns three silos into one tool.

  4. 4

    Define SLOs and alert on symptoms. Set SLOs (latency, error rate, availability) from the user perspective and alert on SLO burn and user-facing symptoms, not on every CPU blip. Cause-based alerts cause fatigue and miss novel failures.

  5. 5

    State the trade-offs. Telemetry is not free: cardinality and retention cost money, and full tracing is expensive at volume, so sampling is a real trade-off (latency to keep failures vs cost). Name what you keep at full fidelity and what you sample.

What gets you hired

I start from the diagnostic questions, is it broken, where, and why, because that is exactly what failed in the last incident. I cover the three pillars on purpose. Metrics for aggregate health and trends, structured logs for the detail of a specific event, and distributed tracing to follow one request across service boundaries, which is the thing that is missing when an incident drags on for 3 hours. The glue is a trace id propagated across every hop and stamped into every log line and exemplar, so on-call can pivot from a p99 latency spike to the slow traces to the underlying logs in under a minute instead of grepping 12 services by hand. On alerting, I define SLOs from the user perspective, for example 99.9 percent availability and p99 under 300 milliseconds, and I page on SLO burn rate, a fast burn alert when we are consuming the monthly error budget 14 times faster than sustainable, and on user-facing symptoms rather than on CPU, because symptom alerts catch novel failures and cut fatigue. I would name the cost trade-off honestly. Telemetry costs real money in cardinality and retention, and tracing every request at 20,000 requests a second is not worth it, so I sample, keeping 100 percent of errors and slow requests and sampling the healthy majority at something like 1 percent. The goal is one workflow, alert to trace to logs, not three disconnected tools.

Frames observability around diagnostic questions Uses metrics, logs, and traces for their distinct jobs Correlates via a propagated trace id

Then they probe: Your team is drowning in alerts and starting to ignore the pager. How do you fix it?

Practise this one
Senior

Design a CI/CD pipeline that lets a team deploy many times a day safely. How do you make production changes low-risk with progressive delivery?

What most people say

We automate the build and deploy so that merging to main pushes straight to production for everyone.

Automating the pipeline is necessary but it is only half the story. Pushing to 100% of users at once means any bad change is a full outage, and there is no canary, no health-gated rollout, and no fast rollback, so frequent deploys become frequent incidents.

The structure behind a strong answer

  1. 1

    Build a trustworthy pipeline gate. Every change runs the same automated pipeline: unit and integration tests, security/dependency scanning, and an immutable build artifact. If the pipeline is not trusted, people batch changes, which makes each deploy riskier.

  2. 2

    Reduce the blast radius of a release. Do not flip 100% at once. Use canary or progressive rollout (a small percentage first), or blue-green for instant switchover, so a bad release hits few users before you catch it.

  3. 3

    Make the rollout observe and decide. Watch SLO metrics, error rate, and latency during the rollout and automatically halt or roll back on regression. Progressive delivery is only safe if it is gated on health, not just on time.

  4. 4

    Decouple deploy from release. Feature flags let you ship code dark and turn features on for a cohort independently of deployment, so a risky feature can be released gradually and killed instantly without a redeploy.

  5. 5

    State the trade-offs. Canary (low blast radius, slower, needs good metrics) vs blue-green (instant cutover and rollback, double the infra during the switch); flags add power but also config complexity and cleanup debt. Name which you pick and why.

What gets you hired

Deploying many times a day is only safe if each deploy is low risk, so I design for blast-radius control, not just automation. The foundation is a trusted pipeline: every change runs the same gate, unit and integration tests, dependency and security scanning, and produces one immutable artifact. I keep that pipeline under about 15 minutes, because if it takes an hour people batch changes and batch risk. Then I never flip everyone at once. I roll out progressively, a canary at 5 percent of traffic for 10 or 15 minutes, then 25, then 100, and I gate each step on health, watching error rate and p99 latency against the baseline and automatically halting or rolling back on regression. A bad release then reaches 5 percent of users for 10 minutes instead of everyone for an hour. I also decouple deploy from release with feature flags, so code ships dark and a feature is turned on for 1 percent, then 10, and killed instantly without a redeploy. Trade-offs I would name: canary keeps blast radius tiny but is slower and only works if the metrics are good, while blue-green gives an instant cutover and rollback at the cost of running 2 full copies of the infrastructure during the switch, and flags are powerful but accrue config debt, so I put an expiry on each one and clean them up. The point is that fast rollback and a small blast radius make high deploy frequency a safety feature, not a risk.

Ties deploy frequency to blast-radius control Uses canary/progressive or blue-green rollout Gates rollout on SLO/health with automated rollback

Then they probe: A canary looks healthy on its own metrics but is quietly corrupting data that only shows up later. How does your pipeline help, and where does it fall short?

Practise this one
Senior

A company asks you to choose between AWS and Azure for a new workload. How do you make the decision?

What most people say

AWS, it is the biggest and most popular cloud so it is the safe choice.

Popularity is not a requirement, and "biggest" does not mean best fit for this company. It ignores existing investments, team skills, the specific services the workload needs, and cost and lock-in, which are the factors a senior is actually accountable for.

The structure behind a strong answer

  1. 1

    Decide from requirements, not allegiance. Both are mature, capable clouds, so the answer is rarely "X is better". Start from the workload and constraints: the right question is which fits this company and this workload best.

  2. 2

    Weigh the organizational fit. Existing investments matter a lot: a Microsoft/.NET shop with Active Directory and enterprise agreements often gets real integration and pricing benefits from Azure, while teams already deep in AWS get velocity from familiarity. Account for the team skills you actually have.

  3. 3

    Map specific workload needs to each platform. Compare on the dimensions that matter for this workload: the specific managed services needed, data residency and regions, compliance certifications, and ecosystem/maturity. Avoid a generic feature checklist; weigh the few features this workload depends on.

  4. 4

    Account for cost and lock-in honestly. Model total cost of ownership including egress and committed-use discounts, and be honest that deep use of either platform creates lock-in. Decide where to use managed services for velocity and where to stay portable.

  5. 5

    State the trade-off and the call. Name the decisive factors and make a recommendation, then state what you would re-evaluate. Sitting on the fence is not a senior answer; a justified, reversible decision is.

What gets you hired

Both are mature, capable clouds, so I would not argue brand, I would decide from requirements. The single biggest factor is organizational fit and existing investment. If this is a Microsoft and .NET shop already running Active Directory with a 3 year enterprise agreement, Azure gives real integration and licensing benefits, Hybrid Benefit alone can cut Windows and SQL licensing costs by around 40 percent, and the team is productive on day 1. A team already 5 years deep in AWS gets that same velocity from what they know. Then I map the specific workload needs, the handful of managed services it truly depends on, data residency and which regions are actually available, and required compliance certifications, rather than a generic checklist where both score similarly. On cost I model total cost of ownership over about 3 years, including egress and committed-use discounts, not sticker compute prices, because egress at roughly 9 cents a gigabyte can dwarf the instance bill for a data-heavy workload. I would be honest about lock-in. Leaning hard on either platform's managed services buys velocity at the cost of portability, so I decide deliberately where that is worth it and where to stay portable. Then I make the call and name the decisive factor, for example Azure, because the AD integration and the existing enterprise agreement outweigh everything else, and I would note that if the workload mix shifts I would re-evaluate.

Decides from requirements, not brand loyalty Weighs existing investments and team skills heavily Maps the workload needs to each platform specifically

Then they probe: The CTO insists on a multi-cloud strategy to avoid lock-in. How do you respond?

Practise this one
Principal

Design a globally distributed, multi-region active-active system. Walk me through the hard parts.

What most people say

I would deploy the app to multiple regions and put a global load balancer in front.

That is the easy 10%. It ignores the entire hard problem, replicating and reconciling state across regions, conflict resolution, and the CAP trade-off. Glossing the data layer is the tell that someone has not run global systems.

The structure behind a strong answer

  1. 1

    Start from the requirement, not the topology. Why multi-region active-active? Latency, regional DR, or data residency? Active-active is the most expensive option, justify it before designing it.

  2. 2

    Name data as the hard part. Stateless compute per region is easy; the database is the problem. You must choose your consistency model: strong (with latency cost) vs eventual (with conflict cost).

  3. 3

    Resolve writes and conflicts. Either partition writes by region (home-region per entity, no conflicts) or accept multi-master with conflict resolution (CRDTs, last-write-wins, or app-level merge). Each has sharp edges.

  4. 4

    Route and fail over. Latency/geo routing (Route 53/Anycast) sends users to the nearest healthy region; health checks and automated failover with a tested runbook, failover you have never rehearsed does not work.

  5. 5

    State the CAP trade-off explicitly. In a partition you choose consistency or availability. Say which you pick for which data (e.g. payments strong, profile eventual) and why.

What gets you hired

I would first challenge the requirement, because active active roughly doubles the infrastructure bill, so is this for latency, DR, or residency? If it is justified, stateless compute per region is the easy part, data is where it lives or dies. I decide the consistency model per data class. Payments and inventory need strong consistency, and I will either pay the cross region round trip, which is about 80 to 100 milliseconds between, say, Frankfurt and Virginia, or partition writes by home region so conflicts cannot happen at all. Profile and preferences can be eventually consistent, converging in a second or two, with real conflict resolution behind them. For multi master data I use partitioned writes where I can and CRDTs or an explicit merge function where I cannot, never silent last write wins on anything that matters, because a clock skew of even 50 milliseconds quietly eats a customer's update. Users reach the nearest healthy region through latency based routing, with health checks that fail a region out after about 30 seconds, and failover that is automated and rehearsed in a game day every quarter, because untested failover is not failover. And I would say the CAP choice out loud. During a partition, my strongly consistent data favors consistency over availability, so those writes are rejected in the minority region, and I accept that.

Leads with data/consistency as the hard part Chooses consistency per data class Has a real conflict-resolution strategy

Then they probe: Two regions accept a conflicting write to the same record during a partition. What happens?

Practise this one
Principal

The org is moving to many cloud accounts across many teams. How do you design the multi-account landing zone and guardrails so teams move fast safely, instead of locking everything down or letting it become the wild west?

What most people say

I would set up an organization with separate accounts, apply service control policies to restrict what teams can do, and have the central cloud team review and approve new accounts and any policy exceptions.

The structure is fine but it centralizes approval, which becomes a bottleneck at scale. It also leans entirely on prevention and a gate, missing self-service, detection, and the autonomy that makes teams move fast.

The structure behind a strong answer

  1. 1

    Account boundaries as blast-radius control. Use per-team or per-environment accounts so a mistake or breach is contained, and group them so policy applies cleanly by tier.

  2. 2

    Guardrails, not gates. Set non-negotiable preventive controls as policy-as-code at the boundary, then give teams freedom inside it so they do not queue behind a central approver.

  3. 3

    Preventive and detective together. Block the catastrophic things outright and continuously detect drift on the rest, accepting that you cannot prevent everything without strangling delivery.

  4. 4

    Baseline every account automatically. Vend new accounts from a template with logging, identity, network, and budget baked in, so security is the default state, not a follow-up ticket.

  5. 5

    Make the secure path self-service. Let teams provision within guardrails without filing tickets, so governance scales with the org instead of bottlenecking on a central team.

What gets you hired

I design the landing zone so the boundary does the governing and teams stay autonomous inside it. I split accounts by team and environment, so a typical team owns 3 accounts, dev, staging, and prod, and a mistake or a breach stays inside one of them. Then I organize accounts into groups so policy applies cleanly by tier. At those boundaries I set guardrails as policy as code, not gates. I keep the preventive set small and non negotiable, usually 10 to 15 controls like blocking public storage, forbidding the disabling of logging, and restricting regions, and inside that box teams have real freedom so they never sit in a queue waiting on a central approver. I pair prevention with detection, because trying to prevent everything strangles delivery, so I continuously scan for drift and route findings straight back to the owning team with an SLA, say 7 days for a high severity finding. Every new account is vended from a template with logging, identity, network, and budgets baked in, so a team gets a compliant account in about 15 minutes and the secure baseline is the default state, not a follow up ticket. And vending is self service inside the guardrails, which is the only way governance keeps up when you are adding accounts every week. The principle is autonomy with a safety net, sized so the rare catastrophic mistake is impossible and everything else is fast.

Uses account boundaries to contain blast radius Expresses controls as policy-as-code guardrails rather than manual gates Combines preventive and detective controls deliberately

Then they probe: A team needs an exception to a guardrail to ship an important feature. How does that work without you becoming the bottleneck?

Practise this one
Principal

Design a global rate limiter that protects your APIs across many regions, enforcing per-customer quotas. Walk me through the design and, more importantly, the trade-offs you would make and how you would sequence building it.

What most people say

I would put a counter in a central distributed cache keyed by customer, increment it on every request, and reject when it exceeds the quota. That gives a single accurate global count that every region shares.

Technically workable but it makes one strict choice and ignores the trade-offs. A synchronous central counter adds cross-region latency to every call and becomes a single dependency whose outage can break all APIs. No mention of fail-open, approximation, or sequencing.

The structure behind a strong answer

  1. 1

    Clarify the requirement behind the requirement. Pin down whether the goal is hard billing enforcement or abuse protection, because that decides how strict and consistent the limiter must be.

  2. 2

    Name the core trade-off. State the tension up front: a strictly accurate global count needs coordination that adds latency and a shared dependency, while local counting is fast but can overshoot.

  3. 3

    Pick a pragmatic point on the spectrum. For most APIs, choose approximate limiting with local token buckets per region and periodic async reconciliation, accepting small overshoot for speed and availability.

  4. 4

    Design for failure and overload. Decide fail-open versus fail-closed per use case, since failing closed on rate limiting can take down the very API it protects.

  5. 5

    Sequence the rollout. Ship a single-region limiter first, then per-region local limits, then cross-region reconciliation, delivering protection early and adding global accuracy only where it pays off.

What gets you hired

First I ask what the limit is really for, because hard billing enforcement and abuse protection need very different strictness. Then I name the trade off out loud. A strictly accurate global count means cross region coordination on every request, and that is 80 to 100 milliseconds added to a call that should take 20, plus a shared dependency that can fail. Counting locally is fast and resilient but can overshoot the quota. For most APIs I deliberately land on approximate limiting, a local token bucket per region on the fast path, adding well under a millisecond, with regions reconciling counts asynchronously every second or so. If I run 4 regions, a customer on a 1,000 request per minute quota might briefly see a few percent overshoot, and I would rather accept that bounded error than add latency to every request and fall over during a partition. I design for failure explicitly. On the abuse protection path I fail open, because a limiter that fails closed can take down the very API it protects. For strict billing I might fail closed and accept the availability hit. And I sequence it, ship a single region limiter in the first sprint so we have protection in production, then per region local limits, then cross region reconciliation only for the customers and quotas where global accuracy genuinely matters. I am choosing a point on the accuracy versus latency spectrum on purpose, and shipping value incrementally rather than building the most precise system on day one.

Surfaces the accuracy-versus-latency trade-off explicitly and picks a point on purpose Prefers approximate local limiting with async reconciliation for resilience Reasons about fail-open versus fail-closed per use case

Then they probe: A whale customer games the approximate limiter by spreading traffic across regions to exceed their quota. How do you respond?

Practise this one

Troubleshooting

25 questions · Junior, Mid, Senior
Junior

You get paged at 2am. Checkout latency has gone from 200ms to 4 seconds and it started right after the 11pm deploy. Walk me through what you do.

What most people say

I would look at the logs and try to find the error, then I would fix the bug and redeploy.

It leaves the customer bleeding while you read logs, it has no rollback instinct, no blast radius check, and treats every incident as a single bug hunt rather than a stop the pain, then find the cause sequence.

The structure behind a strong answer

  1. 1

    Stabilise first. Roll back the 11pm deploy before you try to understand it, because users are paying for your curiosity right now.

  2. 2

    Confirm the blast radius. Check whether all checkout traffic is slow or only one region, one pod, one customer segment.

  3. 3

    Bisect what changed. Deploys, config flags, feature toggles, dependency versions, infra changes, and traffic shape in the last few hours.

  4. 4

    One hypothesis at a time. State the guess out loud, name the signal that would prove or kill it, then look.

  5. 5

    Verify and write it up. Watch p95 return to baseline for a full traffic cycle, then write a short timeline.

What gets you hired

First move is stabilise, not diagnose. The change window is obvious, the 11pm deploy, so I roll it back immediately. On our setup that is one command against the deployment pipeline and it takes about 3 minutes. I do not need to understand the bug to know that 200ms was healthy and 4 seconds is not. While the rollback runs, I check blast radius on the dashboard: is p95 up for all checkout traffic or one region, is error rate up too or is it pure latency, and is the request rate normal or did we get a traffic spike that just happens to coincide. Say p95 is 4 seconds across every region and requests per second are flat at about 900, that points at us, not at load. Once the rollback lands I watch p95 for a full 10 minutes. If it drops back to roughly 200ms, the incident is over and the hunt begins calmly. Now I diff the deploy. I look at the pull requests in it, the config changes, and the one signal that usually cracks this: database call count per request. If checkout went from 4 queries to 60 queries, that is an N plus one introduced by an innocent looking loop. I confirm it in a trace, not in my head. Then I write a 10 line timeline with the graph, so the next person on call does not start from zero.

Rolls back before diagnosing, and says why in one sentence Checks blast radius with a specific signal, not a vibe Names a concrete metric like p95 or queries per request

Then they probe: What if the rollback does not fix it?

Practise this one
Junior

It is 3am and you get paged: the primary application server is at 100% disk usage and the app is throwing write errors. You are the only one online. Walk me through exactly what you do.

What most people say

I would SSH in and delete the biggest files I can find to free up space, then go back to sleep.

It jumps straight to a destructive action with no idea what the files are, and it never verifies the service actually recovered or explains why the disk filled.

The structure behind a strong answer

  1. 1

    Confirm the alert. Run df -h to confirm which filesystem is actually full, not just the alert text

  2. 2

    Buy safe headroom. Free space from things that are provably safe first, such as truncating rotated logs

  3. 3

    Find the big consumers. Use du and lsof to rank the largest directories and any deleted-but-open files

  4. 4

    Verify recovery. Re-check writes, error rate and the app health endpoint before you stand down

  5. 5

    Escalate honestly. If the growth is unexplained or accelerating, wake someone rather than guessing alone

  6. 6

    Write it up. Record the cause, the commands you ran and the permanent fix for the morning

What gets you hired

First I confirm rather than trust the alert. I SSH in and run df -h, which tells me it is /var at 100% and not the root volume, so the blast radius is logs and not the data directory. Stabilising comes before root cause. My safest lever is rotated logs, so I truncate anything already gzipped in /var/log and I check lsof +L1 for deleted files still held open by a process, which is the classic trap where du shows 20GB free but df still says full. If I find one, restarting that one service releases the handle. That usually buys me 10 to 15 percent headroom in under 5 minutes, which stops the write errors. Then I hunt. du -xh /var --max-depth=2 | sort -h ranks the consumers, and if it is one application log growing gigabytes an hour I know something is looping and logging an exception. At that point I confirm the app is writing again, check the error rate on the dashboard has dropped back to baseline, and only then decide whether to page the service owner. If the disk refills within the hour, I do not keep deleting, I escalate. In the morning I raise the real fixes: logrotate with a size trigger, an alert at 80 percent rather than 100, and a bigger volume if growth is legitimate.

Confirms with df before touching anything Knows the deleted-but-open file handle trap Verifies recovery on a dashboard, not just on the shell

Then they probe: You freed 20GB but df -h still shows the disk full. What now?

Practise this one
Junior

You push a new version of a service and within two minutes the pods are all showing CrashLoopBackOff. The team is watching you share your screen. Walk me through exactly what you do.

What most people say

I would restart the pod and see if it comes back healthy. If it keeps crashing I would delete the deployment and redeploy it.

Restarting is what Kubernetes is already doing for you on a loop, so the answer shows the candidate does not know what the state actually means, and deleting the deployment destroys the evidence and the service at the same time.

The structure behind a strong answer

  1. 1

    Say what the state means. CrashLoopBackOff means the container started, exited, and Kubernetes is now waiting before restarting it again.

  2. 2

    Stabilise before you investigate. If this is a fresh deploy hurting real users, roll back first with kubectl rollout undo, then debug the bad image calmly.

  3. 3

    Read the exit signal. kubectl describe pod, then look at Last State, the exit code and the reason field before guessing.

  4. 4

    Read the previous container logs. kubectl logs POD --previous, because the crashed container is gone and the live one has no history.

  5. 5

    Split app failure from platform failure. Exit 1 usually means the app threw, 137 means OOMKilled, and 143 means it received SIGTERM.

  6. 6

    Verify and write it up. Reproduce the fix in a staging namespace, redeploy, then record the cause in the ticket.

What gets you hired

First I would say out loud what CrashLoopBackOff actually means, because it calms everyone down. The container started, it exited, and Kubernetes is backing off before restarting it. That is a symptom, not a cause. Since this is a fresh deploy and users are on the other side of it, my first action is not debugging, it is stabilising. I run kubectl rollout undo on the deployment, which usually has the last good image serving traffic again inside about 60 seconds. Now I have time to think. I run kubectl get pods and look at the restart count, then kubectl describe pod and read the Last State block, specifically the exit code. Exit 1 means the app itself threw, 137 means it was OOMKilled, and 143 means it took a SIGTERM. Then I pull kubectl logs POD --previous, because the crashed container is already gone and the live one has no history. Nine times out of ten the cause is sitting right there, a missing environment variable, a config path that does not exist, or a connection refused to the database. Last time I hit this it was a 256Mi memory limit on a JVM that needed about 700Mi, so it was OOMKilled within 20 seconds of boot every single time. I fix it in a staging namespace, watch it stay Running for 5 minutes, then roll forward and write the cause into the ticket so the next person does not repeat my 20 minutes.

Defines CrashLoopBackOff as backoff between restarts, not as a crash type Rolls back to protect users before hunting the root cause Knows kubectl logs --previous exists and says why it matters

Then they probe: The logs are completely empty. Now what?

Practise this one
Junior

A developer pings you: the checkout service cannot reach the payments service, the calls just hang and time out. You have never worked on either service. Walk me through what you do.

What most people say

I would check the logs and see if there is an error, and then probably ask the senior engineer or the networking team to take a look.

It outsources the thinking immediately and shows no mental model of how a request actually travels between two services, so the interviewer cannot tell if you could make progress alone.

The structure behind a strong answer

  1. 1

    Ask impact first. Find out if real customers are failing right now, or if this is one developer on one machine.

  2. 2

    Stabilise before you investigate. If customers are hurting, get the last deploy rolled back or the feature flag turned off first.

  3. 3

    Walk the path outward. Check name resolution, then TCP reachability, then TLS, then the application response, in that order.

  4. 4

    Read the failure shape. A hang means packets are being dropped by a firewall, refused means nothing is listening.

  5. 5

    Confirm the allow rules. Check the security group or firewall on the destination side actually permits the source and the port.

  6. 6

    Write down what you found. Post the exact command, the output and the next step so nobody repeats your work.

What gets you hired

First I ask what changed and who is affected. If real checkouts are failing, my first move is not debugging, it is stabilising: I ask whether we can roll back the last deploy or flip the payments fallback flag, because customers should not wait for my investigation. Then I walk the path outward. Name resolution first. From a box inside the same network I run nslookup payments.internal and confirm the answer is a private address in our VPC range, not something public or stale. Then TCP. I run nc with a 5 second timeout against port 443. This is the most useful single signal, because a hang means packets are being silently dropped, almost always a security group, while connection refused in under 50 milliseconds means the route is fine and nothing is listening. Then I check the payments security group actually allows 443 from the checkout security group, not just from an old CIDR. Then I look at the payments service itself: error rate, CPU, and whether its connection pool is saturated, because a healthy but overloaded service also looks like a timeout. At each step I say out loud what I expect to see, so the moment reality differs I know I have found the layer. Then I post the exact command and output in the channel with my one hypothesis.

Asks about customer impact before touching a terminal Names a specific ordered path: DNS, then TCP, then TLS, then application Knows that a hang and a refusal mean different things

Then they probe: You run nc and it hangs. What is your single best hypothesis?

Practise this one
Junior

A developer messages you: 'My app suddenly gets Access Denied writing to the S3 bucket. Nothing changed.' It worked yesterday. Walk me through what you do.

What most people say

I would just give the role s3:* on the bucket to unblock the developer, then look at it properly later.

It treats a permissions bug as an obstacle rather than a signal, it hides the real cause, and the temporary wildcard almost always becomes permanent.

The structure behind a strong answer

  1. 1

    Reduce the noise. Ask for the exact error string, the timestamp, the bucket name and the role, because Access Denied has many causes and the message narrows them.

  2. 2

    Confirm who the caller actually is. Run aws sts get-caller-identity from the same environment so you debug the real principal, not the one you assumed.

  3. 3

    Check what changed, not what is broken. Search CloudTrail and the IaC git log for the last 24 hours on that role, bucket policy and KMS key.

  4. 4

    Walk the four gates in order. Identity policy, resource policy, permission boundary or SCP, then KMS key policy. Each one can deny alone.

  5. 5

    Fix narrowly and write it up. Restore or add only the missing action on the exact resource ARN, never a wildcard, and record the cause.

What gets you hired

First I ask for the raw error, the time and the role, because Access Denied is four different bugs wearing one message. I run aws sts get-caller-identity inside the failing task, and in one case that alone solved it: the app was running as the node role, not the pod role, because IRSA annotation was missing. Assuming it is the right principal, I look at CloudTrail for the last 24 hours filtered on that role and that bucket, since something almost always changed even when people say nothing did. Then I walk the four gates in order: identity policy, bucket policy, any SCP or permission boundary, and finally the KMS key policy, which is the one people forget. About half the Access Denied cases I have seen on an encrypted bucket are actually kms:GenerateDataKey missing, not s3:PutObject. I confirm the hypothesis with the IAM policy simulator before I touch anything, so I am not shotgunning changes into production. Then I add exactly the one action on the exact resource ARN, not a wildcard, roll it out through Terraform rather than the console so it does not get wiped on the next apply, and ask the developer to retry while I watch. Fix time is usually under 20 minutes. Finally I post a short note in the channel saying what the cause was, because the next person will hit it.

Confirms the actual calling identity before theorising Knows KMS is a separate gate from S3 Fixes through IaC and scoped to one action and one ARN

Then they probe: How would you tell an SCP denial apart from a missing identity permission?

Practise this one
Mid

Your load balancer shows 5xx errors jumping from 0.1 percent to 12 percent of requests over ten minutes. There was no deploy. What do you do?

What most people say

I would restart the service, that usually clears 5xx errors, and then see if it comes back.

Restarting is a coin flip that destroys the evidence you needed, it shows no model of where the error is generated, and it gives you nothing to say when the errors return twenty minutes later.

The structure behind a strong answer

  1. 1

    Stabilise the user path. Scale out targets or shed non critical traffic so healthy capacity absorbs what is left.

  2. 2

    Split the 5xx. Separate load balancer generated 5xx from target generated 5xx, because they point at completely different causes.

  3. 3

    Confirm the blast radius. Which route, which target group, which availability zone, which downstream dependency is in every failing request.

  4. 4

    Bisect what changed without a deploy. Config, secrets rotation, certificate expiry, a dependency deploy, a data change, or a traffic shift.

  5. 5

    One hypothesis, then verify. Test the single most likely cause against one signal, then confirm error rate returns to baseline.

What gets you hired

No deploy is useful information, it does not mean nothing changed. First I stabilise. I scale the target group out, say from 6 tasks to 12, because if this is capacity or a slow dependency queueing requests, more headroom buys me room to think, and it is cheap for an hour. Then I split the errors, because on an ALB the important question is HTTPCode_ELB_5XX_Count versus HTTPCode_Target_5XX_Count. If it is ELB 5xx with 502s, targets are being killed or are returning garbage. If it is target 5xx with 503s, my app is genuinely returning errors. Say it is target 500s. Now I check blast radius: is it one path like POST /checkout, one availability zone, one target group. If 12 percent lines up suspiciously with one of eight tasks being unhealthy, that is a bad host and I drain it. Next I look at what changed without a deploy: a secret rotation, a certificate that expired at midnight, a downstream service that deployed, or a database connection pool that is exhausted. My favourite signal here is the app's dependency latency panel. If the payments call went from 40ms to a 3 second timeout, my 500s are just me surfacing their outage, and I flip the payments feature into degraded mode instead of hunting my own code. Then I hold for 15 minutes to confirm the error rate is back under 0.5 percent, and I write the timeline.

Distinguishes load balancer 5xx from target 5xx Adds capacity as a containment move before root causing Considers non deploy changes like secret rotation or certificate expiry

Then they probe: The 5xx are all 502s. What does that tell you?

Practise this one
Mid

One of your containers keeps getting OOM killed. It restarts, serves traffic for about twenty minutes, then dies again. Talk me through how you handle it.

What most people say

I would just increase the memory limit, that fixes OOM kills.

It is the right first move said as if it were the last move. Raising the limit on a real leak only changes how long you wait for the same page, and the interviewer is listening for you to know that.

The structure behind a strong answer

  1. 1

    Stabilise the service. Raise the memory limit and add a replica so restarts stop dropping user requests.

  2. 2

    Confirm the shape. Is memory a flat plateau near the limit, or a straight line that always climbs.

  3. 3

    Confirm the blast radius. One pod, all pods, or only pods handling one specific endpoint or tenant.

  4. 4

    Bisect what changed. Compare memory profile against the previous image, and against traffic volume and payload size.

  5. 5

    Verify with a real number. Watch steady state memory over a full traffic cycle before you call it fixed.

What gets you hired

First I stabilise, and yes, that does mean raising the limit. If the container is capped at 512Mi and dies every 20 minutes, I bump it to 1Gi and add a second replica so a restart does not drop live requests. That is containment, not a fix, and I say so out loud so nobody thinks I am done. Now I read the memory graph, because its shape answers the question. If memory rises fast, plateaus at 480Mi and sits there, the limit was simply too tight for the real working set and the fix is a correct limit plus a request that matches. If memory climbs in a straight line from 120Mi to 512Mi over 20 minutes and never comes back down, even when traffic dips at night, that is a leak and no limit will save me. I check kubectl describe pod for reason OOMKilled and the exit code 137 so I am not imagining it, and I check restart count. Then I bisect: did the previous image plateau at 300Mi. If the leak started with the release two days ago, I have a suspect list of maybe 6 pull requests. For the actual cause I take a heap snapshot in the running pod, or turn on the runtime profiler for 5 minutes, and look for the collection that only grows: an unbounded in memory cache, a client that is created per request instead of once, or listeners that are never removed. I confirm the fix by watching memory sit flat at around 200Mi across a full 24 hour cycle, then I add an alert at 80 percent of the limit so next time we get warned, not killed.

Raises the limit as containment while naming it as containment Uses the shape of the memory curve to separate leak from undersizing Names the concrete evidence, OOMKilled and exit code 137

Then they probe: How would you tell a leak from a limit that is just too low, in one graph?

Practise this one
Mid

Your API starts returning 500s and the logs are full of 'too many connections' from the database. Traffic is normal. Walk me through what you do, in order.

What most people say

I would just increase max_connections on the database and restart the app so it clears the connections.

Raising the limit hides a leak and can push the database into memory pressure, and a blind restart destroys the evidence you need to find the actual cause.

The structure behind a strong answer

  1. 1

    Stabilise the user impact. Get traffic served again before you start any investigation of causes

  2. 2

    Confirm the blast radius. Check whether every service is affected or only one client of the database

  3. 3

    Bisect what changed. Compare the timeline of the errors against deploys, config changes and scaling events

  4. 4

    One hypothesis at a time. Test leaked connections, pool misconfiguration and long transactions separately, not all at once

  5. 5

    Verify and write it up. Watch active connections return to baseline, then document the pool math you chose

What gets you hired

My first move is stabilising, not diagnosing. On RDS I look at the DatabaseConnections metric in CloudWatch and see it flat at the ceiling, say 500 out of a max of 500, while traffic is unchanged. That shape tells me a leak, not load. If a deploy went out in the last 30 minutes I roll it back immediately, because a rollback is the fastest hypothesis test I have and it also fixes the customer. If there was no deploy, I do a rolling restart of the API tasks, which drops the held connections and buys me maybe 20 minutes of clean service. Then I hunt with the service back up. I query pg_stat_activity grouped by state and application_name. If I see 300 sessions sitting in idle in transaction, that is a code path opening a transaction and not committing, usually an exception path that skips the commit or a missing context manager. I check whether the pool size times the number of tasks exceeds max_connections, because 20 tasks with a pool of 25 needs 500 connections on its own. The fix is normally two things: correct pool math, and put PgBouncer or RDS Proxy in front so a burst of tasks cannot exhaust the server. Then I add an alert at 80 percent of max connections so we see it before customers do.

Rolls back or restarts to restore service before diagnosing Reads pg_stat_activity by state and spots idle in transaction Does the pool size times instance count arithmetic out loud

Then they probe: How do you size the pool?

Practise this one
Mid

A report page that always loaded in 300ms now takes 12 seconds. Nobody changed the query and nobody deployed. What is your first hour?

What most people say

The database is probably under load, so I would ask to scale it up to a bigger instance and see if that helps.

Scaling up is a guess that costs money and often changes nothing, and it shows no ability to read a plan or reason about why a plan flipped.

The structure behind a strong answer

  1. 1

    Scope the pain. Establish whether it is one query, one tenant, or the whole database slowing down

  2. 2

    Reduce impact cheaply. Cache, rate limit or degrade the report page so the site stays usable

  3. 3

    Get the actual plan. Run EXPLAIN ANALYZE and compare it against what the plan used to be

  4. 4

    Bisect the change. Look at data growth, stale statistics, a dropped index and lock contention in turn

  5. 5

    Verify with numbers. Re-measure p95 after the fix and confirm it returns to the old baseline

What gets you hired

Nothing changed in the code, so I assume something changed in the data or the plan. First I scope it. I check whether p95 is up across the whole database or only on this one endpoint. If it is one endpoint, the database is healthy and I can take my time. I still reduce impact: I put a 60 second cache in front of the report, which takes the page from 12 seconds to instant for most users and stops it hogging a connection. Then I get evidence. I run EXPLAIN ANALYZE on the exact query with real parameters. The classic finding is that the planner has flipped from an index scan to a sequential scan because the table crossed some size threshold or because statistics went stale after a bulk import, so a plan that touched 5000 rows now touches 4 million. I check pg_stat_user_tables for last_analyze and n_live_tup. If autovacuum has not run since a big load, a manual ANALYZE on the table often puts the plan straight back in under a minute. If it is not statistics, I check whether an index was dropped by a migration, and I check pg_locks for contention. I verify by re-running with timing and by watching p95 on the dashboard drop back to roughly 300ms. Then I write it up and add an alert on this endpoint at 1 second so we catch the next flip early.

Separates one slow query from a globally slow database Reaches for EXPLAIN ANALYZE and reads the plan Names stale statistics and autovacuum as a real cause

Then they probe: How would a plan change without the query changing?

Practise this one
Mid

It is Monday morning. Overnight the platform team migrated the container registry. Now about half the pods across three namespaces are stuck in ImagePullBackOff and new deploys will not start. You are on call. What do you do?

What most people say

I would tell the platform team their migration broke everything and wait for them to roll it back. It is not my service that is failing.

It reads junior because it treats the incident as someone else's ticket, it produces zero diagnostic information for whoever does fix it, and it leaves the service degraded while the candidate waits for a reply on Slack.

The structure behind a strong answer

  1. 1

    Size the blast radius before you touch anything. Count affected pods and namespaces, and check whether running pods are still serving traffic on cached images.

  2. 2

    Stabilise what is still healthy. Freeze deploys and scaling immediately, because a scale up or an eviction will kill a pod that cannot be replaced.

  3. 3

    Read the real error, not the state. kubectl describe pod and read the pull error verbatim, since unauthorized, not found and timeout are three different bugs.

  4. 4

    Bisect what changed. The registry moved last night, so test the exact image reference by hand from a node before blaming the app.

  5. 5

    Form one hypothesis at a time. Credentials, image tag path, or network reachability. Prove or kill one before moving to the next.

  6. 6

    Verify then write it up. Roll one deployment, confirm it pulls, then document the fix and the missing pre migration check.

What gets you hired

My first job is to protect what is still up. Pods that are already Running have their image cached on the node, so they keep serving. The danger is that a scale event or a node drain replaces one and it cannot come back. So within the first 2 minutes I freeze the deploy pipeline and pause the cluster autoscaler, and I tell the channel I have done it. That buys me room. Then I read the actual error, not the state name. kubectl describe pod on one of the failures and I read the pull message verbatim. Unauthorized means credentials, manifest unknown means the tag or path is wrong, and a timeout means networking. On EKS with ECR the usual answer is the first one. I check whether the service account or the node role still has ecr:GetAuthorizationToken and BatchGetImage on the new registry, and whether an imagePullSecret is pointing at the old account. To prove it, I run aws ecr get-login-password and pull the exact image reference by hand from a node. Last time I hit this, 40 of 62 workloads were pinned to the old account id in their image path, and the fix was a values override plus a rollout, done in about 15 minutes. I roll one non critical deployment first, confirm the pull succeeds, then batch the rest. Afterwards I write up the one line that was missing from the migration plan, a dry run pull from every namespace before cutover.

Realises running pods survive on cached images and protects them by freezing scaling Reads the literal pull error instead of reacting to the state name Tests the image pull by hand from a node to prove the hypothesis

Then they probe: How would you have caught this before the migration?

Practise this one
Mid

Support says roughly one in five requests to the customer portal is returning a 502. Nothing has been deployed for two days. The pods all look Running and Ready. Walk me through how you find it.

What most people say

A 502 is a backend problem, so I would restart the pods and see if the errors clear up. If they come back I would scale up the deployment.

It reads junior because a rolling restart hides the evidence without explaining anything, it will appear to work whenever the cause is a single sick replica, and it leaves the real cause, usually a timeout or keepalive mismatch, alive to come back next week.

The structure behind a strong answer

  1. 1

    Confirm the blast radius first. Is it one route, one ingress, one backend, or every service behind the same load balancer?

  2. 2

    Stabilise if the error rate is user visible. Scale the backend out or shift traffic away from a bad replica before you start bisecting.

  3. 3

    Read the 502 from the proxy side. Ingress controller logs name the upstream, the upstream status and the timing, so start there.

  4. 4

    Split proxy from pod. Port forward straight to a pod and hit it directly, which tells you if the app or the path is broken.

  5. 5

    Bisect what changed even if nobody deployed. Traffic volume, certificate renewal, node scaling and dependency latency all change without a deploy.

  6. 6

    Form one hypothesis and verify it. Prove the fix by watching the 502 rate fall, then write up the timeout mismatch or the bad replica.

What gets you hired

A 502 means the proxy reached the backend and the backend failed the exchange, so the fastest path is to read it from the proxy side, not the pod side. First, blast radius. I check whether the 502s are on one route or every route behind that ingress. If it is one in five requests and the deployment has 5 replicas, that pattern is loud, it usually means one sick replica is still passing its readiness probe. So I stabilise first. I scale the deployment from 5 to 8, which immediately drops the share of traffic going to the bad pod from 20 percent to about 12 percent, then I look at per pod error rate in the dashboard and delete the one replica that owns nearly all of them. Error rate should fall to near zero within a minute and users stop feeling it. Now I can hunt properly. I read the ingress controller logs, which name the upstream address, the upstream status and the upstream response time. If the bad pod is the story, I check its memory and its connection pool. If every pod shows occasional 502s instead, my hypothesis changes to a keepalive mismatch, the classic case being an idle timeout of 60 seconds on the app behind a load balancer idle timeout of 75 seconds, so the proxy reuses a connection the app just closed. I prove it by raising the app timeout above the proxy's, then watch the 502 rate for 15 minutes before I call it fixed.

Explains that a 502 is the proxy's report on the backend, not the backend's own error Uses the one in five ratio to infer a single bad replica out of five Scales out to reduce user pain before starting root cause work

Then they probe: Every pod shows 502s at the same low rate, not just one. What is your new hypothesis?

Practise this one
Mid

After a routine deploy, about a third of requests to your internal orders API start failing with connection refused. You run dig and the hostname is resolving to an IP address that was decommissioned last week. Walk me through it.

What most people say

DNS is cached, so I would just wait for the TTL to expire and it should fix itself in a while.

It treats a live customer-facing failure as something to sit out, shows no urgency, and misses that you can force the correct answer immediately instead of waiting.

The structure behind a strong answer

  1. 1

    Stop the bleeding first. Roll back the deploy or repoint traffic to the known good target before you diagnose.

  2. 2

    Confirm blast radius. Check whether one third of clients fail, or one third of resolvers, which is different.

  3. 3

    Ask where the answer came from. Query the authoritative record directly, then the resolver, then check the local cache.

  4. 4

    Bisect what changed. Compare the DNS record and TTL against the previous value in your infrastructure as code history.

  5. 5

    Fix, then shrink the blast radius. Lower the TTL, then wait it out, then re-raise it once the change has propagated.

  6. 6

    Write it up. Record why a stale record survived, and add a check so it cannot recur silently.

What gets you hired

Failing requests means I stabilise first. One third failing usually means one of three records behind a round robin is wrong, so my first action is to pull the bad answer out of rotation, or roll the deploy back if it touched the DNS record. That buys me back the error budget while I dig. Then I confirm what is actually being served. I query the authoritative zone directly with dig plus the nameserver, so I bypass every cache and see the truth. If the authoritative record is already correct, then the stale IP is living in a resolver cache or in a client that ignores TTL, and I check whether we set a 3600 second TTL on a record we then changed, which means clients keep the dead address for up to an hour. If the authoritative record itself is wrong, I look at the last commit to the DNS module and compare it to the previous value. The fix is to correct the record and drop the TTL to 60 seconds so I am not fighting an hour of cache on the next change. Then I verify from three different vantage points: a pod in the cluster, a laptop on the VPN, and a public resolver, because a fix that only works from my machine is not a fix. Finally I write up why the old IP survived the decommission, and add a pipeline check that fails a deploy if a DNS record points at an address with no running target.

Pulls the bad answer out of rotation before diagnosing Knows to query the authoritative nameserver to bypass caches Reasons explicitly about TTL as a recovery time cost

Then they probe: The authoritative record is correct but clients still fail. Explain that.

Practise this one
Mid

It is 2pm, peak traffic. Half the targets behind your ALB just flipped to unhealthy, and the surviving half are now taking double the load and starting to shed requests. Walk me through the next fifteen minutes.

What most people say

I would look at the CloudWatch dashboards and try to work out why the instances went unhealthy, then fix the root cause.

It goes hunting for a root cause while the remaining half of the fleet is being overloaded, which is exactly how a partial outage becomes a total one.

The structure behind a strong answer

  1. 1

    Protect the survivors. Scale out immediately so the healthy targets are not crushed by the redistributed load.

  2. 2

    Decide if it is real. Check whether the targets are genuinely dead or just failing a too strict health check.

  3. 3

    Roll back the suspect change. If a deploy or config change landed in the last hour, revert it before investigating further.

  4. 4

    Read the health check reason. The target group tells you timeout, connection refused, or a wrong status code, which each point elsewhere.

  5. 5

    One hypothesis at a time. Test the single most likely cause, prove or kill it, then move on.

  6. 6

    Write the timeline. Capture the first bad minute, the action taken, and the recovery, while it is fresh.

What gets you hired

In the first two minutes I do not care why. Half the fleet is gone and the other half is absorbing double the traffic, so my first action is to scale out the target group, say from 8 tasks to 16, and if there was a deploy in the last hour I roll it back straight away. Stabilise, then investigate. Then I look at the target group health check reason in the console, because it tells me which layer broke. Timeout means the target is alive but too slow or a security group is dropping the health check port. Connection refused means the process is not listening, usually a crashed container or a port mismatch after a deploy. A 503 from the app means the target is up but its own dependency is down. I also check whether the failures cluster in one availability zone, because if all the dead targets are in one AZ this is an AZ or subnet problem, not an app problem, and I would shift traffic away from that AZ. I then look at target response time on the ALB. If p99 was already climbing from 200 milliseconds to 3 seconds before the health checks started failing, the real cause is downstream saturation and the health check is a symptom, not the disease. Once traffic is stable I write the timeline: first bad minute, action taken, recovery, and the one hypothesis I proved.

Scales out or rolls back before diagnosing anything Uses the health check failure reason to pick the layer Checks whether failures cluster in one availability zone

Then they probe: The health check reason is Timeout, but you can curl the target from your own box. Explain that.

Practise this one
Mid

Your CI pipeline has been green for months. This morning every build fails at the deploy step with an authentication error against the cloud provider. Nobody touched the pipeline. Walk me through it.

What most people say

I would generate a new access key, paste it into the CI secrets, and the build goes green again. Problem solved.

It fixes today and guarantees the same outage in ninety days, and it shows no curiosity about why the credential died in the first place.

The structure behind a strong answer

  1. 1

    Confirm the blast radius. Is it every branch and every repo, or only one? That tells you credential level versus pipeline level.

  2. 2

    Read the actual error, not the summary. InvalidClientTokenId, ExpiredToken and AccessDenied mean three different things and point at three different fixes.

  3. 3

    Unblock with a known good path. Restore or re-mint the credential so the team ships, and time box that to under 30 minutes.

  4. 4

    Find what expired or rotated. Check the secret store, the key age, and any rotation job or security policy that ran overnight.

  5. 5

    Remove the class of bug. Move CI to short lived OIDC federation so there is no long lived key left to expire.

What gets you hired

First I check the blast radius, because that tells me where to look. If every repo fails and not just one, the credential is shared and the problem is at the identity layer. Then I read the exact error. ExpiredToken means a session ran out, InvalidClientTokenId means the key is gone or deactivated, AccessDenied means the key is valid but the permissions moved. That single string saves me an hour. My first move is to stabilise: I confirm with the security team that the key was not deactivated on purpose, and if it is safe I mint a fresh credential and unblock the team, target under 30 minutes, because 40 engineers cannot ship while this is red. Only then do I hunt the cause. In the last case I saw, the answer was in CloudTrail: an automated rotation policy had disabled any IAM access key older than 90 days, and our CI key was 94 days old. Nobody touched the pipeline, the pipeline was simply standing on a key with a fuse in it. The real fix is to stop storing long lived keys at all. I move CI to OIDC federation so the pipeline exchanges its build token for a 15 minute role session per run. That is roughly a day of work per repo, it deletes the whole class of outage, and it also removes a secret that could leak. Then I write the incident up in five lines so the next person does not rediscover it.

Reads the exact error code and reasons from it Unblocks the team first with a time box, then hunts cause Checks whether the key was killed intentionally before reviving it

Then they probe: How does OIDC federation actually stop this happening again?

Practise this one
Mid

Your deployment pipeline is green through dev and staging, and fails every time on the production stage. Same commit, same image. How do you debug that?

What most people say

Production is just different, so I would retry the pipeline a few times and if that fails I would deploy manually from my laptop to get it out.

Retrying is not a hypothesis, and hand deploying from a laptop destroys the audit trail and hides the very difference you were supposed to find.

The structure behind a strong answer

  1. 1

    Stop the bleeding. Freeze further deploys and confirm production traffic is healthy on the last good version before you investigate.

  2. 2

    Get the real failure signal. Pull the raw job logs and the target platform events, not just the red X in the pipeline UI.

  3. 3

    List every difference, then rank. Identity, network path, secrets, resource quotas, config values, data volume, approval gates. Production differs in all of them.

  4. 4

    Bisect the difference set. Test one candidate at a time from the production runner, cheapest and most likely first, and record each result.

  5. 5

    Prove it and close the gap. Fix the cause, then make staging resemble production on that dimension so it fails there first next time.

What gets you hired

First I stabilise. I freeze the pipeline so we are not stacking half applied deploys, and I confirm production is still healthy on the previous version, so this is a blocked release and not an outage. That changes how fast I have to move. Then I get the real signal. The pipeline UI just shows a red step, so I pull the raw job log and the platform events, for example kubectl describe on the failing pods or the ECS service events, because the pipeline usually just times out waiting for something that never went ready. Then I list the axes on which production differs: the deploy role, the network path to the registry and the API, the secret names, quotas, replica counts and any approval gate. Same commit and same image means the artefact is fine, so the difference has to be environmental, and that narrows it a lot. I test one axis at a time from the production runner. In the last case I hit, staging pulled the image over a public NAT and production had a private endpoint with a registry policy that did not include the new repository, so the pull timed out after 120 seconds and the rollout stalled at 0 of 6 pods ready. I confirmed it in 15 minutes with a single crictl pull from a prod node. The fix was one line of policy. The bigger fix was making staging use the same private endpoint, so the next time it fails in staging on a Tuesday afternoon instead of in production on a Friday night.

Confirms production is healthy before diving into the pipeline Reasons that identical artefact means the difference is environmental Tests one difference at a time from the production runner

Then they probe: How would you make staging trustworthy without paying for a full production clone?

Practise this one
Senior

Traffic has doubled, latency is climbing, and your autoscaler is sitting there at 4 replicas doing nothing. You are on the call with the business asking why. What do you do and what do you say?

What most people say

I would check the autoscaler configuration and see why the scaling policy is not triggering.

It is not wrong, it is just slow, and the interviewer heard you spend the outage reading YAML while the queue built up. A senior scales by hand first and investigates second, and they say something useful to the business while they do it.

The structure behind a strong answer

  1. 1

    Stabilise by hand. Manually set the replica count now, do not wait for the autoscaler to agree with you.

  2. 2

    Say the honest sentence. Tell the business what you have done, what you expect, and when you will update again.

  3. 3

    Walk the scaling chain. Metric, threshold, controller decision, then actual capacity, because it breaks at exactly one of those.

  4. 4

    Confirm the blocker. Check for a max replica cap, missing metrics, quota limits, or nodes with no room to schedule.

  5. 5

    Verify and remove the trap. Confirm latency recovers, then fix the ceiling or the metric so it cannot silently reoccur.

What gets you hired

Two threads, and I run them at the same time. Thread one is containment: I override the autoscaler and set the replica count by hand, 4 to 12, right now. It takes about 90 seconds for new pods to become ready. I do not need to know why the autoscaler is stuck to know that traffic doubled and 4 replicas are not enough. Thread two is the sentence I owe the business: we have manually added capacity, we expect latency to recover within about 5 minutes, and I will update at the top of the hour. That is it, no speculation on air. Then I walk the scaling chain in order, because it is broken at exactly one link. Is the metric arriving? Very often the answer is no, the metrics server or the custom metric adapter is down, so the controller sees unknown and refuses to act, which is correct behaviour and a horrible surprise. Is the threshold right? If it scales on CPU at 80 percent but the service is IO bound and sits at 30 percent CPU while its queue depth is 4000, the metric is simply the wrong one. Is the controller deciding but blocked? Check maxReplicas, which is my top suspect, because someone set it to 4 for a cost review in March. And is capacity actually available? If pods are Pending, this is not an autoscaler problem, it is a cluster autoscaler or instance quota problem, and I raise the quota. I verify by watching p95 fall back under 300ms, then I fix the ceiling and add an alert for desired replicas equal to max replicas, so the trap cannot spring quietly again.

Overrides the autoscaler manually within the first minute Runs containment and communication in parallel Walks the scaling chain in order instead of guessing

Then they probe: Pods are Pending. What now?

Practise this one
Senior

A team tells you their service is slow and asks for more CPU. Their dashboard shows CPU usage at only 40 percent. They are convinced the platform is broken. How do you run this?

What most people say

The CPU is only at 40 percent so it is not a CPU problem, it must be their code. I would tell them to profile it.

It is technically defensible and operationally useless. It ends the conversation, leaves the service slow, and misses the fact that a container can be throttled hard while its average utilisation looks calm and healthy.

The structure behind a strong answer

  1. 1

    Buy stability, cheaply. Give them the CPU headroom now, because an hour of extra CPU costs less than an argument.

  2. 2

    Separate the two numbers. Average CPU utilisation hides throttling, so pull the throttled periods counter instead.

  3. 3

    Confirm the blast radius. Is every replica throttled, or only the ones on one noisy node.

  4. 4

    Form one hypothesis, name the killing signal. State what number would prove the platform innocent or guilty, then go get it.

  5. 5

    Land the fix and teach it. Correct the limit, then show the team the graph so they can self serve next time.

What gets you hired

I start by giving them the headroom, because being right slowly is worse than being generous fast. I raise the CPU limit from 500m to 1500m on their staging deployment and then production, and I say clearly that this is a test, not a conclusion. Then I go get the number their dashboard is not showing them. Average CPU at 40 percent is a mean over a minute, and CFS throttling happens inside a 100ms window, so a container can burn its entire quota in the first 30ms and sit frozen for 70ms, sixty times a minute, and still average 40 percent. The signal I want is container_cpu_cfs_throttled_periods_total over container_cpu_cfs_periods_total. If that ratio is 45 percent, they are being throttled almost half the time and the platform is not broken, the limit is. That also explains a p99 that is ugly while p50 looks fine, which is the fingerprint of throttling. If raising the limit takes p99 from 2.5 seconds to 300ms, I have my answer in about 15 minutes. If throttling is near zero and the higher limit changes nothing, I have honestly disproven the platform hypothesis and I bring them a profile instead of an opinion, and we look at the two dependency calls eating 1.8 seconds. Either way I finish the same way: I put the throttling ratio on their dashboard next to CPU usage, and I write four lines explaining why the two disagree. That is the part that stops this ticket coming back next quarter from a different team.

Grants headroom immediately as a cheap experiment, not as a surrender Knows throttled periods is a different number from CPU utilisation Connects throttling to a bad p99 with a healthy p50

Then they probe: They ask why the limit exists at all. Should you just remove it?

Practise this one
Senior

Support says customers are updating their profile and then seeing the old value straight after. Replica lag on your read replica is at 40 seconds and climbing. Take me through it.

What most people say

Replication lag happens sometimes, so I would wait for it to catch up and tell support it is temporary.

It accepts a live correctness bug as normal, it offers no way to protect users during the lag, and it never asks what pushed the replica behind in the first place.

The structure behind a strong answer

  1. 1

    Protect correctness first. Route the affected reads back to the primary so users stop seeing stale data

  2. 2

    Confirm the blast radius. Check whether all replicas lag or one, and whether the primary itself is healthy

  3. 3

    Classify the lag. Decide if the replica is starved of resources, blocked, or fed too much write volume

  4. 4

    Bisect what changed. Line the lag graph up against deploys, batch jobs, backfills and schema migrations

  5. 5

    Verify and harden. Watch lag return under target, then fix the read-after-write pattern permanently

What gets you hired

The user-visible bug is a read-after-write problem, and I can fix that in minutes without fixing replication at all. My first action is to flip the profile read path to the primary, or to a session-consistent read, using the feature flag we have for read routing. That stops customers seeing stale data even while lag is 40 seconds. That is the stabilise step. Then I diagnose. On RDS I look at ReplicaLag alongside WriteIOPS, CPU and network on both nodes. Three shapes are common. One, the primary is suddenly writing far more than usual, for example a backfill pushing 15,000 writes per second when the normal peak is 900, and the replica apply stream cannot keep up. Two, the replica is doing something expensive, like a long analytics query holding a snapshot and blocking apply. Three, the replica is undersized after we scaled the primary up. I check pg_stat_replication on the primary for write, flush and replay LSN offsets, which tells me whether the bytes are arriving late or arriving fine and applying slowly. If it is a backfill I throttle it to batches of 1000 rows with a pause, and lag typically drains back under 1 second within 10 minutes. Longer term I want lag alerting at 5 seconds, a hard rule that any read within 2 seconds of a write goes to the primary, and backfills that respect a lag budget and pause themselves.

Fixes the customer-visible correctness issue before the lag itself Distinguishes shipping bytes from applying them, using LSN offsets Suspects a batch job or backfill as the write source

Then they probe: How do you give read-after-write consistency without sending all reads to the primary?

Practise this one
Senior

You need to restore production after a bad migration dropped a column, and the nightly backup fails to restore. You have a business asking when the data will be back. What do you do?

What most people say

I would keep retrying the restore until it works, and tell the business it will be back soon.

It tunnels on the one path that already failed, it gives a commitment with no evidence behind it, and it ignores every other recovery source that might hold the data.

The structure behind a strong answer

  1. 1

    Stop the bleeding. Freeze writes or put the app in read-only so the damage cannot spread further

  2. 2

    Enumerate every recovery source. List snapshots, PITR, replicas, exports, WAL archives and even application logs

  3. 3

    Time box each attempt. Give each path a fixed window, for example 30 minutes, then move on

  4. 4

    Communicate a range, not a promise. Tell the business what you know, what you are trying and when you will update again

  5. 5

    Verify the restore is real. Check row counts and a business invariant before you point traffic back at it

  6. 6

    Fix the class of problem. Make restore drills routine so a backup is never trusted untested again

What gets you hired

The backup failing is bad, but it is not the only source of truth, and my first job is to stop the situation getting worse. I put the app into read-only, which freezes the data at a known state and buys me room. Then I tell the business one honest sentence: the nightly backup is not restoring, I am working three recovery paths in parallel, and I will update them in 30 minutes with a range, not a promise. Then I enumerate. Point in time recovery from transaction logs is usually the strongest path, because I can restore to one minute before the migration ran, say 02:14, into a new instance rather than over the top of production. In parallel I check whether a read replica or a pre-migration snapshot still has the column, and whether any analytics export or data warehouse copy from last night holds the values I need. I time box each path to 30 minutes so I do not tunnel. Very often I do not need a full restore at all, I only need one dropped column, so I restore to a scratch instance and backfill 2 million rows from it into production, which is far less risky than swapping the whole database. Before I let traffic back in I verify row counts and one business invariant, for example that no active account has a null in that column. Afterwards the real fix is a monthly restore drill, because an untested backup is not a backup.

Freezes writes before attempting any recovery Lists multiple independent recovery sources, not just the backup Time boxes attempts and communicates a range honestly

Then they probe: Why restore into a new instance rather than over production?

Practise this one
Senior

It is 02:10. You are paged: three of your twenty worker nodes have flipped to NotReady, pods are being evicted onto the survivors, and now new pods are stuck Pending. Latency on the main API is climbing. Take me through your first thirty minutes.

What most people say

I would SSH into one of the NotReady nodes and check the kubelet logs to find out why it went down, then fix it and bring it back.

It is not wrong, it is just out of order, and order is the whole test here. While the candidate reads logs the cascade keeps eating nodes, so a thirty minute diagnosis turns a three node problem into a cluster wide outage that the diagnosis will not undo.

The structure behind a strong answer

  1. 1

    Name the loop out loud. Nodes leaving means pods pile onto fewer nodes, which pushes those nodes over, which is a cascade.

  2. 2

    Stabilise capacity first. Add nodes now, scale the node group up by a margin, and cordon anything looking unhealthy before it flips.

  3. 3

    Confirm the blast radius. Are the three nodes in one availability zone, one instance type, one AMI, or one deploy?

  4. 4

    Protect the critical path. Check PodDisruptionBudgets and priority classes so the API keeps replicas while batch work gets squeezed.

  5. 5

    Bisect the cause on a quarantined node. Keep one NotReady node cordoned and un terminated so you can read kubelet, disk and network state.

  6. 6

    Verify and write it up. Watch Ready node count, Pending pod count and API p99 recover, then write the postmortem action.

What gets you hired

The first thing I say in the channel is that this is a cascade, not three broken nodes, because pods evicted from those three are landing on the seventeen survivors and pushing them toward the same edge. So I stabilise capacity before I ask why. I raise the node group desired count, say from 20 to 28, which on EKS gives me new nodes joining in roughly 3 to 4 minutes, and I cordon any node whose memory pressure or disk pressure condition is already true so nothing new schedules onto a node that is about to flip. If Pending pods are the API itself, I check that the API deployment has a priority class above the batch workloads so the scheduler evicts the right things. That is the first 5 minutes and it is what stops the bleeding. Then blast radius. I check whether the three nodes share an availability zone, an instance type, or an AMI, because that one query usually names the cause. Three nodes in one AZ points at the network or the control plane path. Three nodes on the same new AMI points at the last node group upgrade. I keep exactly one NotReady node cordoned and alive as evidence, and I let the others terminate. On that one I check kubelet status, disk usage on /var/lib, and whether the kubelet lost its connection to the API server. Once Ready count is back to 20 or more and Pending is zero and API p99 is back under 300ms, I write it up.

Names the eviction cascade as a feedback loop within the first sentence Adds capacity and cordons at risk nodes before opening a single log file Groups the three nodes by zone, instance type or AMI to find the common cause

Then they probe: All three nodes are in the same availability zone. What does that tell you?

Practise this one
Senior

A rollout of your payments service has been stuck half done for eleven minutes. Four new pods are Ready, six old pods are still serving, and kubectl rollout status just hangs. Error rate is up slightly but not enough to page. The release manager wants to know if he should tell the business we are down. What do you say and what do you do?

What most people say

I would keep watching the rollout for a bit longer and hope the remaining pods come up. If they do not, I would look at the logs and try to work out what is wrong with the new version.

It reads junior because it makes no decision, and a half done rollout of a payments service is a decision, not a status. Hoping is not a strategy, and every extra minute the system runs two versions is a minute of risk that nobody has agreed to take.

The structure behind a strong answer

  1. 1

    Answer the human first. Tell the release manager we are degraded, not down, and give a time box for the decision.

  2. 2

    Decide forward or back inside a time box. Set a hard limit, for example 10 more minutes, then default to rollback rather than drift.

  3. 3

    Stabilise by returning to one version. kubectl rollout undo removes the split brain, because two versions running is its own risk for payments.

  4. 4

    Then find why the new pods stalled. describe the new ReplicaSet, read the events, and check readiness, quota, PDB and node capacity.

  5. 5

    Check whether the two versions are compatible. A migration or a schema change makes a half done rollout dangerous in a way a stuck deploy is not.

  6. 6

    Verify and write it up. Confirm ten pods on one version, error rate flat, then record the cause and the deadline.

What gets you hired

First I answer him in one sentence, we are degraded, not down, six of ten pods are serving the old version and payments are still completing, and I will have a go or no go answer in 10 minutes. That stops him guessing. Then I stabilise, because a payments service running two versions at once is a risk in itself. Unless I already know the new version is what fixes an active bug, my default at 11 minutes stuck is kubectl rollout undo, which returns all ten pods to one known good version in about 90 seconds. Being on one version is worth more than being on the new one. Then I find out why it stalled, calmly. I describe the new ReplicaSet and read its events, and I look at the four things that hold a rollout at exactly this shape. Readiness never going true on the new pods, which the logs and the probe endpoint will show. Insufficient quota or CPU so pods sit Pending. A PodDisruptionBudget with minAvailable set so high that maxUnavailable can never take an old pod down. Or progressDeadlineSeconds, default 600, which is about to mark this rollout failed anyway. In my experience the PDB case is the sneakiest, because everything looks healthy and nothing ever moves. Once I know which one it is, I fix it, redeploy with a canary of 1 pod, watch error rate and p99 for 15 minutes, and I write up why the rollout could stall silently instead of failing loudly.

Answers the non engineer in plain language before touching kubectl Treats running two versions of a payments service as a risk on its own Puts a time box on the decision instead of watching and hoping

Then they probe: The new version contains a database migration that has already run. Does that change your rollback?

Practise this one
Senior

At 03:10 every call from your mobile app starts failing with a TLS handshake error. The certificate on your public endpoint expired ten minutes ago and the renewal automation that has worked for two years did not fire. Walk me through the next hour, and then the next week.

What most people say

I would renew the certificate and then look into why the automation did not run, and remind the team to check certificates more often.

Renewing can take longer than you think under pressure, and a reminder is not a control, so the same silent failure recurs the moment the person who was reminded moves teams.

The structure behind a strong answer

  1. 1

    Declare and communicate. Call the incident, get a comms owner, and post a status page update within ten minutes.

  2. 2

    Restore service before understanding it. Install any valid certificate now, by hand if necessary, and issue a new one later.

  3. 3

    Verify from the outside. Confirm the chain from a client outside your network, not from the load balancer console.

  4. 4

    Only then ask why the automation failed. Check the renewal job logs, the validation record and the permissions it used.

  5. 5

    Close the whole class. Inventory every certificate, add expiry alerting at thirty days, and test the renewal path.

  6. 6

    Write a blameless postmortem. Silent automation is the real finding, so the action is monitoring the automation, not blaming a person.

What gets you hired

Every call failing is a full outage, so I declare an incident, hand comms to someone else, and put a status page update out within 10 minutes. My job is restoration, not understanding. The signal is unambiguous: the cert expired at 03:00 UTC and 100 percent of new handshakes are failing, so there is nothing subtle to diagnose. The fastest safe path to a valid certificate wins. If we already hold a valid wildcard cert in the certificate store for another endpoint on the same domain, I attach that to the listener, which is a 2 minute change and gets us back. If not, I issue a fresh certificate with DNS validation and keep the TTL on the validation record at 60 seconds so it lands in minutes, not hours. While it issues, I check whether we can shift mobile traffic to a secondary endpoint or CDN that still holds a valid chain. Then I verify from outside our network with openssl s_client against the public hostname, checking notAfter and the full chain, because a green console does not prove a client in Mumbai on a mobile network sees a valid chain. Only once the error rate is back under 1 percent do I ask why automation that ran fine for 2 years went silent. Usually it is an expired IAM permission, a deleted validation record, or a cron that failed quietly. The week after, I inventory every certificate we own, add expiry alerting at 30, 14 and 7 days, and run a game day that forces a renewal so we know the path works before we need it again.

Restores service with any valid certificate before diagnosing the automation Verifies the chain from outside the network, not from a console Alerts on the absence of success, not only on failure

Then they probe: Why did nobody notice for two years that the renewal job was fragile?

Practise this one
Senior

The network team applied a change last night. Since then roughly five percent of connections between your web subnet and your data subnet time out, the other ninety five percent are perfectly fine. It is intermittent and nobody can reproduce it on demand. Walk me through how you attack this.

What most people say

Five percent is quite low so I would add retries in the application and keep an eye on it to see if it gets worse.

Retries hide a real fault, the five percent is a signal that something is genuinely different about one path, and burying it means the next change makes it a hundred percent.

The structure behind a strong answer

  1. 1

    Stabilise what you can. Ask for the change to be reverted, or ring fence traffic away from the suspect path.

  2. 2

    Turn intermittent into deterministic. Find the axis: which node, which port, which direction, which availability zone the failures share.

  3. 3

    Trust the data, not the memory. Read NSG flow logs and Connection Monitor rather than asking what the change was meant to do.

  4. 4

    Check both ends and the effective rules. An NSG at subnet level and one at NIC level combine, so read the effective rules.

  5. 5

    One hypothesis at a time. State it, predict what you will see, run the test, and kill it fast if wrong.

  6. 6

    Write it up and add a guard. Add a synthetic probe on that path so the next drift is caught in minutes.

What gets you hired

First, 5 percent is not noise, it is a clue. It almost always means 1 thing out of 20 is different, so my goal is to turn an intermittent fault into a deterministic one. I start by asking for the change to be reverted, because we have a known good state from yesterday and I would rather debug in a lab than in production. If revert is blocked, I ring fence, pinning the web tier to the instances that are healthy while I dig. Then I hunt the axis. I pull NSG flow logs into Log Analytics and group the denied flows by source IP, destination port and direction. If the drops cluster on 2 out of 40 web VMs, that is a NIC level NSG that never picked up the new rule, which is common when the subnet NSG was updated but a NIC level NSG still holds an old one, and the effective security rules blade shows me the merged result in about a minute. If drops cluster by port, someone narrowed a range. If they cluster by direction, it is an asymmetric rule where outbound is allowed but the return path is denied. I confirm with Connection Monitor between the exact pair, which gives me the hop that drops the packet rather than a guess. Once I know, I fix the rule, verify the failure rate goes from 5 percent to 0 across a 15 minute window with at least 1000 probes, then add a synthetic probe on that subnet pair so the next drift is caught in minutes rather than by a customer.

Treats five percent as a clue rather than acceptable noise Actively converts an intermittent fault into a deterministic one Knows subnet and NIC level rules combine into effective rules

Then they probe: Flow logs show nothing denied, yet connections still time out. Where do you look next?

Practise this one
Senior

Finance pings you on a Monday: the cloud bill for the weekend is double the usual run rate, roughly 40,000 dollars ahead of forecast. Nobody knows why. What do you do?

What most people say

I would tell everyone to turn off anything they are not using and start a cost optimisation review across the whole estate.

A broad optimisation review is a quarter of work, not an incident response, and it will not find the one thing that bent the curve on Saturday.

The structure behind a strong answer

  1. 1

    Treat it as an incident, not an audit. Money is still leaving right now, so your first question is what is burning, not who caused it.

  2. 2

    Attribute before you speculate. Slice Cost Explorer by service, region, account and usage type at daily and hourly grain to find the delta.

  3. 3

    Stop the bleeding on the biggest line. Cap, throttle or terminate the specific runaway once you have an owner or a safe rollback.

  4. 4

    Bisect against the change log. Line up the hour the curve bent with deploys, feature flags, data backfills and traffic events.

  5. 5

    Make the same surprise impossible. Add anomaly detection, budget alarms and a hard guardrail on the specific service that spiked.

What gets you hired

I treat this as an incident, because money is still leaving the building. First, attribution before speculation. I open Cost Explorer at hourly granularity grouped by service, then region, then linked account, and I look for the exact hour the line bent. That is usually a five minute job and it collapses the search space, because a doubled bill is nearly always one service in one account, not a broad drift. Say it shows NAT gateway data processing going from 300 dollars a day to 9,000 dollars a day starting Saturday at 02:00. Now I stabilise. I do not go hunting for root cause with the meter running, I find the cheapest safe way to stop the burn: throttle the job, scale the fleet back, or roll back the deploy that lines up with 02:00 Saturday. Then I bisect against the change log, deploys, feature flags, backfills and traffic. In the case I am describing, a batch job had been changed to read from S3 over the NAT gateway instead of the gateway endpoint, so every gigabyte was being charged twice. The fix was a route table entry, roughly 20 minutes of work, and it took the run rate back down the same afternoon. Last, I make it impossible to be surprised again. I turn on cost anomaly detection at the service level with a threshold well under the damage this did, I add tag enforcement so every resource has an owner, and I write it up in a page. The lesson is not do not use NAT, the lesson is that we had no alarm between a normal day and a 40,000 dollar weekend.

Slices the bill by service, region and account before theorising Stops the burn before hunting root cause Correlates the exact hour the curve bent with the change log

Then they probe: The spike is in an account nobody claims. How do you find the owner?

Practise this one
Senior

You find a large cluster running in a production account. It has no tags, no owner, no ticket, and it is generating errors and cost. Do you delete it? Walk me through your thinking.

What most people say

It has no owner and it costs money, so I would delete it and if it turns out something needed it, someone will complain and we can rebuild it.

That is an irreversible action taken on missing information, and in production the thing that complains might be a customer or an auditor rather than a colleague.

The structure behind a strong answer

  1. 1

    Assume it matters until proven otherwise. Unowned does not mean unused, and deleting a load bearing resource is not recoverable.

  2. 2

    Establish evidence of use. Check network flow logs, request metrics, connections and dependency graphs over at least the last 30 days.

  3. 3

    Find the creator from the audit trail. Query CloudTrail or Activity Log for the creating principal, the date and the tooling used.

  4. 4

    Contain before you remove. Downscale, isolate or stop it and watch for 7 to 14 days, because stopping is reversible and deleting is not.

  5. 5

    Retire and prevent. Snapshot, delete, and then enforce tagging so an unowned resource cannot be created again.

What gets you hired

No, I do not delete it on day one. Unowned is not the same as unused, and delete is the one button I cannot undo. So I work in a specific order. First I establish whether anything is actually using it: VPC flow logs, load balancer request counts, database connection counts, and 30 days of metrics. If it is serving 4,000 requests an hour from a subnet I recognise, this is not orphaned, this is undocumented, and those are very different problems. Second, I find the creator from the audit trail. CloudTrail or Azure Activity Log will name the principal who created it, the date and whether it came from Terraform or the console, and that has given me a name in almost every case, even when the person has left, because their team is still there. Third, I contain rather than remove. I scale it down or stop it and leave a very loud tag saying scheduled for deletion, contact me, with a date. Stopping is reversible. Then I wait 14 days and watch for errors and for people. If nothing and nobody appears, I snapshot the storage, keep the snapshot for 30 days, and delete. The last step is the one that matters most: I add a tag policy so a resource cannot be created in a production account without an owner and a cost centre, because this cluster is not a one off, it is a symptom. Finding one orphan means there are more, and the fix is the guardrail, not the cleanup.

Distinguishes unowned from unused with real usage evidence Uses the audit trail to find the creating principal Prefers a reversible stop over an irreversible delete

Then they probe: The creator left the company two years ago. Now what?

Practise this one

Containers

1 question · Mid
Mid

A team wants to run containers on AWS. How do you decide between ECS, EKS, and Fargate?

What most people say

I would use EKS because Kubernetes is the industry standard and everyone uses it.

It picks on hype, not fit, and treats three different choices as one. It ignores that EKS carries real operational overhead, never mentions Fargate as a compute model, and gives no reason tied to the team skills or workload.

The structure behind a strong answer

  1. 1

    Separate the two decisions. ECS vs EKS is the orchestrator choice; Fargate vs EC2 is the compute model. You pick one from each axis, for example ECS on Fargate, or EKS on EC2.

  2. 2

    ECS as the simpler orchestrator. AWS-native, lower learning curve and operational overhead, tight integration with IAM, ALB, and CloudWatch. Good when the team wants to ship without becoming Kubernetes operators.

  3. 3

    EKS when you need Kubernetes. Managed Kubernetes: choose it for portability across clouds, the Kubernetes ecosystem (Helm, operators), or existing in-house k8s skills. The cost is real operational complexity.

  4. 4

    Fargate vs EC2 for compute. Fargate is serverless containers, no nodes to patch or scale, you pay per task. EC2 launch type gives more control and can be cheaper at steady high utilization, but you own the node lifecycle.

  5. 5

    Decide on team and workload. Default to ECS on Fargate for the lowest ops burden; move to EKS for portability or ecosystem needs, and to EC2 nodes when utilization is high and steady enough that owning nodes pays off.

What gets you hired

I separate the two decisions: ECS vs EKS is the orchestrator, Fargate vs EC2 is the compute model, and you pick one from each. ECS is the AWS-native, lower-overhead orchestrator with tight IAM, ALB, and CloudWatch integration, great when the team wants to ship without running Kubernetes. EKS is managed Kubernetes, worth it for cross-cloud portability, the k8s ecosystem like Helm and operators, or existing in-house skills, but it carries genuine operational complexity. On compute, Fargate is serverless, no nodes to patch or scale and you pay per task, while EC2 gives more control and can be cheaper at steady high utilization but you own the node lifecycle. My default is ECS on Fargate for the lowest ops burden, I move to EKS when portability or the ecosystem justifies the complexity, and to EC2 nodes when utilization is high and steady enough that owning them actually pays off.

Separates orchestrator from compute model Defaults to the lowest-overhead option that fits Justifies EKS with portability or ecosystem, not hype

Then they probe: The team chose Fargate and now the bill is higher than expected at steady high load. Why, and what would you change?

Practise this one

System Design

15 questions · Mid, Senior, Principal
Mid

Design a log and metrics ingestion pipeline for a platform with about 200 services. Engineers need to search logs and alert on metrics.

What most people say

I would send everything to Elasticsearch and build Kibana dashboards on top of it.

It names one product instead of sizing the load, and it treats metrics and logs as one problem, which is exactly how teams end up with a storage bill nobody can explain.

The structure behind a strong answer

  1. 1

    Pin the numbers. Ask events per second, average log line size, daily gigabytes, retention window, and expected growth.

  2. 2

    Split the workloads. Metrics are small, regular, and numeric. Logs are large, bursty, and text heavy.

  3. 3

    Sketch the happy path. Agent on the host, buffer, transport, processing, then a searchable store.

  4. 4

    Find the bottleneck. Usually the indexing tier and the cost of retaining hot searchable data.

  5. 5

    Design for failure. Buffer on disk at the agent so a downstream outage does not drop lines.

  6. 6

    State the trade-off. Name what you gave up, for example searchability of old logs, and when you revisit.

What gets you hired

First I would pin numbers. Say 200 services, roughly 50,000 log lines per second at peak, 500 bytes a line, so about 25 megabytes per second, call it 2 terabytes of raw logs a day, plus maybe 2 million metric data points a minute. Growth of 30 percent a year. Those numbers say logs and metrics are different problems, so I would build two paths. Metrics go from a Prometheus style agent into a time series store with 15 second resolution kept for 15 days, then rolled up to 5 minute resolution for 13 months, and alerting reads only the time series store. Logs go from a Fluent Bit agent with a local disk buffer into Kinesis Data Streams, a consumer that parses and drops known noisy lines, then hot storage in OpenSearch for 7 days, and everything lands in S3 in Parquet for cheap long term queries with Athena. The agent buffer is the key reliability piece. If OpenSearch is down for 30 minutes, lines queue on disk and replay instead of vanishing. What I deliberately gave up is instant full text search on anything older than 7 days. An Athena query over S3 takes tens of seconds, not one second. I accepted that because it cuts hot index cost by roughly 80 percent, and 90 percent of searches are in the last day. I would revisit if incident reviews showed us routinely digging into month old logs.

Sizes the load in gigabytes per day before naming a single service Separates metrics from logs and gives each a different retention Uses a local agent buffer as the anti data loss mechanism

Then they probe: Log volume triples overnight because someone turned on debug logging. What breaks first?

Practise this one
Mid

Design an image upload and processing service for a marketplace app. Users upload photos from their phone, and we need thumbnails and a moderation check before the listing goes live. Walk me through it.

What most people say

I would use S3 for the images, Lambda to resize them, and maybe Rekognition for moderation. It is a pretty standard serverless pipeline.

It is a list of service names with no numbers, no failure story, and no trade off, so the interviewer cannot tell whether you have ever run this in production or only read the diagram.

The structure behind a strong answer

  1. 1

    Pin the numbers. Ask uploads per day, peak per second, average image size, retention, and how fast a listing must go live.

  2. 2

    Sketch the happy path. Draw the shortest end to end flow from phone to stored original to processed derivatives.

  3. 3

    Find the bottleneck. Name the one component that saturates first under peak load and say why.

  4. 4

    Design for failure. Decide what happens on a processing crash, a poison image, and a duplicate upload event.

  5. 5

    Talk cost. Put a rough monthly figure on storage, egress, and compute so the number is not hand waved.

  6. 6

    State the trade off. Say what you deliberately gave up and the metric that would make you revisit it.

What gets you hired

First, numbers. Say 200,000 uploads a day, peak around 30 per second on weekend evenings, average 3 MB per photo, and the listing must be live in under 60 seconds. That is roughly 600 GB a day of originals, and moderation is the slow step, not resizing. So the phone never uploads through my API. The API issues a presigned S3 URL, the phone PUTs directly to the bucket, and the listing is created in a pending state. The S3 event lands on SQS, and a small consumer fleet on Fargate pulls from the queue, generates three thumbnail sizes, calls the moderation API, and flips the listing to live. The queue is the shock absorber, so a spike just grows queue depth instead of dropping uploads. For failure, processing is idempotent on the object key, and after three attempts the message goes to a dead letter queue with an alarm at depth greater than 100. Cost is dominated by storage and egress, so originals move to infrequent access at 30 days and I serve derivatives through CloudFront. The trade off I am making is latency for reliability. Because moderation is async, a seller sees pending for up to a minute instead of an instant result. If product tells me pending is hurting listing completion, I would add a fast synchronous path for the first thumbnail and keep moderation async.

Asks for uploads per second, image size, and time to live before naming any service Puts a queue between upload and processing and explains it as a shock absorber Names the moderation call as the slow step rather than treating all work as equal

Then they probe: What if a single 4 GB file is uploaded and blows up your worker memory?

Practise this one
Senior

Design an event-driven order processing system for an e-commerce site. Payment, inventory, and shipping are separate services.

What most people say

The order service publishes to a queue and payment, inventory, and shipping each subscribe and do their part.

It stops exactly where the hard part starts. It says nothing about what happens when payment succeeds and inventory fails, which is the entire reason this question gets asked.

The structure behind a strong answer

  1. 1

    Pin the numbers and the guarantees. Orders per second at peak, acceptable end to end latency, and what must never double charge.

  2. 2

    Sketch the happy path. Order accepted, event published, downstream consumers react, customer sees confirmation.

  3. 3

    Decide choreography or orchestration. Pick one and justify it against how many services must agree.

  4. 4

    Find the failure modes. Duplicate delivery, out of order events, a service down for an hour.

  5. 5

    Design for compensation. There is no distributed transaction, so define the undo for each step.

  6. 6

    State the trade-off. Name the consistency you gave up and the condition that would make you change.

What gets you hired

I would start with numbers. Say 300 orders per second at peak on sale days, ten times the normal 30, and the business rule is that a customer must never be charged twice and must never be shipped goods we do not have. Latency budget for the customer facing confirmation is under 500 milliseconds, so the checkout API only validates and writes the order as PENDING, then returns. Everything after that is asynchronous. I would use an orchestrated saga rather than pure choreography, because three services must agree and choreography makes the failure story impossible to trace. Concretely, the order service writes the order and an outbox row in one database transaction, a relay publishes to EventBridge or Kafka, and a Step Functions workflow drives reserve inventory, then authorize payment, then capture, then create shipment. Every consumer is idempotent on the order id, so a duplicate delivery is a no-op. Every step has a compensating action, so a failed payment releases the inventory reservation within seconds. Failures after three retries go to a dead letter queue with an alert, and the order sits in a FAILED state that support can see. What I deliberately gave up is strong consistency. For a few hundred milliseconds the inventory count is optimistic, so we can oversell in a genuine race. I accept that because overselling one unit in a thousand is a refund, while a synchronous two phase commit across three services would cut availability. I would revisit for high value or limited edition items and put a hard reservation with a strict lock on those.

Uses the transactional outbox instead of writing to the database and the broker separately Makes every consumer idempotent on a business key Defines a compensating action for each saga step

Then they probe: The payment provider times out. Did the charge happen or not?

Practise this one
Senior

Design an analytics pipeline that produces a daily report for the leadership team by 8am. The data comes from a production database and a stream of app events.

What most people say

I would run a nightly job that pulls the tables, joins them, and writes the numbers into a dashboard.

It has no deadline math, no story for late events, and no way to rerun a bad day, so the first time a source is slow the leadership report is silently wrong.

The structure behind a strong answer

  1. 1

    Pin the numbers and the SLA. Rows per day, source sizes, and the hard deadline the report must meet.

  2. 2

    Work backwards from the deadline. Budget each stage so the whole run fits with slack for one retry.

  3. 3

    Sketch the layers. Raw landing, cleaned and conformed, then aggregated marts the report reads.

  4. 4

    Design for late and bad data. Events arrive hours late, and a source schema will change without warning.

  5. 5

    Make reruns safe. Idempotent partition overwrites so yesterday can be recomputed without duplicating rows.

  6. 6

    State the trade-off. Say what freshness or cost you gave up and what would make you revisit.

What gets you hired

The number that drives this design is the deadline. The report is due at 8am, so I would set an internal target of 6am, which leaves two hours for exactly one full rerun. Suppose the production database has 40 million new rows a day and the event stream is 200 gigabytes a day. I would land raw data in ADLS Gen2 partitioned by date, using Azure Data Factory with change data capture from the operational database rather than a full table scan, and Event Hubs Capture writing events straight to Parquet. Then a Databricks or Synapse Spark job builds a bronze, silver, gold set of tables. Gold is the small aggregate the report reads, so the BI layer never scans the raw 200 gigabytes. Late data is the part people forget. Events can arrive up to 6 hours late, so each nightly run reprocesses a 3 day window and overwrites those partitions rather than appending, which makes reruns idempotent. I would add data quality checks between silver and gold, for example row counts within 20 percent of the trailing 7 day average, and fail the run loudly rather than publish a wrong number. What I deliberately gave up is freshness. Leadership sees yesterday, not the last 15 minutes, and I chose that because batch at this scale costs a fraction of a streaming aggregation and is far easier to correct. I would revisit the moment someone needs intraday decisions, and at that point I would add a streaming path for a handful of metrics rather than rebuild the whole pipeline.

Works backwards from the 8am deadline and leaves room for a rerun Handles late arriving events with a reprocessing window Makes the pipeline idempotent so a backfill is routine

Then they probe: The upstream database dump is 3 hours late. What does the 8am report show?

Practise this one
Senior

Design a system that must never lose a message. It ingests payment notifications from a partner and each one must be processed exactly once.

What most people say

I would use Kafka with exactly once semantics enabled, so messages cannot be lost or duplicated.

It hands the whole problem to a config flag. Kafka's exactly once applies within Kafka, not across a partner HTTP call and a database write, and an interviewer will push straight into that gap.

The structure behind a strong answer

  1. 1

    Pin the numbers and the definition. Messages per second, size, and what losing one actually costs the business.

  2. 2

    Trace every hop. Loss happens at handoffs, so list producer, broker, consumer, and the sink.

  3. 3

    Make each hop durable. Acknowledge only after the write is persisted, never before you have committed.

  4. 4

    Accept at least once. Exactly once end to end is a myth, so add idempotency at the sink instead.

  5. 5

    Design the poison path. A message that always fails needs a dead letter queue and a human review loop.

  6. 6

    State the trade-off. Durability costs latency and throughput, so say which one you sacrificed and why.

What gets you hired

First the numbers. Say the partner sends 500 payment notifications per second, each about 2 kilobytes, and losing one means a customer paid and we never credited them, so the cost of a single lost message is a support ticket and a refund. RPO is zero. RTO can be a few minutes because the partner retries. I would be honest up front that exactly once end to end does not exist. What I can build is at least once delivery plus idempotent processing, which looks like exactly once to the business. Concretely, the ingest endpoint writes the raw payload to a durable queue with replication across three availability zones, and only then returns 200 to the partner. If the write fails, I return 500 and let the partner retry, because a lost 200 is worse than a duplicate. The consumer reads, processes, and writes to the database with the partner's message id as a unique key, so a replay hits a constraint violation and is skipped. The consumer acknowledges the message only after the database commit, never before, so a crash mid processing means redelivery, not loss. Anything failing five times goes to a dead letter queue with an alert, and I would target zero messages sitting there for more than 15 minutes. What I deliberately sacrificed is ingest latency and throughput. Waiting for a three way replicated write before acknowledging adds maybe 10 to 20 milliseconds per message. That is a fair price for zero data loss on payments. I would revisit only if the partner needed sub 5 millisecond acknowledgement, and even then I would push back rather than trade away durability.

Says out loud that exactly once end to end is not achievable Acknowledges only after a durable commit, in both the producer and the consumer Uses a business key for idempotency at the sink

Then they probe: Your consumer crashes right after the database commit but before the ack. What happens?

Practise this one
Senior

We have about 50 product teams and every one of them has rolled their own pipeline. Design a CI/CD platform that all 50 can use. Take me through it.

What most people say

I would set up Jenkins, or maybe GitHub Actions, with a shared library, and then all the teams use it. I would add stages for build, test, and deploy, and use Docker for consistency.

It names a tool and a stage list but never mentions build volume, queue times, migration, or what happens to the team whose service does not fit the template. That reads like a single-repo pipeline scaled up by hope.

The structure behind a strong answer

  1. 1

    Get the numbers. Ask how many repos, builds per day, average build minutes, deploy frequency, and how many environments each team owns.

  2. 2

    Name the users. The users of a CI/CD platform are the 50 teams, so state what a good day looks like for them.

  3. 3

    Sketch the happy path. Commit to merge to build to artifact to environment promotion, drawn once as the golden path.

  4. 4

    Find the bottleneck. Usually runner capacity, queue wait time, or a shared approval step that serialises fifty teams.

  5. 5

    Design for failure and escape hatches. Say what happens when the golden path does not fit a team, and who approves the deviation.

  6. 6

    Cost and adoption. Talk runner minutes cost, and how you migrate teams without a big bang cutover.

  7. 7

    State the trade-off. Name the flexibility you removed on purpose and the trigger that would make you revisit.

What gets you hired

First I would pin numbers. Say 50 teams, roughly 400 repos, 2,000 builds a day, a median build of 9 minutes, and a target of under 60 seconds queue wait at peak. Those numbers decide the whole design. Architecturally I would build a golden path: a small set of versioned reusable workflow templates, say four, one per service archetype, plus a shared build image, an artifact registry with immutable digests, and one deployment mechanism, GitOps with a per-environment repo. Teams get a paved road by referencing a template at a pinned version, not by copying YAML. The bottleneck at 2,000 builds a day is runner capacity, so I would run autoscaling ephemeral runners with a warm pool sized to p95 peak, plus a remote build cache to pull that 9 minute build closer to 4. For failure, every template ships with automatic rollback on a failed health check and a documented escape hatch: a team can opt out, but they own their pipeline and file a one page exception. Migration is 5 pilot teams first, then 10 per month, never a big bang. The trade-off I am deliberately making is flexibility. Teams lose the freedom to hand craft pipelines, and a genuinely unusual workload will feel constrained. I would revisit if more than about 20 percent of teams are filing exceptions, because at that point the golden path is wrong, not the teams.

Asks for build volume and queue time before naming any tool Treats the 50 teams as customers and designs a paved road with an escape hatch Has a migration plan that starts with pilots instead of a mandate

Then they probe: How do you stop the platform team becoming the bottleneck for every change?

Practise this one
Senior

We are moving from one shared cloud account to a proper multi-account setup. Design the landing zone.

What most people say

I would create separate accounts for dev, test, and prod, use organisations, and apply service control policies. Then I would peer the VPCs together so everything can talk.

Three accounts is a starting point, not a landing zone, and full mesh peering quietly recreates the single blast radius they were trying to escape. It also ignores account provisioning, IP planning, identity, and who pays for what.

The structure behind a strong answer

  1. 1

    Get the numbers and the constraints. Ask team count, workload count, regulated data, existing spend, and how fast new accounts are requested.

  2. 2

    Decide the boundary rule. State explicitly what earns its own account or subscription: team, workload, environment, or data class.

  3. 3

    Sketch the happy path. Org root, organisational units, a baseline account factory, and shared services like networking and logging.

  4. 4

    Find the bottleneck. Usually account creation lead time, IP address planning, and cross account access sprawl.

  5. 5

    Design for failure and guardrails. Preventive policy at the organisation level, detective controls, and a break glass path that is audited.

  6. 6

    Cost and ownership. Every account has a tagged owner and a budget alarm, so cost lands on the team that spends it.

  7. 7

    State the trade-off. Name what the account sprawl costs you in operational overhead and when you would consolidate.

What gets you hired

I would start with the numbers: how many teams, say 40, how many workloads, say 120, expected growth of maybe 10 new accounts a quarter, current spend, and whether any workload is regulated. Those decide the boundary rule. My rule would be one account or subscription per workload per environment, so a team with three services in three environments gets nine, and that is fine because they are cheap and the blast radius is small. Structurally: an organisation root, organisational units for workloads, sandbox, security, and shared services, with an account factory in Terraform so a new account is provisioned in under 30 minutes with identity, logging, budget alarm, and network already wired. Networking is a hub and spoke, not a mesh, with a transit gateway or virtual WAN and a pre allocated IP plan, because IP overlap is the mistake you cannot undo cheaply. Identity is single sign on with permission sets, no long lived keys. Guardrails are preventive at the organisation level, for example deny regions we do not use and deny disabling logging, plus detective controls streaming to a central security account. What I am sacrificing is simplicity. Hundreds of accounts means real operational overhead, and a developer who wants to see two services at once now has to switch context. I would revisit the granularity if account count passes roughly 300 and the platform team is spending more than about a day a week on account plumbing.

States the account boundary rule out loud and justifies it Plans IP addressing before drawing any network Automates account creation and gives a provisioning time target

Then they probe: How do you keep a baseline consistent across 200 accounts that already exist?

Practise this one
Senior

Secrets are scattered across environment variables, config files, and a spreadsheet somewhere. Design secrets management for the whole organisation.

What most people say

I would put everything in a secrets manager like Vault or AWS Secrets Manager, and the applications read from it instead of environment variables. That way nothing is hardcoded.

It solves storage and stops there. It never mentions how the application authenticates to the store in the first place, how a leaked secret is revoked, what happens when the store is unavailable, or what to do about the secrets already sitting in git history.

The structure behind a strong answer

  1. 1

    Get the numbers and the inventory. Ask how many services, how many secrets each, which are static credentials versus certificates or tokens.

  2. 2

    Define the goal precisely. The goal is not storage, it is short lived credentials and a revocation that takes minutes.

  3. 3

    Sketch the happy path. Workload identity to a central store, secret fetched at runtime, never written to disk or logs.

  4. 4

    Find the bottleneck. Usually the store becoming a hard dependency on every cold start, and rotation breaking long lived connections.

  5. 5

    Design for failure. Cache with a bounded TTL, graceful degradation, and a rehearsed break glass with audited access.

  6. 6

    Migration and detection. Pre commit and CI secret scanning, plus a burn down list of the secrets already in git history.

  7. 7

    State the trade-off. Name the operational coupling you accepted and the condition that would make you change it.

What gets you hired

I would start with the inventory numbers: say 120 services, roughly 800 secrets, of which maybe 60 percent are static database passwords and API keys, and a target that any secret can be revoked and replaced within 15 minutes. The core design is workload identity, not a password to get a password. Every workload gets a platform issued identity, an IAM role or a managed identity or a Kubernetes service account bound to it, and exchanges that for a short lived credential from a central store. For databases I would push toward dynamic credentials with a 1 hour lease, so most of that 60 percent stops being a standing secret at all. Applications fetch at startup and refresh via a sidecar or SDK with a bounded cache TTL of a few minutes, so the store being briefly unavailable degrades gracefully instead of taking down every cold start. Everything is namespaced by team and environment with least privilege read policies and a full audit trail. On migration I would run secret scanning in pre commit and in CI on day one to stop the bleeding, then burn down the existing 800 in waves, highest blast radius first, treating anything found in git history as already compromised and rotating it. What I am giving up is independence: the secret store becomes a tier zero dependency, and if it is down, new instances cannot start. I accept that because the cache covers short outages, and I would revisit if store availability drops below about 99.9 percent.

Leads with workload identity rather than with a product name Treats rotation and revocation time as the real success metric Has a plan for secrets already in git history and CI logs

Then they probe: A credential leaked into a public repo an hour ago. Walk me through the next 30 minutes.

Practise this one
Senior

Design a URL shortener that we would actually run in production. Assume it is used inside marketing emails, so redirects have to be fast and links can never break.

What most people say

I would hash the long URL with MD5, take the first seven characters, store it in a database, and redirect. Maybe put Redis in front of it.

It skips the read to write ratio entirely, ignores hash collisions and hot keys, and never says what happens when the cache is cold or the database is down, which is exactly the moment links break.

The structure behind a strong answer

  1. 1

    Pin the numbers. Ask writes per day, reads per second at peak, read to write ratio, and link lifetime.

  2. 2

    Sketch the happy path. Trace one create call and one redirect call from the client to storage and back.

  3. 3

    Find the bottleneck. Decide whether you are write bound on key collisions or read bound on redirect latency.

  4. 4

    Design for failure. Say what a cache miss, a hot key, and a region outage each do to redirect success.

  5. 5

    Talk cost. Show that reads dominate the bill and where caching removes most of that cost.

  6. 6

    State the trade off. Name what you gave up, for example custom vanity slugs or strict analytics accuracy.

What gets you hired

Let me anchor on numbers. Say 500,000 new links a month, so about 0.2 writes per second, but 20,000 redirects per second at campaign send time. That is a five orders of magnitude read to write ratio, so this is a read system with a small write path bolted on, and my design should follow that. Writes go to DynamoDB with the short code as the partition key and a conditional put so two concurrent creates cannot claim the same code. I generate codes from a counter based scheme, base62 encoded, rather than hashing, because that gives me collision freedom by construction instead of by retry. Reads go through CloudFront to a tiny Lambda at edge or a small ECS service that reads from an in memory cache with DynamoDB behind it. Target is p99 under 50 milliseconds. Links are immutable once created, which lets me cache them with a long time to live and gives me a very high hit rate, realistically above 99 percent after warm up. For failure, the mapping table gets point in time recovery and a cross region replica, because a lost row is a dead link in a customer email forever, and my RPO here is effectively zero. Cost is dominated by request volume, not storage, since a hundred million links is only a few gigabytes. The trade off is that I gave up editable destinations. If marketing needs to repoint a live link, I would add a small versioned indirection and accept a shorter cache time to live.

Explicitly computes the read to write ratio and designs for reads Prefers counter based code generation over hashing and explains collisions Treats a lost mapping row as permanent customer facing damage with a near zero RPO

Then they probe: One link in a viral campaign is taking 40 percent of your traffic. What breaks?

Practise this one
Senior

We run a customer facing web app in a single Azure region. Leadership wants it to survive a regional outage. Take me through how you would make it multi region.

What most people say

I would deploy the same app to a second region, put Traffic Manager in front, and turn on geo replication on the database so it is active active.

It states a topology without ever asking for an RPO or an RTO, and it calls geo replication active active, which hides the fact that writes still have a single primary and failover is not automatic or free.

The structure behind a strong answer

  1. 1

    Pin the numbers. Get the RPO, the RTO, the cost ceiling, and the tolerable write latency before choosing a topology.

  2. 2

    Sketch the happy path. Show normal steady state traffic routing and where each tier lives in each region.

  3. 3

    Find the bottleneck. Identify the stateful layer, since stateless tiers are trivial to duplicate and data is not.

  4. 4

    Design for failure. Define the failover trigger, who pulls it, and how you fail back without losing writes.

  5. 5

    Talk cost. Compare warm standby against active active as a concrete multiple of today's monthly bill.

  6. 6

    State the trade off. Name the consistency or cost property you surrendered and the trigger to revisit it.

What gets you hired

I would start by asking two numbers, because they decide everything. What is the RPO and what is the RTO. If the answer is RPO 5 minutes and RTO 30 minutes, I do not need active active, and saying that out loud saves the company a lot of money. Assume that answer, plus 3,000 requests per second at peak and a 200 GB database. My design is warm standby. App tier runs in App Service in both regions behind Azure Front Door with health probes, so the stateless layer is duplicated and always healthy. The data tier is the real work. Azure SQL with active geo replication gives me an asynchronous secondary in the paired region with replication lag typically under 5 seconds, which clears a 5 minute RPO comfortably. Blob storage moves to RA GZRS. Failover is a documented, one command runbook, rehearsed quarterly, with a human deciding, because automatic database failover on a network blip is how you get split brain. Sessions and caches must be regionless, so I move any in memory session state to Redis or to a signed cookie first. Cost lands around 1.4 times today, not 2, because the standby app tier runs at a smaller SKU and scales out on failover. What I deliberately gave up is instant recovery and zero data loss. If the business later says a single lost order is unacceptable, I would revisit and move to synchronous writes or an active active partitioned design, and I would expect the bill and the write latency to roughly double.

Opens by asking for RPO and RTO and lets those numbers pick the topology Says plainly that the stateless tier is easy and the database is the hard part Mentions rehearsing failover rather than assuming the runbook works

Then they probe: Why not just go active active from day one?

Practise this one
Senior

Our sale goes live at midnight and last year the site fell over in the first four minutes. You have six weeks. How do you design for this year's spike?

What most people say

I would set up autoscaling and add more instances, and put a CDN in front so it can handle the traffic.

Autoscaling reacts in minutes and the outage happened in four, so this answer does not engage with the actual failure, and it never names which component broke or what gets sacrificed under load.

The structure behind a strong answer

  1. 1

    Pin the numbers. Get last year's peak requests per second, the shape of the curve, and where it actually broke.

  2. 2

    Sketch the happy path. Trace the single hot journey, usually home to product to cart to checkout, and ignore the rest.

  3. 3

    Find the bottleneck. Load test to the point of failure and name the first thing that saturates, not the thing you assume.

  4. 4

    Design for failure. Decide what degrades gracefully and what must never fail, then shed load in that order.

  5. 5

    Talk cost. Price the pre warmed capacity for the spike window against the revenue lost per minute of downtime.

  6. 6

    State the trade off. Say which feature you turned off on the day and when you would turn it back on.

What gets you hired

The first thing I want is last year's data. Say we peaked at 12,000 requests per second within 90 seconds of midnight, from a baseline of 400, and the database connection pool exhausted at about the four minute mark. That tells me two things. Autoscaling is useless here, because a 30 to 1 step in 90 seconds is faster than any scale out loop, so I pre warm. I provision for peak from 23:30 and scale back down at 01:00, and I load test to 1.5 times last year's peak so I know where the next wall is. Second, the bottleneck was the database, so I take reads off it. Product pages and inventory counts get cached at the CDN and in Redis with a 10 second time to live, which is stale enough to be honest and fresh enough to sell. Checkout is the path that must never fail, so I put a queue in front of it and add a virtual waiting room that admits users at a rate the checkout path is measured to sustain, roughly 800 orders per minute. Everything else degrades. Recommendations, reviews, and the personalised banner are behind feature flags I can turn off from a dashboard in seconds. The trade off is honest. On sale night I ship a colder, less personalised site and some users wait in a queue, and I accept that because a queued user still converts and a 500 does not. The week after, I turn the flags back on and put the real fix, connection pooling and read replicas, on the roadmap.

Asks for last year's peak and the exact component that failed Says explicitly that autoscaling cannot answer a step function and pre warms instead Protects checkout and degrades everything else behind flags

Then they probe: The waiting room itself gets hammered at midnight. Now what?

Practise this one
Principal

You have inherited four teams each running their own ingestion pipeline and their own warehouse. Leadership wants one data platform. How do you design it, and how do you get there?

What most people say

I would pick the best of the four platforms, standardise everyone onto it, and decommission the others within a quarter.

It is a mandate, not a design. It ignores that each pipeline encodes years of business logic, and quarter long big bang migrations across four teams almost never land intact.

The structure behind a strong answer

  1. 1

    Pin the numbers and the pain. Total spend, duplicated headcount, and which reports currently disagree with each other.

  2. 2

    Define the target contract. One ingestion standard and one storage format, not one team owning everything.

  3. 3

    Sketch the platform boundary. Central team owns pipes and governance, domain teams own their datasets.

  4. 4

    Design the migration path. Strangler approach, dual write, verify parity, then cut over one domain at a time.

  5. 5

    Design for failure and drift. Assume one team refuses, and make the platform the easier path anyway.

  6. 6

    State the trade-off. Name the autonomy or speed you gave up and when you would loosen it.

What gets you hired

The numbers first. Four teams, say 25 engineers total with roughly six full time equivalents spent maintaining duplicated pipelines, a combined warehouse spend of about 90,000 dollars a month, and three different revenue numbers in three dashboards, which is the pain leadership actually feels. My target is a single storage layer, object storage with an open table format like Iceberg, one ingestion standard, and a shared catalog, but critically not a single team owning all the data. The central platform team owns the pipes, the catalog, cost attribution and access control. Domain teams keep ownership of their datasets and their business logic, because that logic is where the value and the risk both live. Getting there is a strangler migration, not a rewrite. I would land the platform, migrate one willing team first as the proof, run dual write for 30 days, and compare row counts and key metrics daily until they match within a tolerance we agree up front. Then the next team, and so on over about three quarters. I would make the platform the cheaper and easier path by giving each domain a cost dashboard that shows their spend, because incentives move teams faster than mandates. What I deliberately gave up is short term velocity. During dual write, teams carry two systems and delivery slows for roughly a quarter. I accepted that because a big bang cutover risks a wrong revenue number in front of the board. I would revisit the domain ownership model if the central team became a bottleneck, measured by how long a domain waits for a new dataset to go live.

Quantifies the current cost and the duplicated effort before designing anything Separates platform ownership from data ownership Proposes an incremental strangler migration with a parity check

Then they probe: One team refuses to migrate. What do you do?

Practise this one
Principal

The business says it can tolerate losing at most 15 minutes of data. Design the disaster recovery plan.

What most people say

I would replicate everything to a second region and set up automated failover so we can switch over if the primary goes down. Backups would run nightly to be safe.

Nightly backups directly contradict a 15 minute RPO, which shows the candidate did not do the arithmetic. It also never asks for RTO, never mentions data corruption, and never says whether anyone has ever tested the failover.

The structure behind a strong answer

  1. 1

    Turn the sentence into numbers. Pin RPO at 15 minutes, then ask the harder question, what is the RTO.

  2. 2

    Scope what disaster means. Region loss, data corruption, and a bad deploy need different answers, so name which you are designing for.

  3. 3

    Classify the data. Not all systems need 15 minutes, so tier the services and price each tier separately.

  4. 4

    Sketch the recovery path. Replication, backup cadence, failover mechanism, and how DNS or traffic actually moves.

  5. 5

    Find the failure in the plan itself. Dependencies that do not fail over, secrets and identity in the primary region, stale runbooks.

  6. 6

    Prove it with tests. Schedule a real failover exercise with a date and a measured recovery time.

  7. 7

    State the trade-off and the cost. Name the money and complexity you accepted or refused, and the revisit trigger.

What gets you hired

The first thing I would do is separate the two numbers. RPO is 15 minutes, so replication lag must stay under that, but nobody has told me the RTO, and RTO is what drives the cost. If the business can be down for 4 hours, this is a warm standby. If they need 10 minutes, we are in active active and the bill roughly doubles. Assume warm standby with a 1 hour RTO. Concretely: the database replicates asynchronously to a second region with an alarm if lag exceeds 5 minutes, which gives me headroom against the 15 minute promise. Object storage replicates cross region. Infrastructure in the second region is defined in the same Terraform and kept running at about 10 percent capacity so it is warm, with autoscaling to full on failover. Traffic moves by DNS with a low TTL, 60 seconds. Critically, I would tier the services, because probably only 6 or 8 of our 40 services actually need a 15 minute RPO, and the rest can have a 24 hour one at a fraction of the cost. I would also design for corruption, not just region loss, so point in time restore with retained snapshots, since replication faithfully copies bad data. Then I would put a real game day on the calendar every quarter and measure actual recovery time, because an untested plan is fiction. What I sacrifice is that async replication means we can lose up to 15 minutes of writes, and warm standby means an hour of downtime. I would revisit if revenue per hour of downtime exceeds the cost of the second active region.

Immediately asks for RTO because RPO alone does not size the design Tiers services instead of giving everything the strictest target Designs for data corruption, not only region loss

Then they probe: Finance says the standby region is too expensive. What do you cut?

Practise this one
Principal

Cloud spend is up 60 percent year on year and security keeps finding the same misconfigurations. Design the org wide guardrail and cost governance model for 50 teams.

What most people say

I would set up a cloud security posture tool and a cost dashboard, then send reports to the teams so they can fix the issues and reduce their spend. We would also write a policy document.

Dashboards and policy documents change nothing unless something is enforced or someone is accountable. It gives no enforcement point, no ownership of spend, no exception process, and no measurable target, so the same findings will reappear next quarter.

The structure behind a strong answer

  1. 1

    Get the numbers and the top offenders. Ask total spend, growth rate, spend by team, and the five most repeated security findings.

  2. 2

    Pick the enforcement point. Decide what is blocked at commit, what at deploy, and what is only detected after the fact.

  3. 3

    Sketch the happy path. A compliant team should never notice the guardrails, so make the default path the cheap safe path.

  4. 4

    Find the bottleneck. Central review by a platform or security team does not scale past a few teams a week.

  5. 5

    Design for the exception. Every guardrail needs a documented override with an owner and an expiry date.

  6. 6

    Make cost land on the team. Attribute spend by tag or account so each team sees their own number weekly.

  7. 7

    State the trade-off. Name the autonomy you removed, and the signal that would tell you it went too far.

What gets you hired

First the numbers. Say spend is 4 million a year, up 60 percent while headcount grew 15 percent, and the top five findings are 80 percent of the security tickets. That tells me this is a defaults problem, not a discipline problem. The design is three enforcement layers. Preventive, in the organisation policy and in admission control, so the five repeat offenders become literally impossible, for example public object storage or an unencrypted volume simply cannot be created. Proactive, in the pipeline, so policy as code runs on the plan and fails the build with a message that says exactly how to fix it, and the golden module is the fastest way to pass. Detective, for everything left over, streaming to a central account with an owner and an SLA, 7 days for high severity. On cost, every account and resource carries a team tag enforced at creation, and each team gets a weekly spend number and a budget with an alarm at 80 percent. The cheapest thing we ever did was showing engineers their own number. Every guardrail has an exception path: a pull request against the policy repo, an owner, and a 90 day expiry, because a permanent exception is just a policy you have not admitted to. What I am sacrificing is team autonomy. Some teams will find the paved road slower for genuinely unusual work, and I accept that. I would revisit if exception volume rises above roughly 20 percent of deploys or if the guardrails start showing up as the top complaint in the developer survey.

Distinguishes preventive, proactive, and detective controls and chooses deliberately Puts cost in front of the team that spends it, weekly and by tag Every guardrail has an exception path with an owner and an expiry

Then they probe: A senior team lead says your policy is blocking a customer deadline. What do you do?

Practise this one
Principal

You are the principal engineer on a consumer platform with six product teams, all shipping into one customer facing checkout journey. Peak events keep taking us down and each team blames another. Design the technical and organisational answer.

What most people say

I would introduce a platform team, define standards, and make sure everyone follows best practices for resilience and observability.

It is a governance answer with no architecture in it, no numbers, no error budget, and no explanation of how a principal with no authority over six teams actually gets a single trade off adopted.

The structure behind a strong answer

  1. 1

    Pin the numbers. Establish one shared peak figure, one error budget, and a per journey latency objective everyone signs.

  2. 2

    Sketch the happy path. Map the single revenue journey across all six team boundaries and mark every synchronous hop.

  3. 3

    Find the bottleneck. Identify the coupling, which at this scale is usually a synchronous fan out no one owns end to end.

  4. 4

    Design for failure. Give every cross team call a timeout, a fallback, and a named owner of the degraded experience.

  5. 5

    Talk cost. Convert downtime minutes into revenue so the investment is argued in money, not in architecture.

  6. 6

    State the trade off. Name the autonomy or feature richness you traded for reliability and the review date for it.

What gets you hired

I would start by making one number true for everyone. Today each team has its own dashboard and its own idea of peak, so nobody is wrong. I set a single journey objective, checkout available at 99.95 percent with p99 under 800 milliseconds at 15,000 requests per second, and I instrument the journey end to end with one trace id so we can point at a component instead of at each other. That usually reveals the real problem, which is a synchronous fan out. In one system I would expect checkout to call six services inline, so the journey's availability is the product of six numbers, and 99.9 percent each gives you 99.4 percent overall no matter how good any single team is. So the architecture change is to collapse the critical path. Only inventory and payment stay synchronous. Loyalty, recommendations, fraud scoring, and notifications move to events, each with a 200 millisecond timeout and a defined fallback that the owning team writes down. Organisationally I attach that to an error budget. If checkout burns its budget, the teams on the critical path stop feature work until it is back, and I get that agreed with the product leads before the next peak, not during the incident. The trade off is real. I am taking autonomy away from four teams on how their code enters checkout, and I am shipping a slightly less personalised experience. I would revisit the synchronous set once each candidate service demonstrates three months inside its own budget.

Creates one shared number and one shared trace before proposing any architecture Explains availability multiplying down a synchronous chain with real arithmetic Uses an error budget to make the trade off enforceable across teams they do not manage

Then they probe: A team refuses to move off the synchronous path. How do you handle that?

Practise this one

Data

1 question · Senior
Senior

A new service needs a primary datastore. Walk me through how you choose between a relational SQL database and a NoSQL store.

What most people say

NoSQL because it scales better and SQL does not scale.

It treats "scales" as a property of the label rather than the access pattern, and ignores that modern relational databases scale far past most workloads. It shows no analysis of queries, joins, or transactional needs, which are the things that actually decide the choice.

The structure behind a strong answer

  1. 1

    Start from the data and access patterns. Ask: is the data relational with many cross-entity queries, or accessed by a known key? What are the read/write patterns, the query shapes, and the scale? The queries drive the choice, not the label.

  2. 2

    Weigh the relational strengths. SQL gives flexible ad-hoc queries and joins, ACID transactions across rows, and a strong schema. It is the safe default when relationships are rich or requirements are still moving.

  3. 3

    Weigh the NoSQL strengths. Key-value/document/wide-column stores give horizontal scale and predictable low latency when you design the schema around fixed access patterns. The cost is limited ad-hoc querying and usually eventual consistency.

  4. 4

    Pressure-test consistency and scale. Do you need multi-row transactional integrity (money, inventory)? That favors SQL. Do you need to scale writes beyond one node with simple key access? That favors NoSQL. Be honest about whether you are actually at that scale.

  5. 5

    State the trade-off and the escape hatch. Name what you give up either way (joins/transactions vs horizontal scale/flexibility), and remember polyglot persistence: you can use both for different parts of the system.

What gets you hired

It depends, and it depends on the access patterns, so I start there. If the data is richly relational with lots of ad-hoc cross-entity queries, or the requirements are still moving, I lean relational, because SQL gives me joins, flexible querying, and ACID transactions across rows, which is exactly what you want for money or inventory where multi-row integrity matters. If instead access is by a known key at very high scale, say 50,000 reads a second on a fixed set of 3 or 4 query shapes known up front, a NoSQL store gives horizontal scale and predictable single digit millisecond latency, as long as I design the schema around those access patterns. The trade-off, stated plainly: NoSQL buys scale and schema flexibility but gives up easy joins and often strong consistency, an eventually consistent read may be a few hundred milliseconds stale. SQL buys query flexibility and transactions but is harder to scale writes horizontally. I also push back on premature scale. A well-indexed Postgres box with 2 or 3 read replicas comfortably handles thousands of transactions a second and most workloads never leave that, so I would not adopt NoSQL for scale I do not have. And it is not either or. Polyglot persistence is fine, relational for the core transactional data and a NoSQL store alongside it for the one high-volume key-access workload, like a session or event table taking most of the write traffic.

Chooses from access patterns, not the label Knows SQL gives joins and ACID, NoSQL gives scale and fixed-key speed Pushes back on premature scaling

Then they probe: Your NoSQL table is fast for the queries you designed, but the product wants a new query the key schema does not support. What now?

Practise this one

Strategy & Leadership

4 questions · Principal
Principal

You are asked to lead the migration of 200 services to the cloud. What is your strategy and how do you de-risk it?

What most people say

I would lift-and-shift everything to the cloud as fast as possible to get it done.

Lift-and-shift-everything ignores that services differ, skips the landing-zone foundation, has no risk strategy or rollback, and forgets the people. It optimizes for "done" over "succeeded", the opposite of principal judgment.

The structure behind a strong answer

  1. 1

    Assess and segment the portfolio. Inventory the 200 by dependencies, criticality, and effort. You do not migrate 200 the same way, segment them so the plan fits each class.

  2. 2

    Choose a migration pattern per service (the 6 R’s). Rehost (lift-and-shift), Replatform (tweak), Refactor (re-architect), Repurchase (SaaS), Retire (kill the dead ones), Retain (leave for now). Most orgs over-refactor, pick deliberately.

  3. 3

    Sequence to learn early and cheaply. Start with low-risk, low-dependency services to build the landing zone, CI/CD, and team muscle, then tackle the crown jewels once the path is proven. Land the foundation (networking, identity, guardrails) first.

  4. 4

    De-risk with reversibility and evidence. Migrate incrementally with the ability to roll back, run old and new in parallel, validate with real traffic (strangler pattern), and gate each wave on success criteria, not a calendar.

  5. 5

    Carry the org, not just the workloads. Upskill teams, set cloud guardrails/golden paths so 200 teams don’t each reinvent it, track cost and reliability from day one, and communicate progress and trade-offs to leadership.

What gets you hired

I wouldn’t treat 200 services as one project. First, inventory and segment them by dependency, criticality, and effort, then assign a migration pattern per service using the 6 R’s, retiring the dead, repurchasing where SaaS wins, rehosting the simple, and only refactoring where the business case justifies it, because most orgs over-refactor. I’d lay the foundation first, landing zone, identity, networking, guardrails and golden paths so 200 teams don’t each improvise, then sequence waves starting with low-risk services to prove the path and build team muscle before the crown jewels. Every wave is incremental and reversible, old and new running in parallel with real-traffic validation via the strangler pattern, and gated on success criteria, not a date. Throughout I’d upskill teams, watch cost and reliability from day one, and keep leadership honest about trade-offs and timelines.

Segments the portfolio Uses the 6 R’s deliberately (incl. retire/retain) Foundation + golden paths first

Then they probe: A critical legacy service is too risky and expensive to move. What do you do?

Practise this one
Principal

A few teams want an internal feature-flag and config service. You could build one on top of your cloud primitives or buy a SaaS. How do you make the build-vs-buy call for a platform capability the whole org will depend on?

What most people say

I would scope both options, estimate the engineering effort to build it, compare that to the annual license, and pick whichever is cheaper. Building gives us full control, so if it is close I would build it.

Optimizes for a one-time cost comparison and a "control" instinct. It ignores the multi-year carrying cost, opportunity cost of the team, and whether this is even worth differentiating on.

The structure behind a strong answer

  1. 1

    Is it differentiating?. Decide whether this capability is a competitive edge or undifferentiated heavy lifting. You build the former and buy the latter by default.

  2. 2

    Total cost of ownership, not sticker price. Compare the license to the fully loaded cost of building: on-call, upgrades, security patching, and the headcount you fund forever, not just the first quarter.

  3. 3

    Reversibility and exit cost. Favor the option you can walk back. Wrap any vendor behind an internal interface so swapping it later is a contained project, not a rewrite.

  4. 4

    Org readiness. Ask whether you have a team to own a built system for years. A platform with no long-term owner becomes shadow tech debt.

  5. 5

    Decide, document, and set a review date. Write the decision as an ADR with the assumptions, then revisit it when scale or pricing changes the math.

What gets you hired

I start by asking whether feature-flagging is differentiating for us. It almost never is, so my default is buy and spend the team's scarce time on the product. Then I compare total cost of ownership, not the sticker price: a built system means we fund on-call, upgrades, and security patching forever, which is usually several engineers indefinitely. I weigh reversibility by insisting we wrap whatever we pick behind an internal interface, so the choice is contained and we can swap vendors or move in-house later without a rewrite. I also check org readiness: if no team will own a built service for years, building it just creates shadow tech debt. I write the decision and its assumptions as an ADR with a review date, because at 10x scale the answer can flip and I want the next person to see why we chose what we did.

Defaults to buy for undifferentiated capability and reserves build for what is strategic Reasons in total cost of ownership and opportunity cost, not sticker price Designs for reversibility with an abstraction layer and an exit plan

Then they probe: The vendor doubles its price at renewal. What did you put in place a year ago to keep that from being a crisis?

Practise this one
Principal

You have 30 teams each spinning up cloud infrastructure their own way, with wildly different quality. How do you establish golden paths or paved roads so teams stop reinventing the wheel without grinding delivery to a halt?

What most people say

I would write a set of approved Terraform modules and a standards document, then require every team to use them and review their infra in a governance meeting before they ship.

Leads with mandate and gatekeeping. Forced standards without making the path easier breed resentment and shadow infrastructure, and a review gate just becomes a bottleneck.

The structure behind a strong answer

  1. 1

    Make the paved road the easy road. Adoption follows convenience. The golden path must be faster and safer than rolling your own, or teams will route around it.

  2. 2

    Pave the common case, allow escape hatches. Cover the 80 percent well and let teams go off-road with justification, so you standardize without blocking legitimate edge cases.

  3. 3

    Co-design with the loudest skeptics. Build the first version with a few respected teams so the path fits real work and gains internal champions instead of feeling imposed.

  4. 4

    Measure adoption and friction. Track how many teams are on the path and where they fall off, and treat low adoption as a product failure to fix, not non-compliance to punish.

  5. 5

    Invest in the platform team and migration. Fund the path as a product with an owner, docs, and hands-on migration help, because a paved road with no maintainer cracks within a year.

What gets you hired

My goal is that the standard way is also the path of least resistance. I treat the golden path as a product: a small set of well-documented modules and a starter template that provisions a compliant service in minutes, with logging, IAM, and cost tags baked in. I co-design the first version with a couple of respected, skeptical teams so it fits real work and earns champions rather than feeling imposed from a central tower. I pave the common 80 percent and leave deliberate escape hatches: teams can go off-road with a short justification, which keeps me from blocking legitimate edge cases. I measure adoption and where teams fall off, and I treat low adoption as my failure to make the path good enough, not as non-compliance to police. Crucially I fund a platform team to own it with migration help, because a paved road with no maintainer cracks fast. Standardization becomes the natural choice, not a tax.

Drives adoption by making the path the easiest option, not by mandate Co-designs with real teams to earn champions and fit Builds in escape hatches so standardization does not block edge cases

Then they probe: A senior team refuses the golden path and insists their bespoke setup is better. What do you do?

Practise this one
Principal

Cloud spend is growing faster than revenue and leadership wants it under control across dozens of teams. How do you set up org-wide cost governance, or FinOps, without turning into the team that says no to everything?

What most people say

I would set up cost monitoring and alerts, find the biggest spenders, buy reserved instances and savings plans for our steady-state usage, and set budget limits so teams get cut off when they overspend.

These are real tactics but it is a central-policing model. Committing to discounts before eliminating waste locks in inefficiency, and hard cutoffs treat cost as a constraint to enforce rather than a metric teams own.

The structure behind a strong answer

  1. 1

    Visibility and attribution first. You cannot govern what you cannot see, so get spend tagged and allocated to the teams that incur it before doing anything else.

  2. 2

    Push accountability to the teams. Give each team its own budget and dashboard so the people who can change the cost own it, instead of a central team chasing line items.

  3. 3

    Optimize in priority order. Eliminate waste, then rightsize, then commit to discounts, so you capture easy savings before locking in spend, and you never cut things that hurt reliability.

  4. 4

    Make cost a first-class engineering metric. Surface unit cost next to performance in reviews so teams reason about cost per request or per customer, framing efficiency as good engineering.

  5. 5

    Balance savings against velocity. Treat some spend as a worthwhile investment in speed or reliability, so FinOps optimizes value, not just the bill.

What gets you hired

I frame FinOps as making cost a shared engineering responsibility, not a central team that polices spend. First, visibility and attribution, spend tagged and allocated to the team that incurs it, and I do not start optimizing until at least 90 percent of the bill has an owner, because you cannot govern what you cannot see. Then I push accountability outward, each team gets its own budget, a dashboard, and an alert when they cross 80 percent of it, so the people who can actually change the cost are the ones who own it. I optimize in strict order, eliminate waste first, unattached volumes, idle dev environments running 168 hours a week for 40 hours of use, then rightsize, then commit to reserved instances or savings plans. Committing before cleaning up just locks in 3 years of inefficiency. And I never cut in ways that hurt reliability. I make cost a first class engineering metric by putting unit economics, cost per request or per customer, next to latency in reviews, which reframes efficiency as good engineering rather than a finance mandate. And I balance savings against velocity out loud, some spend is a smart investment in speed or reliability, and my job is to optimize value, not shrink the bill. Done that way I am helping teams make good trade offs, not being the team that says no.

Starts with visibility and cost attribution before any optimization Pushes budget ownership to the teams that incur spend Sequences waste, rightsize, then commit, in that order

Then they probe: A team is way over budget but their spend is driving the fastest-growing revenue line. What do you do?

Practise this one

Knowing the answer is not the same as recalling it under pressure

Sign in to save your board, send the ones you fumble to spaced recall so they come back right before you would forget them, and learn the concepts behind them with hands-on labs.