DevOps 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 87 questions inside
A real interview question
What is the difference between continuous integration, continuous delivery, and continuous deployment?
What most people say
drag me
“CI is when you build automatically and CD is when you deploy automatically.”
It collapses two distinct practices into one and completely misses continuous delivery, which is where most real companies actually live. It also shows no awareness of why a company would deliberately keep the manual gate.
Step 2 · Make the stories yours
The behavioural answers here are ours. The interview wants yours.
Five stories from your real work answer almost every behavioural phrasing. Build them once, with real numbers, and pressure-test them against this bank's follow-up ladders.
Step 3 · Know your rounds before the real interview
Every round of a real DevOps Engineer loop, what this bank covers, and where to prep the rest.
Step 3 · Know your rounds before the real interview
Every round of a real DevOps Engineer loop, what this bank covers, and where to prep the rest.
Recruiter screen
Covered hereCI/CD vocabulary, tooling experience, and what you actually owned.
Foundation and junior levels cover the screen. Be ready to name what you owned, not what your team owned.
Foundation questionsTechnical deep-dive
Covered herePipelines, Kubernetes, IaC, observability, security of the delivery chain.
87 questions and climbing. Supply-chain and least-privilege questions are where 2026 loops separate candidates.
This bank- 3
Live incident
Partly coveredA failing deploy or a down service, diagnosed live, the signature DevOps round.
19 troubleshooting scenarios teach the first-five-minutes discipline. The labs terminal builds the hands; narrate your reasoning out loud when you practise, that is what is being graded.
Hands-on labs - 4
Coding or scripting
Partly coveredPython or Bash written live: parse logs, hit an API, glue two systems.
Not in this bank. The Python track covers it; drill the file-and-JSON lessons especially, DevOps coding rounds are mostly text wrangling under a clock.
Graded Python track - 5
System design
Partly coveredDesign a delivery platform, an environment strategy, or a failover story.
The thinnest area of this bank, 5 prompts against a target of 12. Work them, then rehearse drawing the same designs on paper in 20 minutes.
5 design prompts - 6
Behavioural
Partly coveredYour own past work, probed for depth: conflict, failure, ownership.
The bank teaches the shape of a strong answer, but reciting our model stories as your own fails on the second follow-up. Use the story builder to put YOUR experience into that shape.
Build your stories
Browse all 87 devops engineer questions
The complete bank, grouped by topic, with the full rubric for every question. Free, no sign-up.
Browse all 87 devops engineer questions
The complete bank, grouped by topic, with the full rubric for every question. Free, no sign-up.
CI/CD
6 questions · Foundation, Junior, Mid, SeniorFoundationWhat is the difference between continuous integration, continuous delivery, and continuous deployment?
What most people say
“CI is when you build automatically and CD is when you deploy automatically.”
It collapses two distinct practices into one and completely misses continuous delivery, which is where most real companies actually live. It also shows no awareness of why a company would deliberately keep the manual gate.
The structure behind a strong answer
- 1
CI is about merging. Every developer merges to the main branch frequently, and every merge is automatically built and tested. The goal is finding integration breakage in minutes, not weeks.
- 2
Continuous delivery is about readiness. Every build that passes is automatically packaged and proven deployable. A human still chooses when to push the button.
- 3
Continuous deployment removes the button. Every passing build goes to production automatically, with no human gate at all.
- 4
Name what the last step really costs. Continuous deployment is only safe when your tests, monitoring, and rollback are genuinely trustworthy. It is an organisational decision about risk appetite, not just a pipeline setting.
What gets you hired
They are three steps on a ladder. Continuous integration means everyone merges to main often, and every merge triggers an automated build and test run, so integration bugs surface in 10 minutes instead of at the end of a 3 week branch. Continuous delivery adds the guarantee that every passing build is packaged and proven deployable, but a human still decides when to release. Continuous deployment removes that human gate entirely, so a merge that passes goes straight to production. Most teams should stop at continuous delivery until their test coverage, monitoring and rollback are genuinely trustworthy, because the last step converts every test gap into a production incident.
Then they probe: Your company wants continuous deployment. What has to be true first?
Practise this oneFoundationWhy should the same build artifact be promoted through environments instead of rebuilding for each one?
What most people say
“It is faster because you do not have to build the same thing three times.”
Speed is a side effect. The candidate misses the correctness argument entirely, which means they would happily approve a pipeline that rebuilds per environment and silently invalidates its own testing.
The structure behind a strong answer
- 1
State what a test actually proves. A green test suite proves that one specific artifact works, not that the source code works in general.
- 2
Show what rebuilding destroys. Rebuild for production and you ship a different artifact than the one you tested, so every earlier test result is now about something else.
- 3
Name the mechanism of difference. A dependency version resolves differently, a base image moved, a build tool updated. Nothing in your code changed and the binary still differs.
- 4
Describe the correct pattern. Build once, tag it immutably, and let configuration come from the environment at runtime.
What gets you hired
Because a passing test proves that one artifact works, not that the source works. If I build separately for production, I ship a binary that was never the one I tested, so all 3 earlier stages of testing were about a different thing. That is not theoretical: a transitive dependency resolves to a new patch version, or a base image tagged latest moved overnight, and now the production build differs with an identical commit hash. So the pattern is build once, tag it immutably with the commit sha, and promote that exact artifact through staging to production. Anything environment-specific comes from configuration injected at runtime, not baked in at build time. The speed saving is real but it is the bonus, not the reason.
Then they probe: What has to be true about your app for that to work?
Practise this oneJuniorDesign the pipeline for a typical web application. What stages would you have, and in what order?
What most people say
“Build, test, deploy.”
It is technically the right shape but shows no thinking about ordering, security scanning, artifact promotion, or where a human decision belongs. Every candidate says this, so it distinguishes nobody.
The structure behind a strong answer
- 1
Order by cost of feedback. Cheapest and fastest checks run first, so a broken build fails in a minute rather than after a 20 minute suite.
- 2
Lint and unit tests first. Static analysis and unit tests catch most mistakes in seconds and need no infrastructure.
- 3
Build the artifact once. Build and tag with the commit sha, then scan the image for vulnerabilities before it goes anywhere.
- 4
Integration and deploy to staging. Run tests that need real dependencies, then deploy the same artifact to a staging environment.
- 5
Promote with a gate. Production promotion of the identical artifact, with a manual approval or an automated canary depending on risk appetite.
What gets you hired
I order stages by how fast they give feedback. First lint and unit tests, because they run in under 2 minutes and catch most mistakes without needing infrastructure. Then build the artifact once, tagged with the commit sha, and scan the image for known vulnerabilities right there, so a bad base image never reaches a registry. Then integration tests against real dependencies, which are slower so they should not block the cheap feedback. Then deploy that exact artifact to staging and run smoke tests. Production is a promotion of the same artifact, not a rebuild, gated by either a human approval or an automated canary depending on how risky the service is. I would also make the pipeline fail loudly on flaky tests rather than retrying silently, because a retry that hides flakiness is how teams stop trusting their own suite.
Then they probe: Where would you put database migrations?
Practise this oneMidOur pipeline takes 45 minutes and developers have stopped waiting for it. How do you fix that?
What most people say
“Add more powerful runners so everything runs faster.”
It buys a linear improvement for a cost, without asking where the time goes. If 30 of the 45 minutes are a serial dependency chain or an uncached install, faster hardware barely helps and the bill rises permanently.
The structure behind a strong answer
- 1
Measure where the time actually goes. Break the 45 minutes down per stage before changing anything, because intuition about the bottleneck is usually wrong.
- 2
Parallelise independent work. Stages with no dependency between them should run concurrently rather than in a queue.
- 3
Cache aggressively and correctly. Dependency caches and layer caches, keyed on the lockfile so the cache is invalidated exactly when it should be.
- 4
Split by feedback need. Fast gates on every pull request, slow exhaustive suites post-merge or nightly, so the blocking path stays short.
- 5
Attack test time itself. Shard tests across runners, and find the slowest 1% of tests, which usually account for a large share of total runtime.
What gets you hired
First I measure per stage, because the bottleneck is rarely where people assume. Typically I find something like 12 minutes of dependency installation that should be cached, a serial chain of stages with no real dependency, and 3 slow integration tests dominating the suite. Then in order: cache dependencies keyed on the lockfile hash, parallelise the independent stages, and shard tests across runners. After that, split the pipeline by feedback need, so a pull request runs lint, unit and build in under 5 minutes and the exhaustive suite runs post-merge. Bigger runners are the last lever, not the first, because they cost money forever and do not fix a serial dependency chain. The target is under 10 minutes for the blocking path, because that is roughly the point where a developer waits rather than switching context and forgetting.
Then they probe: What is the risk of moving tests to post-merge?
Practise this oneMidA test fails about 1 in 5 runs. The team wants to add an automatic retry. What is your view?
What most people say
“Retries are fine, everyone does it, and it keeps the pipeline green.”
Green becomes meaningless. A test failing 1 in 5 times may be catching a real race that will appear in production, and a retry converts that signal into silence while the team learns that red does not mean broken.
The structure behind a strong answer
- 1
Name what a flaky test is. It is a defect, usually a race, a shared fixture, or a time or ordering dependency, not random noise.
- 2
Explain the cost of blanket retries. A retry can hide a genuine intermittent production bug, and it trains the team to ignore red builds.
- 3
Offer the workable middle. Quarantine the test so it stops blocking, but keep it running and visible with an owner and a deadline.
- 4
Fix the usual causes. Shared state between tests, real timing rather than fake clocks, and hard-coded waits instead of waiting for a condition.
- 5
Make flakiness measurable. Track flake rate per test so it is a number someone owns rather than a shared feeling.
What gets you hired
I would push back on a blanket retry, because a test failing 1 in 5 runs is a defect, and the two candidates are that the test is badly written or that the code has a real race. A retry cannot tell those apart, and if it is the second one the pipeline is now hiding a production bug. It also has a cultural cost: once retries are normal, people stop reading failures at all. What I would do instead is quarantine it immediately, so it runs and reports but does not block, with a named owner and a fix deadline of about 2 weeks, otherwise it is deleted. Quarantine gets the team unblocked today without pretending the problem is solved. Then fix the usual causes: shared state between tests, dependence on wall-clock time instead of a fake clock, and fixed sleeps instead of waiting for a condition. And I would track flake rate per test so it is a visible number, not a vibe.
Then they probe: Is a retry ever legitimate?
Practise this oneSeniorYou inherit 200 Jenkins jobs with no documentation and are asked to migrate to a modern CI system. How do you approach it?
What most people say
“Port all 200 jobs to the new system, then switch over on a chosen date.”
A big-bang migration of undocumented jobs is how these projects die: it takes months with no value delivered, ports dead jobs faithfully, and the cutover concentrates all risk into one day.
The structure behind a strong answer
- 1
Discover before porting. Find which jobs actually ran in the last 90 days, because a large share of 200 will be dead.
- 2
Classify by value and difficulty. Group into delete, simple port, and genuinely complex, then sequence by that.
- 3
Migrate incrementally with both live. Run old and new in parallel per service, compare outputs, then cut over one at a time.
- 4
Build the template first. Establish the shared pipeline pattern on one or two services so the other migrations become configuration.
- 5
Set a decommission date. Without a hard date the old system lives forever and you pay to run both indefinitely.
What gets you hired
I would not port 200 jobs, I would find out how many matter. First discovery: which jobs ran in the last 90 days, who owns them, what they produce. In my experience a set like this is often 40% dead, so that alone removes most of the work, and I would archive rather than delete so nothing is unrecoverable. Then classify the survivors: a large group of near-identical build-and-test jobs that become one template, and a small group of genuinely strange ones, which are usually the risky ones. I build the shared template on 2 services first and get it genuinely good, because every later migration is then configuration rather than engineering. Then migrate incrementally, running both systems in parallel per service and comparing outputs before cutting over, so risk is spread across weeks instead of concentrated in a cutover day. Critically I set a decommission date up front and treat it as real, because otherwise Jenkins runs for another 3 years and we pay for two systems and two mental models.
Then they probe: A job is undocumented and nobody claims it, but it runs nightly. What do you do?
Practise this oneContainers
2 questions · Foundation, JuniorFoundationWhat is a container, and how is it different from a virtual machine?
What most people say
“A container is a lightweight VM.”
It is the single most common wrong answer. It gives no mechanism, so the candidate cannot explain why containers start faster or where the security boundary is weaker, which is exactly what the follow-up will ask.
The structure behind a strong answer
- 1
Start with what is shared. A container shares the host kernel. A VM ships an entire guest operating system with its own kernel.
- 2
Explain the isolation mechanism. Containers are processes isolated by kernel features, namespaces for what they can see and cgroups for what they can use.
- 3
Derive the consequences. No guest OS means images in megabytes not gigabytes, startup in milliseconds not minutes, and many more workloads per host.
- 4
Name the trade-off honestly. Sharing a kernel means weaker isolation than a VM. For hostile multi-tenant workloads that boundary matters, which is why some platforms run containers inside lightweight VMs.
What gets you hired
A container is a process on the host, isolated by kernel features: namespaces control what it can see, like its own filesystem and network, and cgroups control what it can consume, like CPU and memory. The key difference is that it shares the host kernel, where a virtual machine ships a whole guest OS with its own kernel on top of a hypervisor. That is why a container image is often 50 to 200 MB and starts in under a second, while a VM image is gigabytes and takes a minute or more to boot. The honest trade-off is isolation strength: a shared kernel is a bigger blast radius than a hypervisor boundary, so for untrusted multi-tenant code people run containers inside lightweight VMs to get both.
Then they probe: So is a container a security boundary or not?
Practise this oneJuniorOur Docker image is 1.2 GB and takes 8 minutes to build. How would you make it smaller and faster?
What most people say
“Switch the base image to alpine.”
It is one real lever pulled in isolation, and often the least effective one. It does nothing about build time, and alpine brings its own problems with musl libc that can cost more debugging time than the megabytes saved.
The structure behind a strong answer
- 1
Understand the layer cache. Each instruction is a layer, and changing one invalidates every layer after it. Order matters enormously.
- 2
Copy dependency manifests before source. Install dependencies first so a source change does not re-run the install step on every build.
- 3
Use a multi-stage build. Compile in a fat builder stage, then copy only the runtime artifacts into a slim final image, leaving compilers and dev dependencies behind.
- 4
Shrink the base. Move to a slim or distroless base, and add a dockerignore so build context does not ship node_modules and git history.
What gets you hired
Two separate problems, and layer caching fixes both. For build time, the usual cause is copying the whole source before installing dependencies, so every one-line change invalidates the cache and reinstalls everything. Copying just the lock file and manifest first, installing, then copying source typically takes an 8 minute build to under 2 on a warm cache. For size, a multi-stage build: compile in a builder stage with the full toolchain, then copy only the runtime output into a slim final image, so compilers, dev dependencies and build caches never ship. That alone often takes 1.2 GB to around 150 MB. Then a dockerignore so the build context does not include node_modules and git history, and a slimmer base if the runtime allows it. I would measure after each change rather than doing all four blindly.
Then they probe: Why not always use alpine?
Practise this oneWays of working
2 questions · Foundation, SeniorFoundationWhat does DevOps actually mean, and what problem was it invented to solve?
What most people say
“DevOps is using tools like Jenkins, Docker, Kubernetes and Terraform to automate deployment.”
It answers "what tools do you know" instead of the question asked. A team can run all four tools and still throw releases over a wall every 6 weeks, which is exactly the failure DevOps was named for.
The structure behind a strong answer
- 1
Name the original problem. Developers were measured on shipping change and operations on keeping things stable, so the two teams had directly opposing incentives.
- 2
Describe the symptom. Work piled into big risky releases, handed over a wall with a runbook, and every incident became an argument about whose fault it was.
- 3
State the actual fix. Shared ownership of the outcome. The team that builds it runs it, which aligns the incentive to make it operable.
- 4
Put tools in their place. Automation, CI/CD and IaC exist to make small frequent changes safe. They are the consequence of the idea, not the idea.
What gets you hired
It was invented to fix an incentive problem, not a tooling one. Developers were rewarded for shipping change and operations for preventing it, so change queued up into big risky releases handed over a wall. A quarterly release of 300 changes is nearly impossible to debug when it breaks, because any of the 300 could be the cause. DevOps says the people who build a system also run it, which aligns everyone on the same outcome and makes small frequent changes the safe option. The tooling, CI/CD, infrastructure as code, observability, exists to make those small changes cheap and reversible. I would rather join a team that deploys twice a day with modest tools than one with a perfect toolchain and a monthly release train.
Then they probe: Is a dedicated DevOps team a contradiction then?
Practise this oneSeniorLeadership asks you to prove the platform investment is working. What do you measure?
What most people say
“Track how many deploys each engineer does and how many tickets they close.”
It measures individuals with output counts, which is gameable in an afternoon and actively harmful: it rewards splitting work into more deploys and punishes the person who spends a week on a hard reliability problem.
The structure behind a strong answer
- 1
Use the four that balance each other. Deployment frequency, lead time for change, change failure rate, and time to restore.
- 2
Explain why they resist gaming. Speed metrics are constrained by stability metrics, so you cannot improve one by wrecking the other.
- 3
Measure teams and systems, never individuals. Individual measurement drives local optimisation and destroys the collaboration that produces the outcome.
- 4
Add adoption and satisfaction. Whether teams voluntarily use the platform, and what they say about it, which catches a platform that is technically fine and unusable.
- 5
Translate to business language. Lead time is time to value, change failure rate is customer-visible risk. Leadership funds outcomes, not dashboards.
What gets you hired
The four DORA metrics, because they balance. Deployment frequency and lead time for change measure speed, change failure rate and time to restore measure stability, and the pairing is what makes them hard to game: you cannot look good by deploying recklessly, because failure rate catches it, and you cannot look good by freezing, because frequency catches that. I measure at team and system level and never per individual, since individual metrics drive people to optimise their own number at the expense of the team outcome. I would add two more: voluntary adoption, because a platform teams are forced onto is not proven, and a periodic developer survey, since a platform can be technically excellent and horrible to use. Then I translate for leadership: lead time is how fast an idea reaches a customer, change failure rate is how often customers feel our mistakes. If lead time drops from 6 days to 2, that is the argument, not a graph of pipeline runs.
Then they probe: A team games deployment frequency by splitting changes into tiny deploys. Is that bad?
Practise this oneInfrastructure as code
4 questions · Foundation, Junior, Mid, SeniorFoundationWhat is infrastructure as code, and why is it better than clicking in a console?
What most people say
“It automates infrastructure creation so you do not have to click through the console every time.”
It stops at speed, which is the least important benefit. Someone with a shell script also has automation. The reasons IaC wins are review, reproducibility and audit, and none of them appear here.
The structure behind a strong answer
- 1
Define it plainly. Your infrastructure is described in files that are the source of truth, and a tool makes reality match those files.
- 2
Reviewability. A change to production infrastructure becomes a pull request that another human reads before it happens.
- 3
Reproducibility. You can build an identical environment for staging, for disaster recovery, or for a new region, from the same code.
- 4
Auditability and recovery. Git history tells you who changed what and when, and reverting is a normal operation rather than an archaeology project.
- 5
Name the honest downside. It is slower for a genuine one-off, and drift from manual changes will silently break it if nobody enforces the discipline.
What gets you hired
It means the infrastructure is described in version-controlled files that are the source of truth, and a tool reconciles reality to them. Speed is the smallest benefit. The real ones are that a production change becomes a pull request a second person reviews before it happens, that I can stand up an identical staging environment or a second region from the same code in 20 minutes, and that git history answers who changed what and when during an incident. Rollback becomes a revert instead of trying to remember which checkbox someone ticked. The honest cost is that it is slower for a true one-off, and if people still make manual console changes you get drift, where the code and reality quietly disagree until a deploy overwrites something important.
Then they probe: How do you stop people making manual changes?
Practise this oneJuniorWhat is Terraform state, why does it exist, and what goes wrong if two people apply at the same time?
What most people say
“It is a file that keeps track of what Terraform created.”
Correct as far as it goes, but it stops before every consequence that matters: locking, remote backends, secrets in state, and what to do when it drifts. The follow-up will immediately expose the gap.
The structure behind a strong answer
- 1
State is a mapping. It records which real resource corresponds to which block in your configuration, so Terraform knows what it already manages.
- 2
Explain why it is needed. Without it there is no way to tell an unmanaged resource from one that should be updated, or to detect what changed.
- 3
Name the concurrency risk. Two simultaneous applies race on the same file, which can corrupt state or duplicate and orphan resources.
- 4
Give the fix. Remote state with locking, so the second apply waits rather than racing, plus versioning to recover from a bad write.
- 5
Flag the sensitivity. State can contain secrets in plain text, so it needs encryption and tight access control, never a git repository.
What gets you hired
State is the mapping between the resources in my configuration and the real objects that exist. Terraform needs it to know that this aws_instance block is that specific running instance, so it can compute a diff instead of recreating everything. If 2 people apply at once against the same state, they race: both read the same starting point, both write, and you can end up with a corrupted file or duplicated resources that nothing is now tracking. The fix is a remote backend with locking, so the second apply blocks until the first finishes, plus object versioning so a bad write can be rolled back. The other thing people miss is that state can contain secrets in plain text, database passwords for example, so it must be encrypted at rest with tight access control and must never live in git.
Then they probe: Someone deleted a resource in the console. What does Terraform do next run?
Practise this oneMidTerraform plan wants to destroy a production database that someone modified manually. What do you do?
What most people say
“I would run terraform apply since the code is the source of truth.”
This is how production databases get deleted. Code is the source of truth in principle, but reconciling that principle at apply time on a live database is data loss, and the correct sequence is to make code match intended reality first.
The structure behind a strong answer
- 1
Do not apply. A destroy on a production database is a stop condition, not something to push through and see.
- 2
Understand exactly why. Read the plan to see whether it is a replacement forced by an immutable attribute or a genuine deletion.
- 3
Reconcile rather than recreate. Update the configuration to match the intended real state, or import the current resource so state and reality agree.
- 4
Protect against the accident. Add prevent_destroy to the lifecycle block and deletion protection on the resource itself.
- 5
Fix the process. Remove manual write access so the pipeline is the only path, and add scheduled drift detection so this surfaces early rather than at apply time.
What gets you hired
I do not apply. A plan that destroys a production database means state and reality have diverged, and applying resolves that divergence by deleting data. First I read the plan properly: is it a replace forced by changing an immutable attribute, or a delete because the resource left the config? Then I look at what was changed manually and decide what the intended state actually is. Usually the right move is to update the Terraform configuration to describe the real, intended resource, then run plan repeatedly until it shows no changes, which proves code and reality now agree. If the resource is untracked, terraform import binds it. Then I make the accident hard to repeat: prevent_destroy in the lifecycle block, deletion protection on the database, removing console write access in production, and drift detection running nightly so I hear about this in a report rather than in a scary plan 3 weeks later.
Then they probe: How do you stop a destructive plan reaching apply at all?
Practise this oneSeniorHow would you structure Terraform for 40 services across 3 environments? What goes wrong with the obvious approaches?
What most people say
“Use one repository with a workspace per environment, which keeps everything consistent.”
Workspaces share a configuration and a backend, so environment differences get expressed as conditionals that grow unreadable, and a single state per environment still means a 40-service plan takes many minutes and one lock blocks everyone.
The structure behind a strong answer
- 1
Understand what state boundaries control. A state file is a unit of locking, plan time and blast radius all at once.
- 2
Name the monolith failure. One state for everything means slow plans, constant lock contention, and a mistake can destroy unrelated production resources.
- 3
Name the opposite failure. A state per tiny resource means dependency spaghetti and a change requiring 12 coordinated applies.
- 4
Split by blast radius and change frequency. Things that change together and fail together belong together, so networking, shared data and per-service infrastructure separate naturally.
- 5
Handle cross-state references carefully. Reading another state creates coupling, so prefer stable data sources or explicit published outputs.
What gets you hired
I think about a state file as three things at once: the unit of locking, the unit of plan time, and the unit of blast radius. That framing answers the question. One state for everything gives 15 minute plans, constant lock contention with 4 teams, and the property that a bad change can propose destroying unrelated production infrastructure, which is the scariest one. The opposite extreme, a state per resource, produces dependency spaghetti where a single logical change needs 12 applies in the right order. So I split by blast radius and change frequency: foundational networking and shared data stores in their own states because they change rarely and failure is catastrophic, then per-service states because those change daily and a mistake should only affect that service. Environments are separate directories with separate backends rather than workspaces, because workspaces share configuration and environment differences end up as conditionals that become unreadable. For cross-state references I prefer data sources on stable identifiers over reading another state directly, since remote state reads couple you to another team internal structure.
Then they probe: Forty services means 40 near-identical configurations. How do you avoid the duplication?
Practise this oneConfiguration
1 question · FoundationFoundationWhere should configuration like database URLs and API keys live, and why not in the repository?
What most people say
“In a .env file that is gitignored.”
It is half right for local development and wrong for production. It says nothing about how the secret reaches a running container, who can read it, or how it gets rotated, which is what the question is really about.
The structure behind a strong answer
- 1
Separate config from secrets. A feature flag or a log level is config. A database password is a secret. They need different homes.
- 2
Config comes from the environment. Injected at runtime as environment variables or mounted files, so one artifact runs anywhere.
- 3
Secrets come from a secret store. A managed secrets manager or vault, fetched at runtime, with access controlled per service identity.
- 4
Explain why the repo is disqualifying. Git history is permanent, so a committed secret is exposed to everyone with clone access forever, and deleting the line does not remove it from history.
What gets you hired
Non-secret config like log level or feature flags is injected from the environment at runtime, so the same artifact runs in all 3 environments. Secrets go in a managed secret store, and the running workload fetches them using its own identity rather than a shared credential. The repository is disqualifying for secrets because git history is permanent: once a key is committed, anyone who ever cloned the repo has it, and deleting the line in a later commit does not remove it, it just hides it from the current view. If it does get committed, the only correct response is to rotate the credential immediately and treat history rewriting as cleanup, not as the fix. Locally I use a gitignored env file, which is fine because it never leaves the machine.
Then they probe: A secret gets committed to main. Walk me through your response.
Practise this oneGit
2 questions · Foundation, JuniorFoundationWhat is the difference between git merge and git rebase, and when would you use each?
What most people say
“Rebase gives you a cleaner history so it is better.”
It picks a side without understanding the cost. Someone who believes rebase is simply better will eventually rewrite a shared branch and break the working copies of everyone on the team.
The structure behind a strong answer
- 1
Merge preserves history. It creates a merge commit joining two branches, so the true shape of what happened is kept.
- 2
Rebase rewrites history. It replays your commits on top of the target branch, producing new commits with new hashes and a linear history.
- 3
State the golden rule. Never rebase a branch other people have pulled, because rewriting shared history forces everyone else into a painful recovery.
- 4
Give the practical split. Rebase your own feature branch to keep it current and tidy before review, then merge it into main.
What gets you hired
Merge takes the two branch tips and creates a merge commit, preserving exactly what happened, including that the work was parallel. Rebase replays my commits one at a time on top of the target, creating new commits with new hashes and a straight line of history. The rule I hold to is that rebasing is safe on a branch only I have, and dangerous on a branch anyone else has pulled, because rewriting shared history means their local copy and the remote have diverged and 5 other people now need a recovery procedure. So in practice: rebase my feature branch onto main to stay current and to squash noisy fixup commits before review, then merge that branch into main. That gives a readable history without ever rewriting anything shared.
Then they probe: What does a squash merge do differently?
Practise this oneJuniorWhat branching strategy would you recommend for a team of 8 engineers shipping a web app, and why?
What most people say
“Git Flow, because it is the industry standard.”
Git Flow was designed for versioned software with multiple supported releases, and its own author has said it is a poor fit for continuously delivered web apps. Choosing it by reputation rather than by cadence is the tell.
The structure behind a strong answer
- 1
Start from release cadence. How often you release and whether you must support old versions decides the strategy, not fashion.
- 2
Default to trunk-based for continuous delivery. Short-lived branches merged to main within a day or two, with feature flags hiding incomplete work.
- 3
Explain why long branches hurt. A branch open for weeks accumulates merge conflicts and hides integration problems until the worst possible moment.
- 4
Name when heavier models earn their keep. Versioned or on-premise software with parallel supported releases genuinely needs release branches.
What gets you hired
For a web app that deploys continuously, trunk-based with short-lived branches. Everyone branches off main, keeps the branch under 2 days, and merges back behind a feature flag if the work is not finished. The reason is integration risk: a branch that lives 3 weeks accumulates conflicts and hides the fact that two people restructured the same module, and you find out at merge time under deadline pressure. Short branches surface that in hours. Feature flags decouple deploying code from releasing behaviour, which is what makes it safe to merge unfinished work. I would pair that with required review, a green pipeline before merge, and protected main. Git Flow with develop and release branches earns its complexity when you ship versioned software and support multiple releases at once, which is not this case, and adopting it here mostly adds ceremony.
Then they probe: How do you merge unfinished work without breaking production?
Practise this oneObservability
3 questions · Foundation, Mid, SeniorFoundationA web service is running in production. What would you monitor, and why those things?
What most people say
“CPU, memory, and disk usage on the servers.”
These are the easiest things to graph and the least connected to whether users are being served. A service can sit at 30% CPU while returning errors to every request, and this monitoring would show all green.
The structure behind a strong answer
- 1
Start with user-facing symptoms. Latency, error rate and traffic are what a user actually experiences, so they come first.
- 2
Add saturation. How full the system is, which is what predicts the next failure rather than describing the current one.
- 3
Explain the alerting split. Alert on symptoms that affect users, and keep resource metrics for diagnosis rather than paging.
- 4
Anchor it to a target. Pick a service level objective, then alert when the error budget is burning fast enough to threaten it.
What gets you hired
I would start with what a user feels: request rate, error rate, and latency at p95 and p99 rather than the average, because an average of 200ms can hide 5% of users waiting 4 seconds. Then saturation, how close resources are to their limit, since that predicts the next failure. CPU and memory matter for diagnosis but they are bad paging signals, because a service can be at 30% CPU and failing every request. So I alert on user-visible symptoms tied to an objective, for example paging when the error budget for a 99.9% target is burning fast enough to exhaust in hours, and leave resource dashboards for the investigation once someone is already looking. I would also want structured logs and traces so a spike leads to a cause instead of a guess.
Then they probe: Why p99 rather than average latency?
Practise this oneMidYour team gets 40 alerts a night and mostly ignores them. How do you fix that?
What most people say
“Raise the thresholds so fewer alerts fire.”
It reduces the number without improving the signal, and it risks silencing the alerts that mattered. The problem is not that thresholds are low, it is that most of these should never have paged a human.
The structure behind a strong answer
- 1
Name the real danger. When every page is noise, the one real page is missed. Fatigue is an outage waiting to happen.
- 2
Measure before cutting. Count alerts by rule and see which fired most and which ever led to action.
- 3
Delete the never-actioned ones. Any alert that has never resulted in a human doing something is not an alert, it is a metric.
- 4
Convert cause alerts to symptom alerts. Page on user-visible symptoms tied to an SLO, not on every internal cause that may not matter.
- 5
Route by urgency. Page only what needs a human now. Everything else becomes a ticket or a dashboard.
What gets you hired
I treat this as a reliability problem, because a team that ignores 40 alerts will ignore the one that matters. First I measure: group by rule and count firings over the last 30 days, plus how many led to any action. Typically 5 rules generate 80% of the volume and almost none of them ever caused someone to do something. Those get deleted, not tuned, because an alert nobody acts on is a metric wearing a costume. Then I convert cause-based alerts into symptom-based ones: instead of paging on each pod restart, page when the user-visible error rate threatens the SLO. Cause metrics stay on dashboards for diagnosis. Then routing by urgency: page for things needing a human within minutes, ticket for things needing action this week, dashboard for everything else. The bar I hold is that every page should be urgent, actionable and user-visible, and if a responder can do nothing at 3am it should not be a page.
Then they probe: How do you alert on an SLO without paging on every blip?
Practise this oneSeniorDefine an SLO for a checkout service. What would you pick and why?
What most people say
“Five nines availability, since checkout is the most critical service.”
Five nines is about 26 seconds of downtime a month, which is extraordinarily expensive and almost certainly beyond what the business needs or the architecture supports. Picking a number by importance rather than by cost and consequence is the tell.
The structure behind a strong answer
- 1
Choose the indicator from the user perspective. The proportion of checkout requests that succeed within a latency threshold, measured where the user is.
- 2
Set the target from consequence, not habit. Ask what failure actually costs and what each additional nine costs to deliver.
- 3
Express it as an error budget. A 99.9% target is about 43 minutes a month of allowed failure, which is a budget to spend deliberately.
- 4
Measure at the right boundary. Server-side metrics miss failures that never reached you, so include client-side or edge measurement.
- 5
Attach a consequence. An SLO with no agreed action when it is breached is decoration.
What gets you hired
I would define it as the proportion of checkout requests that complete successfully within 2 seconds, measured over a rolling 28 days, because that is what a user actually experiences: a checkout that succeeds in 30 seconds is a failure even though it returned 200. For the target I would resist reaching for nines and work from consequence: what does an hour of failed checkout cost in revenue and trust, and what would each additional nine cost in architecture and on-call. For most businesses that lands around 99.9%, which is roughly 43 minutes a month of error budget. Framing it as a budget is the useful part, because it converts reliability from an argument into an allowance: while we are inside budget the team ships freely, and when we exhaust it, work shifts to reliability by prior agreement. I would measure at the edge or client side rather than only server side, since server metrics cannot see requests that never arrived, which are exactly the ones users complain about. And it needs an agreed consequence on breach, or it is decoration.
Then they probe: Why 28 days rather than a calendar month?
Practise this oneKubernetes
4 questions · Junior, Mid, SeniorJuniorExplain the relationship between a Pod, a Deployment, and a Service in Kubernetes.
What most people say
“A Pod runs containers, a Deployment creates pods, and a Service exposes them.”
It is three definitions with no relationship between them. It misses why the other objects need to exist, which is that pods are ephemeral and their IPs change on every restart.
The structure behind a strong answer
- 1
Pod is the unit of execution. One or more containers sharing a network namespace and storage. It is disposable and has a lifespan measured in deploys, not months.
- 2
Deployment manages pods over time. It owns a ReplicaSet that keeps the requested number of pods running, and handles rolling updates and rollbacks when the spec changes.
- 3
Service gives a stable address. Pods get new IPs constantly, so a Service provides one stable DNS name and load balances across whichever pods currently match its selector.
- 4
Tie them together with the selector. Labels are the glue: the Deployment stamps labels on pods, the Service selects on those labels. A label mismatch is the most common reason traffic goes nowhere.
What gets you hired
The pod is the unit that actually runs, one or more containers sharing a network namespace and storage. The important property is that it is disposable: it dies on a node drain, a crash, or every deploy, and comes back with a different IP. Everything else exists because of that. A Deployment declares the desired state, say 3 replicas of this image, and through a ReplicaSet it keeps that true, replacing dead pods and doing rolling updates so old and new versions overlap during a release. A Service solves addressing: since pod IPs churn, it provides one stable DNS name and virtual IP and load balances across the pods matching its label selector. Labels are the connective tissue, and a selector that does not match the pod labels is the single most common reason a service returns nothing.
Then they probe: Traffic to a Service returns nothing but the pods are Running. Where do you look?
Practise this oneJuniorWhat is the difference between a liveness probe and a readiness probe, and what happens if you configure them wrong?
What most people say
“Liveness checks if the app is alive and readiness checks if it is ready.”
It restates the names without the consequences. The whole point is that one restarts the container and the other only removes traffic, and a candidate who cannot name that will eventually put a database check in a liveness probe.
The structure behind a strong answer
- 1
Liveness answers "should this be restarted". If it fails, the kubelet kills and restarts the container. It is for unrecoverable states like a deadlock.
- 2
Readiness answers "should this receive traffic". If it fails, the pod is removed from the Service endpoints but keeps running. It is for temporary unreadiness like warming a cache.
- 3
Name the classic mistake. Putting a dependency check in liveness. If the database blips, every pod fails liveness and the whole deployment restart-loops instead of waiting.
- 4
Add startup probes for slow starters. A startup probe gives a slow-booting app time before liveness applies, instead of loosening liveness forever.
What gets you hired
Liveness asks whether this container should be restarted. If it fails past the threshold, the kubelet kills and restarts it, so it should only check whether the process itself is wedged. Readiness asks whether this pod should get traffic. If it fails, the pod is pulled out of the Service endpoints but keeps running, so it is right for warming up or shedding load temporarily. The classic disaster is checking a downstream dependency in liveness: the database has a 30 second blip, all 10 pods fail liveness simultaneously, and the entire deployment enters a restart loop, turning a brief dependency hiccup into a full outage that outlives it. That check belongs in readiness, where pods stop taking traffic and then recover on their own. For slow-starting apps I use a startup probe so liveness only applies after boot.
Then they probe: Your app takes 90 seconds to start and keeps getting killed. What do you change?
Practise this oneMidHow do you decide CPU and memory requests and limits for a service, and what breaks if you get them wrong?
What most people say
“I would set limits generously so the app never runs out.”
Generous limits with no requests means the scheduler cannot pack nodes properly, so you pay for capacity you never use, and it still does not prevent an OOM kill if the request was low and the node is under pressure.
The structure behind a strong answer
- 1
Requests are for scheduling. The scheduler uses requests to decide which node has room, so requests set your guaranteed floor.
- 2
Limits are enforcement. Exceeding a memory limit kills the container. Exceeding a CPU limit only throttles it.
- 3
Derive numbers from observation. Measure real usage under load and set requests near the steady state, with headroom for spikes.
- 4
Name both failure modes. Set too high and you waste money and pack fewer pods per node. Set too low and you get OOM kills or heavy throttling.
- 5
Treat CPU limits carefully. Aggressive CPU limits cause throttling that shows up as latency spikes with no obvious error.
What gets you hired
Requests decide scheduling, limits decide enforcement, and the two resources behave differently when exceeded. Memory is incompressible: cross the limit and the container is killed with exit 137. CPU is compressible: cross the limit and you are throttled, which looks like unexplained latency rather than a crash. So I set the memory request from observed steady state with maybe 20% headroom and set the memory limit close to it, since memory that runs away should be killed rather than take down a node. For CPU I set a request from real usage but I am cautious about tight CPU limits, because throttling produces p99 latency spikes with no error in the logs, which is genuinely hard to diagnose. I get the numbers from actual load data over a week, not from a guess, and revisit after traffic changes. Wrong in one direction wastes money, wrong in the other causes 3am pages.
Then they probe: What are Guaranteed, Burstable and BestEffort QoS?
Practise this oneSeniorFour teams want to share one Kubernetes cluster. How do you isolate them, and when would you give them separate clusters instead?
What most people say
“Give each team a namespace, that is what namespaces are for.”
It is the right start and stops before everything that matters. Without quotas one team can consume the cluster, without network policy every pod can reach every other pod, and the answer implies namespaces provide security isolation they do not provide.
The structure behind a strong answer
- 1
Namespaces as the organisational unit. Namespace per team with RBAC scoped to it, which handles the who-can-touch-what question.
- 2
Resource isolation. Resource quotas and limit ranges per namespace so one team cannot starve the others.
- 3
Network isolation. Default-deny network policies, since by default any pod can reach any other pod across namespaces.
- 4
Be honest about the boundary. Namespaces are not a hard security boundary: a shared kernel, shared nodes and cluster-scoped resources mean a determined escape crosses them.
- 5
Name when to split clusters. Hostile tenancy, different compliance regimes, differing upgrade cadence, or blast radius requirements justify separate clusters.
What gets you hired
Namespace per team is the starting point, with RBAC scoped so a team administers its own namespace and nothing else. Then the parts people forget. Resource quotas and limit ranges, otherwise one team deploying a runaway job consumes the cluster and the other 3 teams have an incident they did not cause. Network policies with a default deny, because Kubernetes networking is flat by default and any pod can reach any other pod across namespaces, which surprises people. Pod security standards so nobody runs privileged containers. Then the honest part: namespaces are an organisational boundary, not a hard security boundary. Tenants share a kernel, share nodes, and cluster-scoped resources like CRDs and webhooks are global, so a container escape or a greedy webhook crosses namespaces. So I would split into separate clusters when tenants are mutually untrusted, when compliance requires demonstrable isolation, when teams need different upgrade cadences, or when the blast radius of one cluster failing is unacceptable. Otherwise multiple clusters mostly multiply operational cost.
Then they probe: What cluster-scoped things break the isolation illusion?
Practise this oneTroubleshooting
19 questions · Junior, Mid, Senior, PrincipalJuniorA developer says the build passes locally but fails in CI. How do you debug that?
What most people say
“I would rerun the pipeline and see if it passes.”
It treats the failure as random. Sometimes a rerun does pass, which is worse: it hides a flaky test or a race and trains the team to click retry instead of fixing the cause.
The structure behind a strong answer
- 1
Read the actual failure first. Get the exact error and the failing stage, because half of these are a genuine bug that the local run never exercised.
- 2
Enumerate what differs. The usual suspects: dependency versions from a stale lockfile, environment variables, filesystem case sensitivity, timezone, available memory, and network access.
- 3
Reproduce in the CI environment. Run the same container image locally, or use the CI system's debug session, so you are testing the same thing rather than a guess.
- 4
Close the gap permanently. Once found, make the difference impossible: pin the dependency, containerise the build, or add the missing variable to the config as code.
What gets you hired
First I read the actual error rather than assuming it is environmental, because a decent share of these turn out to be real bugs the local run never hit, like a test that depends on file ordering. If it is genuinely environmental, I enumerate the differences systematically: dependency versions, since a developer with a warm node_modules can be 3 patch versions behind the lockfile, then environment variables, case sensitivity because macOS is case-insensitive and the CI Linux image is not, timezone, memory limits, and whether the build has network access. The fastest route is usually to run the CI container image locally with the same command, so I am debugging the same environment rather than guessing. Once I find it, I make it impossible to recur: pin the version, containerise the build so local and CI are identical, or move the missing variable into config as code.
Then they probe: It passes on retry roughly half the time. What is your response?
Practise this oneJuniorA production server is failing writes. Disk is 100% full. Walk me through what you do.
What most people say
“I would delete the largest log files to free up space immediately.”
Deleting a log a process holds open frees no space at all, so it looks like nothing happened, and deleting a log you have not identified can destroy the evidence you need for the incident that filled it.
The structure behind a strong answer
- 1
Find where the space went. Work down the tree by size rather than guessing, so you delete the right thing.
- 2
Check inodes too. A disk can report space free but be out of inodes from millions of tiny files, which presents identically.
- 3
Reclaim safely. Rotate and compress logs, clear package and build caches, remove old artifacts. Never delete something you cannot identify.
- 4
Know the held-open file trap. Deleting a file a process still has open frees nothing until that process closes it or restarts.
- 5
Fix the cause. Log rotation, retention policy, and an alert at 80% so this is a ticket rather than an outage.
What gets you hired
First find where it went rather than deleting blind: du down the tree to find the biggest directories, which usually lands on logs, an unrotated file, a core dump, or a build cache. I also check inodes, because a filesystem can show free space and still fail writes if millions of small files exhausted the inode table, and that presents identically to a full disk. To reclaim, I rotate and compress logs rather than deleting them, since the log that filled the disk is often the evidence for why. The trap to know is that deleting a file a process still holds open frees nothing: the space returns only when the process closes it, so you delete a 40 GB log, df still says 100%, and you go looking for a second problem that does not exist. Check with lsof for deleted-but-open files and restart or signal the holder. Then the actual fix: log rotation configured properly, a retention policy, and an alert at 80% so next time it is a ticket at 2pm rather than a page at 2am.
Then they probe: You deleted 40 GB and df still shows full. Why?
Practise this oneJuniorAt midnight everything started failing with TLS errors. What happened and what do you do?
What most people say
“I would restart all the services to see if that clears the TLS errors.”
Restarting cannot renew a certificate, so it wastes minutes during a total outage and adds churn. It also signals that the candidate did not recognise the strongest clue available, which was the exact-midnight timing.
The structure behind a strong answer
- 1
Recognise the timing signal. A failure exactly at a date boundary affecting everything at once strongly suggests certificate expiry.
- 2
Confirm quickly. Inspect the served certificate and read its expiry, which takes one command and removes all doubt.
- 3
Restore service. Renew and deploy, or fail over to a path with a valid certificate.
- 4
Check every layer. Edge, internal service to service, client certificates and any pinned chain, since renewing one does not fix the others.
- 5
Prevent the class. Automated renewal plus alerting 30 days out, so a human forgetting is not the control.
What gets you hired
Everything failing simultaneously at midnight is a certificate expiry until proven otherwise, because that is one of very few faults that hits a hard time boundary across an entire fleet. I confirm in one command by inspecting the served certificate and reading the notAfter date, which takes 15 seconds and removes all speculation. Then restore: renew and deploy, or if renewal is slow, fail over to a path with a valid certificate. Something people miss is checking every layer, since a system can have an edge certificate, internal service-to-service mTLS, client certificates and pinned chains, and renewing the edge one while an internal one is also expiring means a second outage an hour later. Then prevention, which is the real answer, because this is 100% preventable: automated renewal with something like cert-manager or ACME, monitoring that alerts 30 days before expiry on every certificate including internal ones, and an inventory so no certificate is unowned. If a human remembering is your renewal process, this will recur.
Then they probe: Renewal is automated but it expired anyway. What went wrong?
Practise this oneMidA pod is in CrashLoopBackOff after a deploy. Walk me through your debugging, command by command.
What most people say
“I would restart the pod and see if it comes back.”
CrashLoopBackOff already is Kubernetes restarting it repeatedly, so a manual restart adds nothing. It signals the candidate does not know what the state name means, which is the whole question.
The structure behind a strong answer
- 1
Read the previous container logs. The current container may not be running, so the useful output is in the previous instance.
- 2
Describe the pod for events. Events reveal image pull failures, mount failures, OOM kills and probe failures that never reach application logs.
- 3
Read the exit code. Exit code 137 means killed, usually out of memory. 1 or 2 usually means the application itself failed.
- 4
Separate config from code. Missing environment variable, unmountable secret and bad config are the most common causes after a deploy.
- 5
Restore service, then fix. Roll back to the previous working revision while investigating, rather than debugging in front of users.
What gets you hired
CrashLoopBackOff means it already restarted several times and Kubernetes is now backing off, so restarting again tells me nothing. I start with logs for the previous instance, since the current container is usually dead: kubectl logs pod --previous. If the app logged a stack trace, I am done in 30 seconds. If logs are empty, the container probably never started, so kubectl describe pod for the events, which surfaces ImagePullBackOff, a missing secret or configmap, or an OOM kill. The exit code narrows it further: 137 is a kill signal, almost always the memory limit, while 1 is the app exiting on its own. After a deploy, the top causes are a missing environment variable, a secret that does not exist in that namespace, and a memory limit that is fine in staging but not under production load. Meanwhile I roll back to the last good revision so users are not waiting on my investigation.
Then they probe: Logs are empty and events show nothing unusual. Now what?
Practise this oneMidThe deploy reports success, pods are Running, but users get 502s. Where do you look?
What most people say
“I would check the application logs for errors.”
Reasonable but incomplete, and often empty here: if traffic never reaches the container, the app logs nothing at all. The candidate needs to reason about the routing path, not just the application.
The structure behind a strong answer
- 1
Read what 502 actually means. A proxy reached your service but got no valid response, so the failure is between load balancer and container, not at the edge.
- 2
Check readiness, not Running. Running only means the container started. Readiness decides whether it is in the Service endpoints.
- 3
Check the endpoints object. Empty endpoints means label selector mismatch or all pods failing readiness, and that produces exactly this symptom.
- 4
Check ports and protocol. A targetPort that does not match the container listening port, or the app bound to localhost instead of all interfaces.
- 5
Check the app is really serving. Port-forward directly to a pod and curl it, which removes every layer of routing from the question.
What gets you hired
A 502 means something proxied to my service and did not get a usable response, so I work the path from the outside in. Running is not ready, so first I check whether pods are passing readiness, because a pod can run happily while failing readiness and therefore never join the Service. Then I check the endpoints object directly: if it is empty, that is the answer, and the cause is either a label selector that does not match or every pod failing readiness. If endpoints look fine, I check ports, since a targetPort of 8080 against an app listening on 3000 gives exactly this, as does an app bound to 127.0.0.1 rather than 0.0.0.0, which works when you exec in and fails from outside. Then I port-forward straight to one pod and curl it: if that works, the problem is routing, if it does not, the problem is the app. That splits the search space in half in about 20 seconds.
Then they probe: Endpoints are populated and port-forward works. What is left?
Practise this oneMidA Kubernetes node goes NotReady and pods are stuck Terminating. What is happening and what do you do?
What most people say
“I would force delete the stuck pods so they get rescheduled.”
It clears the symptom and can cause data corruption. Force delete tells the API server to forget the pod without knowing whether the container is still running, so a StatefulSet member can end up running twice, writing to the same volume.
The structure behind a strong answer
- 1
Understand what NotReady means. The kubelet has stopped reporting healthy to the API server, which may mean the node is dead or merely unreachable.
- 2
Explain why pods hang Terminating. Graceful deletion needs the kubelet to confirm, and an unreachable kubelet can never confirm, so the object lingers.
- 3
Restore capacity first. Ensure workloads reschedule elsewhere, which needs the controller to evict and enough spare capacity to land.
- 4
Diagnose the node itself. Common causes are disk pressure, memory exhaustion, a dead kubelet, or lost network to the control plane.
- 5
Be careful with force delete. Force deleting removes the object without confirming the container stopped, which is dangerous for anything with a stable identity.
What gets you hired
NotReady means the kubelet stopped reporting to the API server, and importantly that does not tell me whether the node is dead or just unreachable. Pods hang in Terminating because graceful deletion waits for the kubelet to confirm the container stopped, and an unreachable kubelet never will. My first priority is capacity: after the eviction timeout, usually about 5 minutes, the controller marks pods for eviction and they should reschedule, so I check that replacements are actually landing and that the cluster has room. Then I diagnose the node: disk pressure, memory exhaustion, a crashed kubelet, or a network partition to the control plane are the usual four. If it is genuinely gone, I drain and remove it and let autoscaling replace it. I am careful with force delete, because it removes the pod object without confirming the container stopped, and for a StatefulSet that risks two instances writing to the same volume at once.
Then they probe: Why is force delete specifically dangerous for a StatefulSet?
Practise this oneMidYou get paged: the site is down. You have no other information. What are your first five minutes?
What most people say
“I would start looking through the application logs to find the error.”
Logs are enormous and unfiltered when you do not yet know the scope. Without knowing whether one endpoint or everything is failing, you are reading a haystack, and you have skipped the two questions that would have cut it by 90%.
The structure behind a strong answer
- 1
Confirm it is real and scope it. Is everything down or one endpoint, all regions or one, all users or a segment. Scope eliminates whole categories immediately.
- 2
Ask what changed recently. Deploys, config changes, feature flags, infrastructure changes and certificate expiry in the last few hours.
- 3
Check dependencies and provider status. Your database, your identity provider and your cloud provider status page, so you do not debug someone else outage.
- 4
Mitigate as soon as you have a candidate. Roll back or fail over on a plausible cause rather than waiting for certainty.
- 5
Communicate from minute one. Say in the channel what you know and what you are checking, so others do not duplicate or conflict.
What gets you hired
My first move is not a terminal, it is 2 questions: what is the scope, and what changed. Scope means is it every endpoint or one, every region or one, all users or a subset, because "checkout fails for 5% of users" and "nothing responds anywhere" are completely different investigations. Then recency: deploys in the last 4 hours, config or flag changes, infrastructure changes, and certificate expiry, since a very high share of outages are something a human did recently. In parallel I glance at the dependency dashboard and the cloud provider status page so I am not debugging someone else outage. I post in the channel immediately, even just "site down, checking scope and recent deploys", because that prevents 3 other people starting the same work. The moment I have a plausible candidate, usually a recent deploy, I mitigate rather than confirm: rolling back a suspect deploy costs 2 minutes, and being right about the cause before acting can cost 40.
Then they probe: Scope is one endpoint failing for everyone, no recent deploy. Where next?
Practise this oneMidPods in one namespace cannot reach an external API by hostname, but the IP works. What is happening?
What most people say
“I would hardcode the IP address in the config so it works.”
It removes the symptom and creates a time bomb: the external provider rotates that IP and the service breaks with no obvious cause. It also abandons the actual diagnosis, which will affect every other name in that namespace.
The structure behind a strong answer
- 1
Read the clue precisely. IP works and name fails means network and routing are fine, so the fault is name resolution only.
- 2
Test resolution from inside the pod. Resolve from the affected pod rather than from your laptop, since the resolver config differs.
- 3
Check the cluster DNS service. Are the DNS pods healthy, is the service reachable, and is DNS itself being rate limited or overloaded.
- 4
Check the pod resolver config. The search domains and ndots setting change how a name is expanded, and a wrong dnsPolicy sends queries to the wrong resolver.
- 5
Consider policy blocking DNS. A default-deny network policy that forgets to allow UDP 53 egress produces exactly this symptom in one namespace.
What gets you hired
IP working and name failing isolates it to DNS, which is genuinely useful because it removes routing, firewalls at the IP level and the remote service from suspicion. The fact that it is one namespace is the second clue, because cluster DNS is shared, so a broken resolver would break everything. That points hard at a network policy: a default-deny egress policy in that namespace that allows the API port but forgets UDP port 53 to the DNS service gives exactly this symptom, and it is probably the most common cause I have seen. Otherwise I would resolve from inside an affected pod rather than from my machine, check the pod resolv.conf for search domains and the ndots setting, since a high ndots value means a name gets tried against 4 or 5 search suffixes before the real one and can time out, and verify the DNS pods are healthy and not rate limited. Hardcoding the IP is not a fix, it is a scheduled outage for whenever the provider rotates it.
Then they probe: Why does ndots cause intermittent slowness rather than failure?
Practise this oneMidService A cannot reach Service B inside the cluster. Give me your diagnostic order.
What most people say
“I would check the network policies since that is usually the cause.”
It may well be right, but jumping to a favourite cause is not a method. If it is not policy, the candidate has no second step, and the interviewer is specifically asking for the order of investigation.
The structure behind a strong answer
- 1
Establish what "cannot reach" means. Connection refused, timeout, DNS failure and TLS error are four different problems with four different causes.
- 2
Test from inside the source pod. Network reality differs by namespace and policy, so test from where the traffic actually originates.
- 3
Resolve name, then connect to IP. That single split separates DNS problems from connectivity problems immediately.
- 4
Check the target is actually serving. Endpoints populated, readiness passing, and the app listening on the expected port and interface.
- 5
Then policy. Network policies on either side, since egress from A and ingress to B are separate rules and either can block.
What gets you hired
First I ask what the failure actually is, because the error is data: connection refused means something answered and said no, so the path works and the target is not listening. A timeout means packets are disappearing, which points at policy or routing. A DNS error means the name never resolved. A TLS error means the connection worked and the handshake failed. Those are four different investigations, so I would not proceed until I know which. Then I test from inside pod A rather than my laptop, resolving the name first and then connecting directly to the pod IP, which cleanly splits DNS from connectivity in about 20 seconds. If the IP works, it is DNS or the service object. If neither works, I check whether B is actually serving: endpoints populated, readiness passing, and the process bound to 0.0.0.0 rather than 127.0.0.1, since binding to localhost works when you exec in and fails from anywhere else. Then network policy on both sides, remembering that egress from A and ingress to B are separate rules.
Then they probe: Connection refused rather than timeout. What does that eliminate?
Practise this oneMidAn app is slow but CPU, memory and disk all look normal. Where do you look next?
What most people say
“If the resources look fine then the application code must be inefficient.”
It jumps to a conclusion that is expensive to act on and usually wrong. Blaming code before checking pools, locks and dependency latency sends a team to optimise algorithms while the real problem is a connection pool of size 10.
The structure behind a strong answer
- 1
Reframe from consuming to waiting. Low resource usage with high latency means the process is blocked, not busy.
- 2
Check connection and thread pools. A saturated pool queues work while CPU sits idle, which is the single most common version of this.
- 3
Check dependency latency. Time spent waiting on a database or an external API shows up nowhere in host metrics.
- 4
Check locks and serialisation. Database lock contention or an application mutex serialises work regardless of available capacity.
- 5
Check throttling and network limits. CPU throttling against a cgroup quota, or network or IOPS limits, all look fine in average utilisation.
What gets you hired
Normal resource usage with bad latency means the process is waiting rather than working, so I stop looking at consumption metrics and start looking at queues and blocking. The most common cause by far is a saturated connection or thread pool: a pool of 10 database connections with 50 concurrent requests means 40 requests wait, while CPU shows 20% because nothing is computing. Next, dependency latency, since time spent waiting on a database or third party never appears in host metrics at all, and this is exactly what distributed tracing is for. Then contention: database lock waits or an application-level mutex serialise work no matter how much capacity exists. Then the sneaky ones: CPU throttling against a cgroup quota, which averages out to low utilisation while causing latency spikes within each period, and network or IOPS limits on the instance type. I would get a trace of a slow request first, because it usually points straight at whichever of these it is instead of me checking all five.
Then they probe: How do you confirm connection pool saturation?
Practise this oneMidA Kubernetes rollout is stuck halfway. Old pods are running, new ones are not coming up. What is going on?
What most people say
“I would delete the stuck pods so Kubernetes recreates them.”
The recreated pod hits the identical condition, so it changes nothing except adding churn. It also skips the diagnosis entirely: the reason the pod cannot become ready is the answer to the question.
The structure behind a strong answer
- 1
Recognise it as a safety mechanism. The rollout stops precisely because new pods are not becoming ready, which is the system preventing a bad release.
- 2
Check whether the pod is scheduling at all. Pending means no node can take it: insufficient resources, node selectors, taints, or unavailable volumes.
- 3
Check whether it is starting. Image pull failures, missing config or secrets, and crash loops all stop a pod short of ready.
- 4
Check whether it is passing readiness. A running pod that never passes readiness holds the rollout indefinitely, which is usually a probe misconfiguration or a real startup failure.
- 5
Decide whether to roll back. Old pods are still serving, so there is no user impact and no need to rush a risky fix.
What gets you hired
The important thing first: this is Kubernetes working correctly. It will not remove old pods until new ones are ready, so users are still being served and I have time to think rather than panic. Then I look at where the new pod stopped, and there are 3 distinct places. Pending means it never scheduled, and describe will say why: insufficient CPU or memory across nodes, a node selector or affinity nothing satisfies, a taint without a matching toleration, or a volume that cannot attach because it is still bound elsewhere. If it scheduled but is not running, the usual causes are an image that cannot be pulled, often a typo or a missing registry credential, or a missing configmap or secret in that namespace. If it is running but never ready, that is a readiness probe failing, which is either a genuinely broken new version or a probe with too short a timeout for a slower startup. Given old pods are serving fine, I would roll back if the cause is not obvious within about 5 minutes, and investigate without the pressure.
Then they probe: The events say Insufficient cpu. What are your options?
Practise this oneSeniorp99 latency tripled overnight with no deploy and no traffic change. How do you investigate?
What most people say
“I would scale up the service to handle the load better.”
It treats a symptom with capacity without knowing the cause, and if the bottleneck is a shared database or a lock, adding instances makes it worse by increasing concurrency against the same contended resource.
The structure behind a strong answer
- 1
Establish the exact shape of the change. Step change or gradual ramp, and which percentiles moved, since p99 alone versus all percentiles mean different causes.
- 2
Localise with tracing. Find which span grew. Latency is almost always concentrated in one hop rather than spread evenly.
- 3
Think about accumulating state. Data growth crossing a threshold, a table outgrowing an index, a cache filling, a queue backing up, disk filling.
- 4
Check what changed that was not a deploy. Managed service maintenance, a dependency deploying their change, a config or flag flip, or an autoscaling event.
- 5
Compare a fast and a slow request. Two traces side by side localise the difference far faster than reading dashboards.
What gets you hired
No deploy and no traffic change points at accumulating state or at something outside my service. First I look at the shape: a step change at a specific minute suggests an event, like a managed database failing over or a config flip, while a gradual ramp over hours suggests something filling up. I also check whether only p99 moved or the whole distribution, since p99 alone often means a subset of requests hitting a bad path, for example one shard or one cache miss pattern. Then tracing to localise: compare a trace from before and one now, and usually 90% of the added time sits in a single span. The classic causes with flat traffic are a table crossing a size where a query stops using an index, a cache reaching capacity so hit rate falls, a queue slowly backing up, or a disk filling and slowing writes. I would also ask whether a dependency deployed, because my no-deploy is not their no-deploy. Scaling up is a last resort, since if the bottleneck is a shared lock, more instances make it worse.
Then they probe: Tracing shows one database query went from 5ms to 900ms. What do you check?
Practise this oneSeniorAbout 2% of requests return 500s. The rest are fine. Nothing is obviously broken. How do you find it?
What most people say
“2% is within acceptable error budget, so I would monitor it and see if it gets worse.”
It rationalises a signal instead of investigating it. Partial failures are usually early warnings of something that will become total, and 2% of requests is a real number of real users experiencing a broken product right now.
The structure behind a strong answer
- 1
Find the dimension that separates them. Group failures by instance, region, version, customer, endpoint and payload shape until one dimension concentrates them.
- 2
Check whether it is one bad replica. 2% of traffic is suspiciously close to 1 unhealthy instance out of a fleet.
- 3
Look for data-dependent paths. A specific record shape, an unusually large payload, or a null in an unexpected place hits only some requests.
- 4
Consider resource exhaustion at the tail. Connection pool limits and thread starvation fail a small percentage first, not everything at once.
- 5
Get one full trace of a failure. A single complete failing trace beats hours of aggregate dashboard reading.
What gets you hired
The whole game is finding what is different about the failing 2%, so I slice by dimension until the failures concentrate. Instance first, because 2% is suspiciously close to 1 bad pod in a fleet of 50, and if all failures come from one instance I have my answer in 30 seconds. If not, then region, service version during a partial rollout, endpoint, customer or tenant, and payload characteristics. Data-dependent failures are common and invisible in aggregate: one customer with 10000 items in a list, or a record with a null nobody expected. Resource exhaustion also presents this way, since a connection pool at its limit fails a small share while most requests get a connection, and that will grow. Then I get one complete trace and one full stack trace of an actual failure, because a single concrete example beats hours of dashboards. I would not accept 2% as within budget: partial failures are usually the early phase of total ones, and 2% of a million requests is 20000 broken experiences.
Then they probe: All failures come from one pod. Do you just delete it?
Practise this oneSeniorA service gets OOM killed every 6 hours. Restarting fixes it. How do you find the cause?
What most people say
“I would add a cron job to restart the service every 4 hours so it never hits the limit.”
It converts a visible failure into a hidden one and stops the clock on the investigation. The leak keeps growing, and the same code in a larger deployment will eventually leak faster than the restart schedule.
The structure behind a strong answer
- 1
Confirm the shape of memory growth. Steady climb to the limit means a leak. Sawtooth or a spike means a workload pattern or garbage collection behaviour.
- 2
Rule out the boring causes first. A limit set too low for real traffic, or a runtime unaware of the cgroup limit sizing its heap off host memory.
- 3
Correlate growth with traffic. If it tracks request count, it is per-request retention. If it grows while idle, it is a background task or a cache.
- 4
Capture a heap profile before the kill. Profile at intervals and diff, so you see what class of object accumulates rather than guessing.
- 5
Mitigate honestly while investigating. A scheduled restart is an acceptable stopgap if you name it as such, with an owner and a deadline.
What gets you hired
A 6 hour cycle with a clean restart is a leak until proven otherwise, but I would rule out the boring explanations first. Is the memory limit simply too low for current traffic, and is the runtime aware of its cgroup limit, because a JVM or Node process that sizes its heap from host memory rather than the container limit will happily grow past it and get killed. Then I look at the growth curve: a steady climb tracking request count means something is retained per request, while growth that continues when traffic drops to nothing points at a background job or an unbounded cache. The decisive evidence is heap profiles captured at 1 hour and 5 hours and diffed, which shows what class of object is accumulating rather than leaving me theorising. The usual culprits are an unbounded cache with no eviction, event listeners never removed, or connections not returned to a pool. I would set a scheduled restart as a stopgap so nobody is paged, but I would say plainly that it is a stopgap with an owner and a date, not a fix.
Then they probe: Memory grows even at zero traffic. What does that narrow it to?
Practise this oneSeniorA message queue is growing and consumers are not keeping up. What do you do, in what order?
What most people say
“Scale up the number of consumers until the queue drains.”
It is right only in one of the four common cases. If consumers are crashing, if there is a poison message, or if the real bottleneck is a downstream database, more consumers either does nothing or actively worsens throughput through contention.
The structure behind a strong answer
- 1
Establish whether it is a spike or a slowdown. More messages arriving is a different problem from each message taking longer.
- 2
Check whether consumers are healthy. Crashed or restart-looping consumers look identical to slow ones from the queue depth graph.
- 3
Look for a poison message. A message that always fails and is redelivered forever blocks progress and burns capacity without draining anything.
- 4
Check the downstream bottleneck. If consumers are blocked on a database, adding consumers increases contention and makes throughput worse.
- 5
Then scale, and protect the backlog. Scale once you know the bottleneck is consumer capacity, and consider load shedding or prioritisation if the backlog is unrecoverable.
What gets you hired
I would not scale first, because 3 of the 4 common causes get worse when you do. First: is the arrival rate up or is per-message processing time up, since those are different problems and the graphs distinguish them immediately. Second: are consumers actually healthy, because a crash-looping consumer produces exactly the same queue-depth curve as a slow one, and scaling replicas of a crashing consumer accomplishes nothing. Third: is there a poison message being redelivered forever, which both blocks progress and consumes capacity, and the fix is a dead letter queue with a redelivery limit rather than more workers. Fourth: what are consumers blocked on, because if each one is waiting on a database at its connection limit, doubling consumers doubles contention and total throughput can actually fall. Only once I know the bottleneck is genuinely consumer capacity do I scale. If the backlog is already 6 hours deep and growing, I would also discuss shedding or prioritising, because processing stale messages in strict order can be worse than dropping some.
Then they probe: The backlog will take 8 hours to clear even at full capacity. What do you propose?
Practise this oneSeniorA change passes every test in staging and fails in production. How do you approach the difference?
What most people say
“Production is just different, so some things can only be caught there. I would fix the bug and move on.”
It accepts the gap as a fact of life and guarantees recurrence. The specific bug is the cheap part; the expensive part is that staging will keep giving false confidence on this same axis forever.
The structure behind a strong answer
- 1
Enumerate the axes of difference. Data volume and shape, traffic pattern and concurrency, configuration, scale, and integrations that are mocked in staging.
- 2
Suspect data first. Staging data is usually smaller, cleaner and older, so query plans and edge cases differ dramatically.
- 3
Suspect concurrency second. Races and lock contention need real parallelism, which sequential test suites never produce.
- 4
Reproduce with production-like conditions. Shadow traffic, a production data sample, or load testing at real concurrency, so the reproduction is meaningful.
- 5
Treat the gap as the real defect. Every one of these is a signal that staging does not represent production in some specific way worth fixing.
What gets you hired
I would treat the divergence as the finding, not just the bug. The axes are usually data, concurrency, configuration, scale, and mocked integrations. Data is the most common: staging often has 10000 rows where production has 40 million, so a query that uses an index in one does a table scan in the other, and edge-case records that accumulated over 5 years simply do not exist in staging. Concurrency is next, because tests run sequentially and never produce the race that appears when 200 requests hit the same row. Then configuration, where a flag or a limit differs, and mocked third parties that always return quickly and never fail. To reproduce meaningfully I want production-like conditions: a sanitised production data sample, load at real concurrency, or shadow traffic mirrored to a staging deploy. Then the real fix is closing whichever axis caused it: seed staging to a realistic scale, or add a load test at production concurrency to the pipeline. Otherwise I have fixed one bug and kept the false confidence.
Then they probe: You cannot copy production data for privacy reasons. What then?
Practise this oneSeniorUsers report a bug you cannot reproduce and that appears in no logs. How do you proceed?
What most people say
“If it is not in the logs and I cannot reproduce it, it is probably user error.”
It dismisses a real signal and, worse, ignores the most useful clue available: the absence of a log usually means an exception is being swallowed or the failure is client-side, both of which are genuine defects in observability.
The structure behind a strong answer
- 1
Take the report seriously and get specifics. Exact time, user, browser or client, and what they did, since a precise timestamp turns an unsearchable problem into a searchable one.
- 2
Ask why there is no log. Absence of a log is itself a finding: a silent catch, a client-side failure, or a path with no instrumentation.
- 3
Look for the request rather than the error. Find the specific request by user and time and inspect its full trace, even where nothing was flagged as an error.
- 4
Consider what differs about that user. Data shape, permissions, feature flags, client version, geography and network conditions.
- 5
Add instrumentation and wait. If you cannot see it, make it visible: targeted logging or client-side error reporting, then catch the next occurrence.
What gets you hired
The absence of a log is the first clue, not a reason to close the ticket. It usually means one of three things: an exception is being caught and swallowed somewhere, the failure is client-side and never reached my server, or the code path has no instrumentation at all. All three are findings. So I start by getting specifics from the user, especially an exact timestamp and their account, because that converts an unsearchable problem into a lookup: I find their actual requests in that 2 minute window and read the full trace, including successful ones, since a 200 response with wrong content is invisible to error monitoring. Then I ask what is different about them: feature flag state, data shape, permissions, client version, region, network quality. If I still cannot see it, I add targeted instrumentation, logging on that specific path and client-side error reporting, and wait for the next occurrence, which usually comes within days. I would tell the user honestly that I cannot see it yet and am instrumenting, because silence makes people stop reporting bugs and that is a much worse outcome.
Then they probe: What kinds of failure never reach your server logs at all?
Practise this oneSeniorThe same incident has now happened three times despite two postmortems. What is wrong?
What most people say
“The postmortems must have missed the real root cause, so I would do a deeper technical investigation this time.”
It assumes a technical failure when the evidence points at a process one. Doing a third deeper investigation without asking why the first two produced no change is likely to produce a third document nobody acts on.
The structure behind a strong answer
- 1
Read the previous postmortems first. What did they identify and what actions were agreed, since the answer is usually visible there.
- 2
Check whether actions were completed. The most common cause is that agreed actions were never done, because nothing tracked them.
- 3
Check whether they addressed the real cause. Actions like "be more careful" or "add documentation" do not change system behaviour and cannot prevent recurrence.
- 4
Look for a deeper shared cause. Three occurrences may be symptoms of an architectural weakness the postmortems each treated locally.
- 5
Fix the process, not just the incident. Action items need owners, dates and tracking, and repeat incidents should escalate to a different level of review.
What gets you hired
Three occurrences after 2 postmortems is a process failure, and I would say that plainly rather than starting a third investigation. So I read the previous 2 first and check the most likely explanation: were the action items actually completed. In my experience this is the answer most of the time, because actions get agreed in the emotional aftermath, no owner or date is attached, and 2 weeks later everyone is on roadmap work. If they were completed, then the second question is whether they were the right kind of action. Items like "be more careful with migrations" or "document the process" feel like prevention and change no system behaviour, so the incident recurs with a different person. Real actions change what is possible: a guardrail, an automated check, a removed permission. Third possibility is that these 3 incidents share a deeper architectural cause each postmortem treated locally. The process fix is that action items get an owner and a date and are tracked like any other work, and a repeat incident automatically escalates to a wider review rather than another copy of the same document.
Then they probe: How do you make action items actually get done?
Practise this onePrincipalOne slow dependency takes down your entire system. Why does that happen and how do you prevent it?
What most people say
“We should add retries so that requests to the slow dependency eventually succeed.”
Retries against an overloaded dependency are actively harmful: they multiply load on the thing already failing, which is a well-known way to turn a partial degradation into a total outage and to prevent recovery.
The structure behind a strong answer
- 1
Explain the propagation mechanism. Callers hold threads or connections while waiting, so a slow dependency consumes the caller resources until it too cannot serve.
- 2
Note that slow is worse than down. A fast failure frees resources immediately, while a slow response holds them for the entire timeout.
- 3
Bound every wait. Aggressive timeouts are the single highest-value control, because unbounded waiting is what enables the cascade.
- 4
Isolate resources. Bulkheads: separate connection pools per dependency so one saturating cannot starve the others.
- 5
Fail fast and degrade. Circuit breakers stop hammering a sick dependency, and a degraded response beats a total outage.
What gets you hired
The mechanism is resource exhaustion propagating backwards. My service calls a dependency that slows from 50ms to 8 seconds. Each in-flight request now holds a thread and a connection for 8 seconds instead of 50ms, so at any real request rate my pool is exhausted within seconds, and now my service cannot serve any request, including the 90% that never needed that dependency. Then my callers do the same thing, and it walks up the graph. Counter-intuitively a dependency being completely down is safer than being slow, because a refused connection frees the resource instantly. The controls, in order of value: aggressive timeouts on every network call, because unbounded waiting is what enables the whole cascade, then bulkheads so each dependency gets its own bounded pool and cannot starve the others, then circuit breakers to stop sending traffic to something already failing, then graceful degradation so the 90% of the response that does not need it still works. Retries need care: naive retries amplify load on the failing thing, so they need exponential backoff, jitter, and a budget capping retries as a fraction of traffic.
Then they probe: How do you choose a timeout value?
Practise this oneDeployment
2 questions · JuniorJuniorCompare rolling, blue-green, and canary deployments. Which would you pick and why?
What most people say
“Canary is the safest so I would always use canary.”
It ignores that canary needs metrics good enough to judge a small sample, plus routing control and automation. Recommending it for a low-traffic internal service means waiting hours for statistically meaningless data.
The structure behind a strong answer
- 1
Rolling: replace gradually. Instances are replaced in batches. No extra infrastructure, but both versions serve traffic together and rollback is another slow roll.
- 2
Blue-green: two full environments. Deploy to the idle one, test it, then switch traffic at once. Rollback is a switch back in seconds, at the cost of double the infrastructure.
- 3
Canary: a small slice first. Send 1 to 5% of traffic to the new version, watch real metrics, then widen. Best detection, most tooling required.
- 4
Choose against blast radius and detection speed. The real question is how quickly you would know it was bad and how much it costs to be wrong for a few minutes.
What gets you hired
Rolling replaces instances in batches, so it needs no extra capacity but runs both versions at once and rolls back slowly. Blue-green keeps two full environments and flips traffic in one move, which makes rollback a 30 second switch, but you pay for double capacity and it does not protect you from a bad release, it just makes undoing it fast. Canary sends a small slice, say 5%, to the new version and widens only if error rate and latency hold, so it limits blast radius and actually detects problems rather than just reversing them. I choose on detection speed and blast radius: a high-traffic user-facing service justifies canary because 5% gives a usable signal in minutes. A low-traffic internal tool does not, since that slice would take hours to say anything, so rolling with a fast rollback is more honest. Both versions overlapping also means the database schema has to be compatible with both, whichever I pick.
Then they probe: What has to be true of your database for any of these?
Practise this oneJuniorYou deploy at 4pm and errors spike at 4:05. Walk me through exactly what you do.
What most people say
“I would check the logs to work out what is wrong and then fix it.”
It leaves users broken while you investigate. Debugging can take an hour, rollback takes 2 minutes, and every minute of that hour is real user harm that was avoidable.
The structure behind a strong answer
- 1
Restore service first. Roll back or disable the feature flag immediately. Diagnosis happens after users stop being affected.
- 2
Communicate while acting. Post in the incident channel what you are doing so nobody duplicates work or makes a conflicting change.
- 3
Confirm recovery with data. Watch the error rate return to baseline rather than assuming the rollback worked.
- 4
Preserve evidence. Capture logs, traces and the failing version before anything is cleaned up, so the investigation is possible afterwards.
- 5
Then investigate and prevent. Root cause in daylight, with a blameless write-up and a concrete change so this class of failure is caught earlier.
What gets you hired
Roll back first, diagnose second. The deploy is the obvious suspect and reverting is the fastest path to a known-good state, so if it is behind a feature flag I turn the flag off, which takes seconds, otherwise I redeploy the previous artifact. I say what I am doing in the incident channel as I do it, so nobody else makes a conflicting change. Then I confirm with the error rate actually returning to baseline rather than assuming, because if errors persist after rollback my assumption was wrong and it may be a dependency, not my change. Before cleaning anything up I capture logs, traces and the failing image tag, since that evidence disappears. Only then do I investigate properly, and the output is a blameless write-up plus one concrete change, usually a test or an alert, that would have caught it before 4pm.
Then they probe: The rollback does not fix it. What does that tell you?
Practise this oneSecurity
6 questions · Junior, Mid, SeniorJuniorYour pipeline needs credentials to deploy to production. How do you handle that safely?
What most people say
“Store them as encrypted secrets in the CI system.”
It is the baseline, not the answer. It leaves a long-lived credential that is valid until someone rotates it, and says nothing about scoping, fork protection, or log leakage, which is where real breaches happen.
The structure behind a strong answer
- 1
Prefer no stored secret at all. Use the CI system's identity federation to exchange a signed token for short-lived cloud credentials, so nothing long-lived exists to steal.
- 2
If a secret must exist, scope it hard. A dedicated deploy identity with only the permissions that deploy needs, never a personal or admin credential.
- 3
Restrict where it can be used. Bind it to protected branches and environments so a pull request from a fork cannot access production credentials.
- 4
Prevent leakage in output. Mask secrets in logs, never echo them, and remember that anything passed as a build argument can persist in the image.
- 5
Rotate and audit. Automatic rotation and access logging, so an exposure has a bounded lifetime and a visible trail.
What gets you hired
The best answer is to not store a credential at all. Modern CI supports identity federation, so the pipeline presents a signed token proving which repository and branch it is, and the cloud exchanges it for credentials that expire in about 15 minutes. Nothing long-lived exists to leak. If that is not available, then a dedicated deploy identity scoped to exactly what deployment needs, never a personal token, and bound to protected branches and a protected environment so a pull request from a fork cannot reach production credentials. Then the hygiene: masked in logs, never passed as a build argument since those persist in image history, and rotated automatically. I would also want an audit trail of which run used which credential, because during an incident the first question is what had access and when.
Then they probe: Why is a fork pull request specifically dangerous?
Practise this oneMidYour vulnerability scanner reports 200 CVEs in a production image. How do you handle it?
What most people say
“Block the deploy until all 200 are fixed.”
It sounds rigorous and is unworkable. Many will have no available fix, some are unreachable in your usage, and a policy nobody can satisfy gets bypassed within a week, leaving you with no policy at all.
The structure behind a strong answer
- 1
Triage by severity and reachability. A critical CVE in a library your code actually calls matters far more than a high one in a package that is never loaded.
- 2
Check exploitability in context. A CVE requiring local access is different in risk from one exploitable over the network by an unauthenticated caller.
- 3
Fix the cheap majority first. Most of the count usually comes from a stale base image, so rebuilding on a current base removes a large fraction at once.
- 4
Set a policy the pipeline enforces. Fail the build on new critical findings, track existing ones with owners and deadlines rather than blocking everything on day one.
- 5
Reduce the surface. A slim or distroless base has far less installed, so there is simply less to be vulnerable.
What gets you hired
I triage rather than treating 200 as one number. First severity crossed with reachability: a critical in a library my code actually calls on a network path is urgent, a high in a package that ships in the base image but is never loaded is not. Then I look for the cheap win, which is usually the base image, since a base that is 8 months old typically accounts for most of the count and rebuilding on a current one can remove 150 of them in a single change. For what remains I check whether a fix exists at all, because some have no patch and need a compensating control or an accepted risk with an expiry date. Then the policy: the pipeline fails on new critical or high findings so the number cannot grow, while existing ones get owners and deadlines. Blocking everything on day one guarantees someone adds a bypass flag, and then the gate is decorative.
Then they probe: How do you decide whether a CVE is actually reachable?
Practise this oneMidHow would you rotate a database password used by 12 running services with no downtime?
What most people say
“Change the password in the database and update the secret, then restart all the services.”
That is an outage. The moment the password changes, every service with the old one starts failing, and the restart window is downtime for all 12 at once with no way to roll back cleanly.
The structure behind a strong answer
- 1
See why a single swap breaks. Changing the password instantly invalidates every existing credential, so all 12 services fail until each picks up the new one.
- 2
Create a second valid credential. Add a new user or a second password so old and new both work during the transition.
- 3
Roll services onto the new one. Update each service to the new credential, watching error rates as you go rather than doing all 12 at once.
- 4
Verify nothing uses the old one. Confirm through connection or audit logs that the old credential has zero usage before removing it.
- 5
Revoke and automate. Remove the old credential, then automate the whole cycle so rotation is routine rather than an event.
What gets you hired
The mistake is treating it as a swap. Changing the password in place invalidates every live credential at once, so all 12 services fail until they restart, which is a self-inflicted outage. Instead I make old and new valid simultaneously: either the database supports 2 active passwords, or more portably I create a second user with identical grants. Then I roll services onto the new credential one at a time, or in small batches, watching connection errors after each, so if something breaks I have changed one service and can revert it. Once all 12 are migrated I verify with connection or audit logs that the old credential has had zero use for a day, because there is always a forgotten cron job or a scaled-to-zero service that nobody remembers. Then revoke it. Then automate the cycle so this runs on a schedule, because the reason credentials sit unrotated for 3 years is that rotation is a scary manual event.
Then they probe: A batch job only runs weekly. How does that change the plan?
Practise this oneMidYour deploy role has full administrator access because narrowing it kept breaking things. How do you fix that properly?
What most people say
“I would remove the admin policy and add permissions back whenever a deploy fails.”
That is exactly the process that failed the first time. Learning permissions by breaking production deploys is slow, stressful and gets abandoned, which is how the admin grant became permanent in the first place.
The structure behind a strong answer
- 1
Understand why the shortcut happened. Guessing permissions and iterating on failures is painful, which is why people give up and grant admin.
- 2
Derive permissions from evidence. Read access logs or access advisor data to see exactly which API calls the role actually made.
- 3
Narrow in stages with a safety net. Apply the derived policy in a non-production account first and watch for denials before touching production.
- 4
Separate roles by job. Plan and apply differ, and read-only planning on pull requests needs far less than applying does.
- 5
Alert on denials rather than fearing them. A denial that pages you is recoverable, and it turns narrowing from a guessing game into a feedback loop.
What gets you hired
The reason it is admin is that narrowing by guesswork means breaking deploys until you find the missing permission, and nobody has the appetite for that twice. So I would derive it from evidence instead. Cloud providers record which API calls an identity actually made, so I take 90 days of that data and generate a policy from real usage rather than from imagination. That gets me most of the way in an afternoon rather than weeks. Then I narrow in stages: apply the derived policy in a development account first, run the full deploy path, and watch for denials in the logs. I would also split the role by job, because the plan step only needs read access and only apply needs write, and giving pull request builds a read-only role removes a large amount of risk immediately. Crucially I set up alerting on access denied events for that role, so instead of fearing a missing permission I find out in seconds with the exact call named. That turns it into a feedback loop rather than a gamble, which is what makes it finishable.
Then they probe: Ninety days of logs might not include an annual disaster recovery action. How do you handle that?
Practise this oneSeniorCompliance requires that every production change is approved and auditable. How do you keep deploying daily?
What most people say
“Add a change advisory board meeting before each production release.”
It satisfies a literal reading and destroys the delivery model, batching changes into weekly releases that are riskier. Research consistently finds heavyweight approval processes correlate with worse stability, not better.
The structure behind a strong answer
- 1
Read the control intent. The requirement is separation of duties and an audit trail, not necessarily a human ticket per deploy.
- 2
Pull request as the approval record. A reviewed, protected-branch merge is an approval by a second person with a permanent record.
- 3
Make the pipeline the evidence. Immutable logs of what was deployed, by whom, from which commit, with which tests passing.
- 4
Automate the policy checks. Encode the compliance requirements as pipeline gates so conformance is enforced rather than attested.
- 5
Bring the auditor in early. Agree the evidence format up front rather than proving it after the fact.
What gets you hired
I would separate the control intent from the ritual usually attached to it. The requirement is normally separation of duties plus an audit trail, and a reviewed pull request satisfies both: a second engineer approved the change, and git records who, what and when permanently, which is stronger evidence than a meeting minute. So the design is protected branches with required review, a pipeline that emits immutable evidence for each deploy, commit sha, approver, tests passed, scan results, artifact digest, and automated policy gates that enforce the actual rules, such as no deploy without a passing scan. That converts compliance from an attestation into an enforced property, which auditors generally prefer once you show them. The critical move is bringing the auditor in early and agreeing what evidence looks like, rather than building it and arguing afterwards. A change advisory board would take us from 10 deploys a day to 1 a week, and a release carrying 50 batched changes is far harder to debug than 1, so the literal reading actively harms the outcome the control exists to protect.
Then they probe: Emergency fix at 3am with nobody to review. What is the process?
Practise this oneSeniorHow would you protect your build pipeline from a supply chain attack?
What most people say
“We scan our images for vulnerabilities before deploying them.”
Scanning finds known vulnerabilities in what you built. It does nothing about a compromised build step, a malicious CI action, a dependency swapped after review, or an artifact tampered with between build and deploy, which are the actual supply chain attacks.
The structure behind a strong answer
- 1
Recognise the pipeline as production. It holds deploy credentials and executes arbitrary code, so it deserves production-grade controls rather than convenience defaults.
- 2
Pin every dependency by digest. Version tags are mutable. Digests and lockfiles make a swapped dependency a build failure rather than a silent compromise.
- 3
Control third-party actions and plugins. A CI action is code running with your credentials, so pin it by commit and allowlist which are permitted.
- 4
Sign artifacts and verify at deploy. Signing proves the artifact came from your pipeline, and verification at admission means an unsigned image cannot run.
- 5
Generate and keep an SBOM. A bill of materials is what lets you answer "are we affected" in minutes rather than days when a CVE lands.
What gets you hired
The reframe is that the pipeline is production: it has deploy credentials and runs third-party code, so it deserves the same controls. Concretely, in order of value. Pin everything by digest rather than tag, both base images and dependencies, because a mutable tag means the thing you reviewed and the thing you built are not necessarily the same. Treat CI actions as code: pin them to a commit sha rather than a version tag, and allowlist which third-party actions can run at all, since an action is arbitrary code holding your credentials. Sign artifacts at build and verify signatures at admission, so an image that did not come from my pipeline cannot run in the cluster even if someone pushes it to the registry. Generate an SBOM per build and store it, because when the next widely-used library has a critical CVE, the difference between answering in 10 minutes and 3 days is whether you already know what you shipped. And remove long-lived credentials in favour of short-lived federated ones, so a compromised run cannot be replayed later.
Then they probe: What specifically goes wrong with using a version tag for a CI action?
Practise this oneNetworking
3 questions · Junior, Mid, SeniorJuniorWhat does a load balancer do during a deployment, and how does it know an instance is ready?
What most people say
“It splits traffic between servers so no single one is overloaded.”
True but it answers a different question. The question is about deployment, and the interesting behaviour is health checking and connection draining, neither of which appears here.
The structure behind a strong answer
- 1
Distribute across healthy targets. It spreads requests over the pool and removes anything failing its health check.
- 2
Health checks gate membership. A target only receives traffic once it passes, which is what prevents sending requests to a still-booting instance.
- 3
Draining protects in-flight requests. On removal the balancer stops sending new requests but lets existing ones finish, instead of cutting them mid-response.
- 4
Connect it to dropped requests. Deploys drop traffic when instances are removed without draining, or added before they are genuinely ready.
What gets you hired
During normal running it spreads requests across healthy targets, but during a deploy the interesting parts are health checks and draining. A new instance only joins the pool once it passes its health check, which is why the check must reflect genuine readiness: if it returns 200 the moment the process starts but the app needs 15 more seconds to warm connections, the balancer sends real traffic into errors. When an old instance is removed it should go into draining, so no new requests arrive but in-flight ones get up to say 30 seconds to complete, instead of being cut mid-response. Most deploy-time request drops trace back to one of those two, either a health check that lies about readiness or a shutdown that does not drain. The app also has to handle the termination signal by refusing new work while finishing current work.
Then they probe: What does the application need to do on shutdown?
Practise this oneMidTrace what happens between a user typing your URL and your container returning a response.
What most people say
“DNS resolves the domain, the request hits the load balancer, and it forwards to the application.”
It is right and shallow. It leaves out TLS entirely, skips the ingress and service layers, and never mentions health checks, so it gives the interviewer nothing to probe and signals a thin model of the path.
The structure behind a strong answer
- 1
Name resolution. Browser and OS caches, then recursive resolution to an authoritative server, returning an address.
- 2
Connection and TLS. TCP handshake, then the TLS handshake with certificate validation and ALPN negotiation.
- 3
Edge and load balancing. CDN or edge proxy, then a load balancer choosing a healthy backend.
- 4
Cluster ingress and service routing. Ingress controller matches the route, the service selects a ready pod, and packets reach the container.
- 5
Application and back out. The app handles the request, hits its dependencies, and the response returns along the same path.
What gets you hired
The browser checks its own cache, then the OS, then asks a resolver, which walks from root to TLD to the authoritative server unless something is cached, returning an IP. Then a TCP handshake to that address, 3 packets, followed by TLS: client hello, certificate presented, chain validated against trust stores, key exchange, and ALPN negotiating HTTP/2. That address is usually a CDN or edge proxy, which may serve from cache and never reach me at all. Otherwise it forwards to a load balancer, which picks a backend that is passing health checks. In a cluster that lands on an ingress controller, which matches host and path rules to a service, and the service resolves to a ready pod through its endpoints, which is where readiness probes matter. Packets reach the container through the CNI, the app processes the request, probably calling a database and a cache, and the response returns back along the same chain. Roughly 8 layers, and the useful thing about knowing them is that debugging becomes bisection: identify which layer the failure sits in rather than guessing.
Then they probe: Where in that path would a 502 originate versus a 504?
Practise this oneSeniorA service starts refusing connections under load, but CPU and memory are fine. What limits would you check?
What most people say
“I would increase the number of replicas so the load is spread across more instances.”
If the limit is per-process, more replicas can help by accident, but if it is conntrack, a shared NAT gateway or a database pool ceiling, adding replicas increases pressure on the shared limit and makes it worse.
The structure behind a strong answer
- 1
File descriptor limits. Every socket is a descriptor, and the default per-process limit is often far lower than production needs.
- 2
Listen backlog and accept rate. A full accept queue drops or refuses connections while the process is otherwise healthy.
- 3
Ephemeral port exhaustion. On the client side, high connection churn exhausts the ephemeral range, especially with many sockets in TIME_WAIT.
- 4
Connection pool ceilings. Application and database pools have their own maximums that are hit long before host resources.
- 5
Conntrack and load balancer limits. Stateful firewalls and NAT track connections in a table with a hard maximum that silently drops packets when full.
What gets you hired
Refusing connections with healthy resources means a countable limit, not a capacity one. First file descriptors, since every socket is one and the default soft limit is frequently 1024, which a busy service passes easily, and the symptom is "too many open files" in logs. Then the listen backlog: if the app accepts connections slower than they arrive, the accept queue fills and the kernel starts refusing, which looks like the service being down while the process is fine. Then ephemeral ports on whichever side initiates: a service making thousands of short-lived outbound connections exhausts the ~28000 port range, especially with sockets sitting in TIME_WAIT for 60 seconds. Then application-level pool maximums, which are configured numbers people forget exist. Then the infrastructure ones that are invisible from inside the box: conntrack table limits on stateful firewalls, and NAT gateway or load balancer connection ceilings, both of which drop silently. I would check these in that order because it runs cheapest to hardest, and the first two cover most cases.
Then they probe: How would you spot conntrack exhaustion specifically?
Practise this oneDatabases
3 questions · Mid, SeniorMidYou need to rename a heavily-used database column with zero downtime. How?
What most people say
“Run ALTER TABLE RENAME COLUMN during a low-traffic window.”
It assumes a maintenance window exists and that a single atomic change is safe, but during any rolling deploy both versions of the application are live, so one of them breaks the moment the rename lands.
The structure behind a strong answer
- 1
Recognise why a rename is impossible atomically. During any rollout old and new code run together, and a rename breaks whichever version does not know the new name.
- 2
Expand: add the new column. Add the new column additively, deploy code that writes to both and reads the old one.
- 3
Backfill in batches. Copy existing rows in chunks to avoid locking a large table or saturating replication.
- 4
Switch reads. Deploy code that reads the new column while still writing both, and verify before going further.
- 5
Contract: stop writing, then drop. Remove writes to the old column, wait, then drop it in a later release once nothing references it.
What gets you hired
You cannot rename atomically, because during rollout both versions of the code are live and a rename breaks one of them. So I use expand and contract over several releases. Release 1 adds the new column, nullable, and deploys code that writes to both columns but still reads the old one, which is safe for both old and new pods. Then I backfill in batches, maybe 5000 rows at a time with a pause, so I do not lock a big table or flood replication. Release 2 switches reads to the new column while still writing both, so rollback is still trivial. Release 3, once I am confident, stops writing the old column. Release 4, after a safe interval, drops it. It is 4 deploys instead of 1, which feels slow, but every step is independently reversible and no step requires downtime or a window.
Then they probe: Why not just drop the old column in release 3?
Practise this oneSeniorYou scale a service from 10 to 100 pods and the database starts failing. Why, and how do you fix it?
What most people say
“Increase the database max connections setting to handle the new load.”
It treats a hard resource limit as a configuration inconvenience. Each connection consumes real memory and, in databases like Postgres, a process, so raising the limit to 2000 can exhaust database memory and cause a much worse failure than refused connections.
The structure behind a strong answer
- 1
Do the multiplication. Pods times pool size equals total connections, and 100 pods with a pool of 20 is 2000 against a database that may allow a few hundred.
- 2
Explain why connections are expensive. Each one costs memory and, in some databases, a backend process, so they are not free even when idle.
- 3
Introduce a pooler. An external connection pooler multiplexes many client connections onto few database ones, which is the standard fix.
- 4
Right-size the per-pod pool. Pools are usually configured far larger than a single pod needs, and shrinking them is the cheapest immediate fix.
- 5
Question the scaling decision. If the bottleneck is the database, adding application pods increases contention rather than throughput.
What gets you hired
The arithmetic is the answer: 100 pods with a pool of 20 each is 2000 connections, against a database typically configured for a few hundred. Connections are not free, since each consumes memory and in Postgres a backend process, so raising max_connections to 2000 trades refused connections for memory exhaustion, which is a worse failure. The right fix is a connection pooler in front of the database, in transaction pooling mode, which multiplexes thousands of client connections onto perhaps 50 real ones. That works because pods hold connections idle most of the time, so the real concurrency is far lower than the connection count. Alongside that I would right-size the per-pod pool, since 20 is usually inherited from a default and a pod serving modest concurrency may need 5. The deeper question is whether scaling to 100 pods was right at all: if the bottleneck was already the database, adding pods increases contention and total throughput can fall, so the honest answer might be fewer pods plus query optimisation rather than more connections.
Then they probe: What breaks in transaction pooling mode?
Practise this oneSeniorYou add a cache and stale data starts appearing. How do you think about invalidation?
What most people say
“I would lower the TTL so the data refreshes more often.”
It trades one problem for another without deciding anything: shorter TTL means more misses and more database load, and it still serves stale data for the whole window. It also does nothing about the actual cause, which is usually a write path that never invalidates.
The structure behind a strong answer
- 1
Decide the staleness budget first. How stale is acceptable for this data is a product question, and it determines the strategy.
- 2
Choose an invalidation strategy deliberately. TTL is simple and always stale for its window; explicit invalidation on write is fresher and easy to miss a path.
- 3
Find every write path. Missed invalidation is usually a write that bypasses the cache-aware code, like a batch job or an admin tool.
- 4
Design for the failure modes caching adds. Stampede on expiry, cold start after deploy, and inconsistency between replicas are new problems you did not have before.
- 5
Prefer safe patterns. Short TTL plus explicit invalidation, jittered expiry, and single-flight so one miss does not become a thousand queries.
What gets you hired
First I would ask what staleness is actually acceptable, because that is a product decision and it determines everything. A product catalogue tolerating 5 minutes and an account balance tolerating zero need different designs. Then the strategy: TTL alone is simple but guarantees staleness for the window, while explicit invalidation on write is fresher but fails whenever a write path forgets, which is the usual root cause of exactly this bug. So I would go looking for write paths that bypass the cache-aware code: batch jobs, admin tools, database migrations, and another service writing the same table are the classic four. My default is short TTL as a safety net plus explicit invalidation for freshness, so a missed invalidation is bounded rather than permanent. Then the failure modes caching introduces: stampede, where a popular key expires and 500 requests hit the database at once, fixed with jittered TTLs and single-flight so only one caller recomputes. And cold start after a deploy, where an empty cache means full database load at the worst moment.
Then they probe: What exactly is single-flight and why does it matter?
Practise this oneGitOps
1 question · MidMidWhat is GitOps, and how is it different from a pipeline that runs kubectl apply?
What most people say
“GitOps means you keep your Kubernetes YAML in git.”
Everyone keeps YAML in git already. The defining property is continuous reconciliation by an agent that pulls, and without that you have version control, not GitOps.
The structure behind a strong answer
- 1
Git is the desired state. A repository describes what should be running, and that is the single source of truth.
- 2
An in-cluster agent pulls. A controller inside the cluster continuously compares actual state to the repository and reconciles the difference.
- 3
Contrast with push. A pipeline running kubectl apply pushes once and then stops caring, so any later manual change persists silently.
- 4
Name the concrete wins. Drift is corrected automatically, CI needs no cluster credentials, and the repository is an accurate audit log of what is deployed.
- 5
Be honest about the costs. Another component to operate, and the feedback loop is less direct than watching a pipeline stage succeed.
What gets you hired
GitOps is continuous reconciliation, not just storing YAML in git. An agent inside the cluster watches a repository and constantly makes actual state match declared state. The difference from a pipeline running kubectl apply is what happens after: push applies once and stops, so if someone edits a deployment by hand at 2am, that change survives indefinitely and nothing notices. A reconciling agent detects the divergence within about a minute and puts it back. Three concrete wins follow. Drift self-heals. CI never needs cluster credentials, because the pipeline only writes to a repository and the agent pulls, which removes production credentials from a system that runs arbitrary code. And the repository becomes a truthful log of what is deployed, so "what is running in production" is a git query rather than an investigation. The costs are real: another controller to run, and a slightly less direct feedback loop for the developer waiting on a deploy.
Then they probe: How do you promote a change from staging to production in GitOps?
Practise this oneCost
1 question · MidMidCloud spend jumped 40% this month with no traffic increase. How do you find out why?
What most people say
“I would ask teams to review their resources and shut down anything unused.”
It delegates the investigation instead of doing it, and it will surface a few idle instances while missing the actual cause. A 40% jump usually has one specific driver that a bill diff finds in under an hour.
The structure behind a strong answer
- 1
Diff the bill by dimension. Compare month over month grouped by service, then by resource, to find where the delta actually sits.
- 2
Separate rate from usage. Did something get more expensive, or is there more of it? Those lead to completely different fixes.
- 3
Check the usual culprits. Orphaned resources, unattached volumes, a data-transfer pattern change, log volume, and forgotten non-production environments.
- 4
Correlate with change history. Line the spike up against deploys and infrastructure changes by date to find what coincided with it.
- 5
Prevent recurrence. Tagging so cost maps to owners, budget alerts on anomalies, and a scheduled sweep for orphans.
What gets you hired
I would find the specific line item rather than asking people to be careful. First a month-over-month diff grouped by service, which usually localises 40% to one or two services immediately, then drill to individual resources. The key question is rate versus usage: a bigger instance type is a rate change, 3 times the volume is usage. Common causes with flat traffic are an orphaned resource nobody deleted, unattached storage volumes left behind by a test, a change in data-transfer pattern such as traffic newly crossing availability zones or leaving through a NAT gateway instead of a VPC endpoint, and log or metric volume exploding after someone raised a log level to debug and never lowered it. Then I correlate the daily cost curve with the deploy and infrastructure change history, because the day the curve bent usually matches a change. Afterwards: enforced tagging so spend maps to an owner, anomaly-based budget alerts rather than monthly surprises, and a scheduled sweep for orphaned resources.
Then they probe: Data transfer is the top line. What specifically causes that?
Practise this oneBehavioural
14 questions · Mid, Senior, PrincipalMidTell me about a time you broke production. What happened and what did you do?
What most people say
“I have not really broken production, I am pretty careful with changes.”
It reads as either inexperience or dishonesty, and both are worse than the incident. Anyone who has operated production for a year has broken something, and the refusal to discuss it removes the chance to demonstrate ownership.
The structure behind a strong answer
- 1
Own it plainly in the first sentence. Say what you did without hedging or distributing blame. Ownership is most of the score.
- 2
Describe the immediate response. What you did to restore service, in what order, and how you communicated while doing it.
- 3
Give the honest impact. Duration and who was affected. Specific numbers read as truthful; vagueness reads as minimising.
- 4
Explain the root cause without excuses. What actually allowed it, including the system conditions that made the mistake easy to make.
- 5
End on the systemic change. The guardrail you added so nobody can repeat it, which is what separates a story from a lesson.
What gets you hired
I ran a database migration during business hours that I believed was non-blocking. It took a lock on a large table and the main API started timing out within about 90 seconds. I killed the migration immediately, which released the lock and recovered service in roughly 4 minutes, and I posted in the incident channel as I did it so nobody else started changing things. Total impact was about 6 minutes of elevated errors for roughly a third of requests. The root cause was that I tested on a staging table with 10000 rows and production had 40 million, so the lock behaviour was completely different. In the review I owned it directly, and the fixes were systemic rather than personal: migrations now run through a tool that refuses long-running locks, staging seeds are sized within an order of magnitude of production, and schema changes go through a review checklist. I would rather tell you about this one than a smaller one, because the guardrails came from it.
Then they probe: What did you say to the team afterwards?
Practise this oneMidTell me about a time you disagreed with a developer about how something should be deployed.
What most people say
“They wanted to deploy without tests so I told them that is not how we do things and blocked the pull request.”
It is a correct position delivered as authority rather than persuasion. It makes you the compliance department, and the developer will route around you next time rather than bring you in early.
The structure behind a strong answer
- 1
State both positions fairly. Describe their reasoning in a way they would recognise, which signals you actually listened.
- 2
Find the shared goal. Usually you both want the change shipped safely, and the disagreement is about method, not values.
- 3
Move to evidence. Replace opinion with data or a small experiment, so the decision has a basis other than seniority.
- 4
Say what you did when it stayed unresolved. Escalation, disagree and commit, or changing your own position, all with reasoning.
- 5
Report the outcome honestly. Including cases where they were right, which is far more credible than a story where you always win.
What gets you hired
A developer wanted to push a hotfix straight to production skipping the staging deploy, because a customer was blocked and staging took 20 minutes. My concern was that the change touched auth and an untested auth change is a much bigger blast radius than the original bug. I started by acknowledging the real cost, since 20 minutes with a customer waiting is genuine. Then I made it concrete rather than procedural: I asked what happens if this is wrong, and the answer was every user is locked out rather than one customer being blocked. That reframed it for both of us. We agreed on a middle path: deploy to staging but only run the auth smoke tests instead of the full suite, which took 4 minutes, and shipped behind a flag so we could disable it instantly. It went out in about 10 minutes total. Afterwards I took the 20 minute staging deploy as my problem to fix, because the process created the pressure to skip it.
Then they probe: What if they had gone ahead anyway?
Practise this oneMidTell me about a time you were on call and it went badly.
What most people say
“It was stressful but I stayed calm and eventually fixed it on my own.”
It answers a different question, the one about a success. It shows no reflection and quietly signals that you would rather struggle alone than escalate, which is exactly the trait that lengthens outages.
The structure behind a strong answer
- 1
Set the scene briefly. What broke and what made it hard, without a long technical preamble.
- 2
Be honest about where you struggled. The specific thing you got wrong or were slow on. This is the question, not a trap.
- 3
Show the escalation decision. When you called for help and why. Escalating early is a strength, not an admission.
- 4
Describe what you learned about yourself. A behavioural insight, not only a technical one.
- 5
Name what changed. Runbook, alert, training or rota change that made the next person luckier than you.
What gets you hired
I was paged for high latency on a service I had never touched, at 3am, 2 weeks into the rota. I spent about 40 minutes convinced it was the database because that was the pattern I knew, and I was wrong. The actual cause was a downstream service returning slowly and my service having no timeout, so threads piled up. What went badly was that I anchored on my first theory and did not escalate, partly because I did not want to wake someone. When I finally called the service owner at around 4am, he identified it in 5 minutes because he recognised the shape immediately. What I learned was about myself: I treated escalation as an admission of failure, when the actual cost was 40 extra minutes of user impact. Now I set myself a rule, if I have no confirmed hypothesis after 20 minutes on an unfamiliar service, I escalate. We also added a runbook for that service and a timeout, which was the real defect.
Then they probe: How do you decide when to escalate now?
Practise this oneMidTell me about a mistake that cost the company money.
What most people say
“I once left some instances running but finance spotted it on the bill and we shut them down.”
No number, no ownership, and the detection was someone else noticing a month later. It also shows no curiosity about why nothing alerted, which is the actual engineering lesson.
The structure behind a strong answer
- 1
State the number. Give the actual figure. Vagueness about money reads as evasion.
- 2
Explain how it went unnoticed. The interesting part is usually the missing feedback loop, not the original error.
- 3
Describe how it was caught. And be honest if it was luck or a bill rather than a system you built.
- 4
Own it without deflection. Even where a process gap contributed, lead with your part.
- 5
Close the loop systemically. The guardrail that makes the same error visible in hours instead of a month.
What gets you hired
I spun up a GPU cluster for a load test, and my cleanup script only removed the instances, not the attached storage or the load balancer. That cost about 2800 dollars over 5 weeks before anyone noticed, and what noticed was the monthly bill review, not any system I had built. I owned it directly in the team channel rather than waiting to be asked, because the awkward part is the delay, not the mistake. Root cause was two things: my teardown did not match my setup, and there was no cost anomaly alerting at all, so a 2800 dollar drift was invisible for over a month. The fixes were both systemic. I moved the test environment to Terraform so teardown is a destroy of the whole thing rather than a script I wrote by hand, and I added cost anomaly alerting on daily spend, which caught an unrelated 400 dollar mistake by someone else 6 weeks later. That second catch is the part I am actually pleased about.
Then they probe: How would you stop this class of thing generally?
Practise this oneMidWalk me through a time you inherited a system nobody understood. Where did you start?
What most people say
“It was such a mess that I proposed rewriting it properly from scratch.”
A rewrite of a system nobody understands cannot preserve the behaviour that matters, because the important parts are exactly the undocumented edge cases the original accreted. Most such rewrites finish late and reintroduce old bugs.
The structure behind a strong answer
- 1
Stabilise before improving. Get monitoring and a runbook in place so the system stops surprising you while you learn.
- 2
Learn from behaviour, not documentation. Read the logs, the metrics, the incident history and the git history, since those record what really happens.
- 3
Document as you go. Write down what you learn immediately, which is the artifact the next person needed and nobody produced.
- 4
Change small and observe. Small reversible changes teach you more about a system than reading it does.
- 5
Resist the rewrite instinct. A system you do not understand is one you cannot correctly replace, and rewrites drop undocumented behaviour that mattered.
What gets you hired
I inherited a billing job with no documentation and an author who had left. My first move was not to read the code but to make it observable, because the immediate risk was it failing silently. I added logging around each phase and an alert on completion time, which told me within a week that it normally ran 40 minutes and occasionally 3 hours, which nobody knew. Then I learned from history rather than documentation: git log showed which parts changed most and therefore where the pain was, and the incident channel search showed 4 past failures with the same signature. I wrote down everything I learned as I went, which became the runbook nobody had. Then small reversible changes: I added a dry-run mode so I could test without touching real billing data, which made every subsequent change safe. I deliberately did not propose a rewrite, because the 200 lines of edge-case handling that looked like mess turned out to encode 3 years of real customer situations. After 2 months I understood it well enough to refactor the slow phase, which took it to a consistent 25 minutes.
Then they probe: When is a rewrite genuinely the right call?
Practise this oneMidTell me about something repetitive you automated. How did you decide it was worth it?
What most people say
“I automated our release notes because doing them by hand was boring and I like scripting.”
Boring is not a business case, and automating for enjoyment is how teams end up with 15 fragile scripts nobody else can maintain. It also skips the more valuable question of whether the task needed doing at all.
The structure behind a strong answer
- 1
Quantify the current cost. Frequency times duration times people affected, plus the error rate when done manually.
- 2
Ask whether the task should exist. The best automation is often deletion: removing the need rather than speeding up the ritual.
- 3
Estimate the build and maintenance cost. Automation is code that needs owning, so include its ongoing burden honestly.
- 4
Count the benefits people forget. Consistency, auditability and removing a single point of knowledge often matter more than the time saved.
- 5
Measure the result. Confirm the saving was real rather than assuming it, since automation often shifts work rather than removing it.
What gets you hired
Our environment provisioning took about 45 minutes of manual clicking and 2 engineers did it maybe 8 times a month, so roughly 12 hours monthly, and about 1 in 5 came out subtly different, which caused debugging later that never got attributed back to the provisioning. So the real cost was higher than the 12 hours. Before automating I asked whether it needed doing at all, and part of it did not: 2 of the steps existed for a system we had decommissioned. Removing those was the cheapest win and took an hour. Then I automated the rest in Terraform, which took about 3 days including making it reliable enough to trust. The payback on pure time was under a month, but honestly the bigger wins were consistency, since every environment now matches, and removing the single point of knowledge, because previously only 2 of us could do it. Afterwards I checked the actual numbers rather than assuming, and provisioning went to about 6 minutes of waiting with no human steps.
Then they probe: When is automating actively the wrong call?
Practise this oneMidTell me about a time you did not know something important and had to act anyway.
What most people say
“I researched it thoroughly until I was confident, then made the decision.”
It dodges the question, which is specifically about acting without certainty. In operations you frequently cannot wait for confidence, and an answer that only describes the comfortable path suggests either inexperience or a tendency to freeze.
The structure behind a strong answer
- 1
Name the gap honestly. What you did not know and why it mattered for the decision at hand.
- 2
Describe how you bounded the risk. What you did to make being wrong survivable rather than catastrophic.
- 3
Show how you closed the gap fast. Who you asked, what you read, what you tested, in the time available.
- 4
Explain the decision you made. The reasoning under uncertainty, not a claim that you were certain.
- 5
Say what you learned. Both the technical knowledge and something about how you handle not knowing.
What gets you hired
A managed database was showing replication lag climbing during an incident and I had never dealt with that specific failure in that service. What I did not know was whether promoting the replica was safe or would lose data. Rather than guessing on a production database, I bounded the risk first: I took a manual snapshot, which took 2 minutes and made the worst case recoverable, and I stopped the write path feeding the lag so it could not get worse while I thought. Then I closed the gap fast in parallel: I read the vendor documentation on promotion semantics and messaged a colleague who had run this database at a previous company. Within about 10 minutes I knew promotion would be safe with a bounded loss window. I said explicitly in the channel that I was 80% confident, not certain, and what my rollback was, because I would rather be visibly uncertain than quietly wrong. It worked. What I learned beyond the technical detail was that stating a confidence level made everyone else more useful, since 2 people immediately offered relevant experience.
Then they probe: What if the snapshot had not been possible?
Practise this oneSeniorDescribe a time you had to push back on a deadline or a stakeholder request.
What most people say
“I told them it was not possible and that they needed to give us more time.”
It is a refusal with no alternative, which reads as inflexibility and gets overruled. Stakeholders can accept risk if they understand it, but they cannot act on "no" with no options attached.
The structure behind a strong answer
- 1
Understand the driver behind the date. A date usually has a reason, a customer commitment or an event, and knowing it changes what you can offer.
- 2
Make the risk concrete and quantified. Replace "it is risky" with a specific consequence and likelihood the stakeholder can weigh.
- 3
Bring options, not a refusal. Reduced scope, a phased release, or a flagged rollout, each with its own trade-off.
- 4
Let the decision sit where it belongs. Business risk decisions are theirs to make. Your job is that they make it informed.
- 5
Record the outcome. Write down what was decided and why, so it is a shared decision rather than a later argument.
What gets you hired
We were asked to ship a payments change for a launch 3 days out, and the load testing was not done. Rather than saying no, I asked what the date was tied to, which turned out to be a marketing campaign already booked, so the date was genuinely hard. Then I made the risk specific: without load testing, my honest estimate was a meaningful chance of degradation at campaign traffic, which is 5 times normal, and the failure mode is failed payments, not a slow page. That is a number a business person can weigh. Then I brought 3 options: ship the full scope on the date and accept the risk, ship behind a flag enabled for 10% of traffic and ramp during the campaign, or cut one non-essential part and load test the rest. They chose the flagged ramp, which I thought was the right call. I wrote the decision and reasoning in the channel so it was shared rather than mine. It shipped, we caught a connection-pool limit at 10% and fixed it before full ramp.
Then they probe: They pick the risky option against your advice. How do you behave?
Practise this oneSeniorTell me about a time you convinced a resistant team to adopt something new.
What most people say
“I demonstrated the benefits in a presentation and management made it a requirement.”
That is a mandate, not persuasion. Teams comply minimally with mandates and keep their old approach alongside, so the outcome is usually two systems and quiet resentment rather than adoption.
The structure behind a strong answer
- 1
Take the resistance seriously. Find out what they were actually protecting. Resistance usually encodes a real past cost.
- 2
Reduce the cost of trying. Make the first step small and reversible so agreeing is cheap.
- 3
Prove it on their problem. Demonstrate value on something they care about, not on a toy example you chose.
- 4
Let evidence and peers do the persuading. One team succeeding recruits the next far better than another presentation from you.
- 5
Report honestly. Including what you conceded or changed, since a story with no adaptation reads as revisionist.
What gets you hired
I wanted a team to move from hand-written Kubernetes manifests to a shared template, and they pushed back hard. Instead of arguing, I asked why, and the real reason was that a previous platform change had broken their deploys during a launch and nobody helped them fix it. That was a trust problem, not a technical one. So I made the first step tiny: I offered to migrate 1 non-critical service myself, with them reviewing, and committed that if anything broke I would be the one to fix it at any hour. That took 2 days. The template caught a missing resource limit they had not noticed, which was concrete value on their own service rather than my slide. They migrated the next 3 themselves over a month. I also changed the template based on their feedback, since they needed an override the original design refused, and that override turned out to be right for other teams too. The lesson was that the resistance contained real information.
Then they probe: What if you had found their objection was simply correct?
Practise this oneSeniorTell me about a conflict with a security or compliance team and how you resolved it.
What most people say
“Security wanted to slow everything down so I escalated to my manager to get an exception.”
It frames a partner as an adversary and resolves the disagreement by going around them. You win once and lose the relationship, and next time they will write stricter rules without consulting you.
The structure behind a strong answer
- 1
Separate intent from implementation. Ask what risk the control addresses. The requirement is usually sound even when the proposed mechanism is not.
- 2
Accept the risk is real. Conceding the legitimate concern immediately moves the conversation from positions to solutions.
- 3
Propose an alternative meeting the same intent. Show how automation can satisfy the control better than the manual process being asked for.
- 4
Provide evidence they can verify. Security teams need auditable proof, so make the pipeline produce it rather than asking for trust.
- 5
Build the ongoing relationship. Bring them in early on the next thing, so you are not meeting only in disputes.
What gets you hired
Security required a manual review before any production deploy, which would have taken us from about 10 deploys a day to 2 a week. Instead of arguing about speed, I asked what risk the review was actually catching, and the honest answer was unreviewed code and unscanned dependencies reaching production. Both are legitimate. So I proposed meeting the same intent automatically: required pull request review enforced by branch protection so nothing self-merges, dependency and image scanning as a hard gate, and a signed evidence trail per deploy recording commit, approver, scan results and artifact digest. Their concern was that they could not verify it, so I built them a dashboard showing every production deploy with its evidence, and we agreed a quarterly sample audit. That gave them stronger assurance than a human eyeballing a diff at 5pm, because it is enforced rather than attested. The bigger change was that I started bringing them in at design time, so we now write controls together instead of negotiating after I have built something.
Then they probe: What if they had insisted on the manual gate regardless?
Practise this oneSeniorTell me about a time you removed complexity rather than adding a feature.
What most people say
“I deleted a lot of old code and config that nobody was using anymore.”
No cost, no evidence it was unused, and no measured outcome. It sounds like tidying, and without proof of disuse it also sounds risky, since the interesting question is how you knew it was safe.
The structure behind a strong answer
- 1
Name the complexity and its cost. What it cost in maintenance, onboarding time, or incidents, so the case is concrete.
- 2
Explain why it existed. It usually solved a real problem once. Respecting that is what makes removal safe rather than reckless.
- 3
Prove it is no longer needed. Usage data or a period of disabling it, rather than an assumption.
- 4
Remove it safely and reversibly. Deprecate, disable, wait, then delete, so a mistake is recoverable.
- 5
Quantify what improved. Build time, incident rate, onboarding, or cost, so the value is not just aesthetic.
What gets you hired
We had a deployment system with 3 abstraction layers built over 4 years: a wrapper around Helm, a wrapper around that wrapper, and a set of per-team overrides. It was 6000 lines and every new engineer needed about 2 weeks to make their first deploy safely. Each layer had solved a real problem at the time, which is why nobody had touched it. I started with evidence rather than opinion: I instrumented which override paths were actually used and found that 4 of 19 accounted for nearly all usage, and 9 had not been used in over a year. So I built a thin replacement supporting the 4, migrated 2 friendly services, and ran both for 6 weeks. Then I removed the unused paths in stages, disabling before deleting so anything I got wrong was a config flip to restore. The result was about 800 lines instead of 6000, onboarding to first deploy went from 2 weeks to 2 days, and pipeline duration dropped by roughly 4 minutes. Nobody has asked for the removed features.
Then they probe: How did you know the unused paths were genuinely dead?
Practise this oneSeniorTell me about a time you stopped a release that other people wanted to ship.
What most people say
“I blocked it because the test coverage was below our standard and rules are rules.”
It appeals to a policy rather than a risk, which makes it easy to overrule and hard to respect. A gate defended by a number rather than a consequence teaches people that the gate is bureaucratic rather than protective.
The structure behind a strong answer
- 1
State the specific concern. A concrete risk with a named failure mode, not a general unease about quality.
- 2
Show you checked yourself first. Verified the concern was real before escalating it, since a false block costs credibility.
- 3
Describe how you raised it. Early, directly to the people affected, with the reasoning rather than the conclusion.
- 4
Offer a path forward. What would need to be true to ship, so it is a condition rather than a veto.
- 5
Report the outcome honestly. Including whether the concern proved justified, and what it cost in delay.
What gets you hired
A release was ready on a Friday afternoon and it included a change to how we wrote to the payments ledger. My concern was specific: the change altered write ordering, and I could not see a test covering the partial-failure case where the first write succeeded and the second did not. Before raising it I checked, because blocking on a false alarm costs credibility I would need later, and I confirmed there was genuinely no coverage for that path. I raised it directly with the engineer and the product owner together rather than commenting in a pull request, and I framed it as the consequence, that a partial write would leave a customer charged with no record, rather than as a coverage percentage. Then I made it a condition rather than a veto: I said I would be comfortable shipping Monday with a test for that path, or shipping now behind a flag disabled for real customers. They chose Monday. The test they wrote failed on the first run, which found the bug. It cost 3 days and I would do it again.
Then they probe: What if the test had passed and there was no bug?
Practise this oneSeniorDescribe a time you improved on-call for your team. What was actually broken?
What most people say
“I added more people to the rotation so each person was on call less often.”
It spreads the pain without reducing it, and it makes each person less practised because they see the system less often. If the underlying cause is 40 nightly pages, more people means more tired engineers rather than fewer incidents.
The structure behind a strong answer
- 1
Diagnose with data, not sentiment. Pages per shift, time of night, how many were actionable, and how many were the same recurring cause.
- 2
Separate noise from genuine load. A rota problem and an alerting problem need completely different fixes.
- 3
Fix the top recurring causes. Incident causes concentrate, so a small number of fixes usually removes most of the pages.
- 4
Make the rota humane. Enough people, compensation or time back, and a handover that actually transfers context.
- 5
Give responders what they need. Runbooks, access, and permission to escalate without it being seen as failure.
What gets you hired
I started with numbers rather than the general feeling that on-call was bad. Over 8 weeks we averaged about 11 pages a week, 6 of them between midnight and 6am, and when I categorised them, roughly 60% came from 3 recurring causes and about half were not actionable at all. That reframed it entirely: it was not a rota problem, it was an alerting and reliability problem. So first I deleted the non-actionable alerts, which took nightly pages down immediately and cost nothing. Then the 3 recurring causes: one was a disk filling weekly, fixed with rotation and an earlier warning threshold, one was a dependency without a timeout causing pile-ups, and one was a known memory leak we had been restarting manually. Fixing those 3 removed most of the remaining volume. Only then did I touch the rota, adding a proper handover and time back after a bad night. Pages went from about 11 a week to 2, and the important part is that people stopped dreading the pager, which showed up in how quickly they responded.
Then they probe: How do you keep it from drifting back?
Practise this onePrincipalTell me about a time you argued against your own proposal or changed your mind on a significant technical decision.
What most people say
“I usually think things through carefully first, so I have not really had to reverse a major decision.”
At principal level this reads as either not making consequential decisions or not tracking their outcomes. Every significant technical bet has a chance of being wrong, and the valuable skill is noticing early.
The structure behind a strong answer
- 1
State the original position and its reasoning. Show it was a considered position, not a whim, so the change means something.
- 2
Name what changed your mind. Specific evidence, ideally data or a pilot result rather than someone senior disagreeing.
- 3
Describe the cost of reversing. Sunk work, credibility, momentum, so the decision is shown to be genuinely hard.
- 4
Explain how you communicated it. Reversing publicly and clearly is what makes it safe for others to do the same.
- 5
Give the outcome. What happened, including whether the reversal was itself correct.
What gets you hired
I proposed and got approval for a service mesh, arguing for mTLS, traffic shifting and better observability. We had spent about 6 weeks on it with 2 engineers when I started tracking whether the benefits were landing. They were not, for us specifically: we had 12 services with modest traffic, our tracing gap was really a missing instrumentation problem the mesh did not solve, and the sidecar added latency and a genuinely hard debugging layer for a team with no prior experience of it. Meanwhile 2 of our 4 recent incidents were mesh-related, in a system it was supposed to make more reliable. I brought that to the team and argued against my own proposal, which was uncomfortable because it was publicly mine and 6 weeks were spent. We stopped and instead did direct OpenTelemetry instrumentation and cert management at the ingress, which got us most of what we wanted in about 3 weeks. The reason I did it that way, publicly and with data rather than quietly letting it fade, was that if I cannot reverse my own decision on evidence, nobody junior to me will feel able to reverse theirs.
Then they probe: How do you decide when to persist versus reverse?
Practise this oneLinux
2 questions · MidMidA Linux box is at 100% CPU. Walk me through identifying what is doing it and why.
What most people say
“I would find the process using the most CPU and kill it.”
It may be the process that matters, and killing it during a production incident can be worse than the CPU load. It also skips the diagnosis entirely, so the same thing recurs in 20 minutes with no more understanding than before.
The structure behind a strong answer
- 1
Identify the consumer. top or ps sorted by CPU tells you the process in seconds, which is the necessary first fact.
- 2
Read the CPU time breakdown. High user means application work, high system means syscall or kernel overhead, high iowait means blocked on disk, high steal means a noisy neighbour.
- 3
Interpret load average correctly. Load counts runnable and uninterruptible processes, so a high load with low CPU usually means IO blocking.
- 4
Narrow to a thread and a call. Per-thread view, then strace or a profiler to see what it is actually doing rather than guessing.
- 5
Decide with impact in mind. Kill, throttle with cgroups, or leave it and scale, depending on whether it is serving users.
What gets you hired
First identify the consumer with top sorted by CPU, which takes 5 seconds. But the more informative thing is the breakdown across user, system, iowait and steal, because each points somewhere different. High user time is the application genuinely computing. High system time means excessive syscalls or kernel work, often a context switching storm or something hammering the network stack. High iowait means the CPU is idle waiting for disk, so the real problem is storage rather than compute, and adding CPU would change nothing. High steal on a VM means the hypervisor is giving my cycles to a noisy neighbour, which is not my problem to fix locally. I would also read load average with that in mind, since Linux load counts uninterruptible processes too, so load of 40 with 10% CPU is an IO problem wearing a CPU costume. Then narrow: per-thread view to find the hot thread, then a profiler or strace to see what it is actually doing. Only then decide, and I would not kill a user-serving process without understanding it first.
Then they probe: iowait is 60%. Where do you go next?
Practise this oneMidWhat is the difference between SIGTERM and SIGKILL, and why does it matter for containers?
What most people say
“SIGTERM asks nicely and SIGKILL forces it. You use SIGKILL when SIGTERM does not work.”
It is technically correct and stops before everything that matters operationally: the grace period, what the application should do in it, and the reason a container often ignores SIGTERM entirely despite handling it correctly in code.
The structure behind a strong answer
- 1
SIGTERM is a request. It can be caught, so the process can finish work and clean up before exiting.
- 2
SIGKILL is not. The kernel terminates immediately, and the process gets no chance to finish anything.
- 3
Describe the orchestrator sequence. Kubernetes sends SIGTERM, waits the grace period, then SIGKILL. Everything depends on what happens in that window.
- 4
Explain what the app should do. Stop accepting new work, finish in-flight requests, close connections, then exit before the deadline.
- 5
Name the PID 1 trap. A process running as PID 1 does not get default signal handling, and shells often do not forward signals, so SIGTERM is silently ignored.
What gets you hired
SIGTERM is a polite request the process can catch, so it can finish what it is doing. SIGKILL cannot be caught and the kernel terminates immediately. In Kubernetes the sequence is: the pod is removed from the service endpoints, SIGTERM is sent, and after the termination grace period, 30 seconds by default, SIGKILL follows. What the app does in that window is the difference between a clean deploy and dropped requests: it should stop accepting new connections, finish in-flight requests, close database connections cleanly, and exit. If it exits instantly on SIGTERM, every in-flight request is cut. The trap that catches people is PID 1: a process running as PID 1 in a container does not get the kernel default handlers, so if it has no explicit SIGTERM handler the signal does nothing and every pod takes the full 30 seconds and then dies hard. And if the entrypoint is a shell script, the shell often does not forward the signal to the child at all, so the app never even sees it. Using exec in the entrypoint or a minimal init process fixes that.
Then they probe: Every pod takes exactly 30 seconds to terminate. What does that tell you?
Practise this oneSystem Design
5 questions · Senior, PrincipalSeniorDesign a CI/CD system for 15 microservices owned by 4 teams deploying several times a day.
What most people say
“Set up Jenkins with a job per service and a shared library.”
It names a tool and a folder structure but answers none of the design questions: how services stay independently deployable, how a security fix reaches 15 pipelines, or what happens when two services must ship together.
The structure behind a strong answer
- 1
Clarify constraints first. Deployment coupling between services, compliance requirements, and whether teams share a language and toolchain.
- 2
Standardise the pipeline as a shared template. A reusable workflow owned centrally so a security change lands everywhere at once, with limited per-service override.
- 3
Independent deployability is the goal. Each service deploys on its own cadence, which requires contract compatibility rather than coordinated releases.
- 4
Environment promotion by artifact. Build once per service, promote the same immutable artifact, with GitOps reconciling declared state per environment.
- 5
Guardrails not gates. Automated policy checks in the pipeline, so speed is preserved and the security team writes rules instead of approving tickets.
What gets you hired
I would start with constraints: are any of the 15 coupled such that they must deploy together, since that changes everything. Assuming mostly independent, the design is a shared pipeline template owned by a platform team, consumed by all 15 services, so a change like adding image signing lands everywhere in 1 pull request rather than 15. Each service builds once, tags by commit sha, and promotes that identical artifact through environments, with a GitOps agent reconciling per environment so deploys are commits rather than jobs holding cluster credentials. Independent deployability then depends on contract compatibility, so I would require backward-compatible API and schema changes and use consumer-driven contract tests to catch breakage before merge, because that is what actually lets 4 teams ship without a release train. Guardrails go in the template as automated policy, so the security team writes rules once instead of reviewing 40 deploys a week. And I would track DORA metrics per service to see which team is struggling rather than guessing.
Then they probe: Two services genuinely must deploy together. How do you handle it?
Practise this oneSeniorDesign an observability stack for a system where nobody can currently answer why a request was slow.
What most people say
“Install Prometheus, Grafana, and the ELK stack.”
It is a shopping list, not a design. It leaves out tracing, which is the only signal that answers the question asked, and says nothing about how an engineer moves from an alert to a root cause.
The structure behind a strong answer
- 1
Start from the question to be answered. Design backwards from "why was this specific request slow", not forwards from a list of tools.
- 2
Metrics for detection. Cheap aggregates that tell you something is wrong and roughly where, alerting on user-visible symptoms.
- 3
Traces for localisation. Distributed tracing shows where the time went across services, which is the specific gap in this scenario.
- 4
Logs for detail. Structured logs carrying the trace ID so you can jump from a slow span to the exact log lines for that request.
- 5
Control the cost. Sampling for traces, retention tiers for logs, and cardinality discipline for metrics, or the bill outruns the value.
What gets you hired
The stated failure is that nobody can explain a slow request, which is a tracing gap, so tracing is where I would start rather than more dashboards. I want every request to carry a trace ID from the edge, propagated across service boundaries, so a single slow request shows its span breakdown and I can see that 400ms of a 500ms request was one database call. Then metrics for detection, alerting on user-visible symptoms tied to an SLO, since traces are for investigating and metrics are for noticing. Then structured logs that include the trace ID, which is the piece teams usually miss: without it you have three systems and a manual correlation problem, and with it you go alert to trace to logs in about 3 clicks. I would standardise instrumentation through OpenTelemetry so the vendor choice stays reversible. Cost control matters: head sampling at a few percent with tail sampling to always keep errors and slow requests, plus retention tiers, because full-fidelity tracing at scale gets expensive quickly.
Then they probe: Sampling at 2% means you miss the slow request someone complains about. How do you handle that?
Practise this oneSeniorDesign secret management for 40 services across 3 environments, with an audit requirement.
What most people say
“Put everything in a vault and give each service a token to read what it needs.”
It solves storage and leaves the hardest problem untouched: how the service gets its token. A long-lived token in an environment variable is just a secret protecting secrets, and it recreates the original problem one layer down.
The structure behind a strong answer
- 1
Separate the four problems. Storage, distribution to workloads, rotation, and audit are distinct and need distinct answers.
- 2
Authenticate workloads by identity. Each service authenticates as itself using platform identity, so there is no bootstrap secret to protect.
- 3
Scope access per service and environment. Policies grant a service access only to its own secrets in its own environment, so a compromise is bounded.
- 4
Prefer dynamic short-lived credentials. Generated per use with a short lease, which makes rotation continuous rather than an event.
- 5
Make audit a property of the system. Every read logged with identity, secret and time, exported somewhere the platform team cannot silently edit.
What gets you hired
The hard part is not storage, it is the bootstrap: how does a service prove it is allowed to read anything. I would use platform workload identity, so a pod authenticates as its own service account and the secret store authorises that identity directly, which means there is no long-lived token anywhere and no chicken-and-egg problem. Policies are scoped per service and environment, so the payments service in staging can read only its own staging secrets, and a compromise of one service does not expose the other 39. Where the backend supports it I prefer dynamic credentials: the store generates a database user on demand with a 1 hour lease, so rotation becomes continuous rather than a scary quarterly event. For audit, every read is logged with identity, secret path and timestamp, shipped to a store the platform team cannot silently edit, which is what an auditor actually needs. Then the operational realities: caching so the store is not in the hot path of every request, and a documented break-glass procedure that is heavily alerted rather than pretending emergencies never happen.
Then they probe: The secret store is down. What happens to your 40 services?
Practise this oneSeniorDesign a system giving every pull request its own preview environment, for a team of 30.
What most people say
“Spin up a full copy of production infrastructure for each pull request and tear it down when it merges.”
A full production copy per PR for 30 engineers is both slow to create and extremely expensive, and it ignores the hardest question entirely: what data lives in it and how it arrives quickly enough to be useful.
The structure behind a strong answer
- 1
Define the boundary of an environment. How much is duplicated per PR versus shared, which is the main cost and complexity lever.
- 2
Solve the data problem explicitly. A per-PR database seeded quickly, or a shared one with namespaced data. This decides feasibility.
- 3
Automate the full lifecycle. Created on PR open, updated on push, destroyed on merge or close, plus a hard TTL for abandoned branches.
- 4
Control the cost. Scale to zero when idle, right-size aggressively, and treat a forgotten environment as a bug.
- 5
Make it useful. A URL posted on the PR, seeded with realistic data, so reviewers actually use it.
What gets you hired
The design lever is what is duplicated versus shared. I would give each PR its own namespace with the application services, and share the expensive stateful pieces where safe. Data is the deciding question: a full production-sized database per PR is unaffordable and slow, so I would use a small seeded dataset created from a template, ideally a snapshot restore or a copy-on-write clone that takes 30 seconds rather than a migration run that takes 10 minutes. Lifecycle fully automated: created on PR open, updated on each push, destroyed on merge or close, plus a hard TTL of about 3 days so abandoned branches cannot accumulate, since with 30 engineers the forgotten environments are what actually generates the bill. Cost control means scaling to zero when idle, because most preview environments are used for 20 minutes and exist for 2 days. Then usefulness: post the URL as a PR comment automatically, seed realistic data, and give it a way to send test traffic, otherwise it becomes an expensive thing nobody opens.
Then they probe: A PR needs to test a database migration. How does that work?
Practise this onePrincipalDesign multi-region failover for a system that currently runs in one region, targeting 15 minutes recovery.
What most people say
“Deploy the same infrastructure in a second region and use DNS failover to switch traffic when the first one is down.”
It describes the easy half. Duplicating stateless compute is straightforward, but it says nothing about the database, which is where the data loss, the split-brain risk and most of the recovery time actually live.
The structure behind a strong answer
- 1
Pin down RTO and RPO first. Recovery time and acceptable data loss are the requirements that determine every subsequent choice.
- 2
Start with the data layer. Stateless compute is easy to duplicate. Replication lag, write routing and failover of the datastore are the actual problem.
- 3
Choose a topology honestly. Active-passive is simpler and usually sufficient for 15 minutes; active-active buys seconds and costs enormous complexity.
- 4
Plan traffic steering. How traffic moves, with health checks and DNS or anycast, including how long client caching delays it.
- 5
Rehearse it. An untested failover does not work. Regular drills are the only thing that makes the number real.
What gets you hired
I would start by pinning the numbers, because 15 minutes recovery time with zero data loss and 15 minutes with 5 minutes of acceptable loss are completely different systems and costs. Then the data layer, since stateless compute is the easy part. For a 15 minute target, active-passive is almost certainly right: a warm standby region with asynchronous database replication, which means an RPO equal to replication lag, typically seconds. Active-active with synchronous writes buys a lower RTO and costs cross-region write latency on every request plus conflict resolution, and I would need a strong reason. So: infrastructure defined once as code and deployed to both regions, compute running warm at reduced capacity, database replicating asynchronously, and object storage replicated. Traffic steering by health-checked DNS, accepting that client caching adds minutes, which is why 15 rather than 2. The critical piece people skip is promotion: promoting a replica is the risky step, and it needs to be automated and rehearsed, with fencing so the old primary cannot accept writes and cause split brain. Then quarterly game days, because an untested failover is a hope.
Then they probe: How do you prevent split brain during promotion?
Practise this oneReliability
2 questions · SeniorSeniorYou are incident commander for a total outage. It is 2am, 6 engineers are online, and nobody knows the cause. What do you do?
What most people say
“Get everyone looking at logs and dashboards to find the root cause as fast as possible.”
Six people investigating in parallel with no coordination is the classic failure: duplicated work, conflicting changes applied simultaneously, nobody talking to stakeholders, and no record of what was tried.
The structure behind a strong answer
- 1
Establish roles explicitly. Commander coordinates, one person communicates externally, others investigate. Without this everyone debugs and nobody leads.
- 2
Mitigate before diagnosing. Restore service by any available means, rollback, failover, traffic shed, before understanding the cause.
- 3
Serialise changes. One change at a time, announced, so you can attribute effects. Parallel uncoordinated fixes make the system unreadable.
- 4
Maintain a timeline. A running log of what was observed and done, which is both coordination now and the postmortem later.
- 5
Manage humans. Communicate on a cadence even with no news, and rotate people out before fatigue causes a second incident.
What gets you hired
My job as commander is coordination, not debugging, and the first mistake to avoid is joining the investigation myself. I state roles out loud immediately: I am commanding, one person owns communications, and I assign specific investigation areas so 6 people are not all reading the same dashboard. Then mitigation before diagnosis: is there a recent deploy to roll back, can we fail over, can we shed traffic to restore partial service. Understanding why can wait until users are served. I enforce one change at a time, announced in the channel before it happens, because 3 simultaneous fixes make it impossible to know what helped or hurt. I keep a timeline as we go, with timestamps, which coordinates the room now and writes most of the postmortem later. I communicate on a fixed cadence, every 30 minutes even with nothing new, because silence makes stakeholders escalate and interrupt the responders. And at 2am I plan the handover early, since tired engineers cause the second incident.
Then they probe: An executive joins the call and starts directing engineers. How do you handle it?
Practise this oneSeniorDesign autoscaling for a service with a sharp traffic spike every day at 9am.
What most people say
“Set up horizontal pod autoscaling on CPU with a target of 70% and let it handle the spike.”
Reactive CPU-based scaling is guaranteed to be late for a sharp spike: by the time CPU rises, the metric is scraped, a decision is made and pods are scheduled, the spike has already caused errors. It also assumes CPU tracks demand, which for IO-bound services it does not.
The structure behind a strong answer
- 1
Measure the full scale-up latency. Metric delay plus decision interval plus node provisioning plus image pull plus application warm-up, which is often minutes.
- 2
Compare it to the spike shape. If demand goes up in 60 seconds and capacity takes 4 minutes, reactive scaling cannot succeed by definition.
- 3
Use scheduled scaling for known patterns. A predictable 9am spike should be pre-warmed on a schedule, since it is known in advance.
- 4
Pick the right scaling signal. CPU is often a poor proxy. Queue depth, concurrent requests, or requests per replica usually track demand more directly.
- 5
Protect against the failure case. Load shedding and queueing so a spike degrades gracefully rather than collapsing while capacity arrives.
What gets you hired
The key number is how long capacity actually takes to arrive, end to end. That is metric scrape delay, plus the controller decision interval, plus scheduling, plus possibly a node coming up, plus image pull, plus application warm-up. Realistically that is 2 to 5 minutes, and if traffic triples in 60 seconds, no reactive system can win: it is arithmetic, not tuning. So for a known 9am spike I would use scheduled scaling to pre-warm capacity at 8:45, because the pattern is predictable and pre-warming is far cheaper than the outage. Reactive autoscaling stays on top of that for the unpredictable component. I would also choose the signal carefully: CPU is a poor proxy for an IO-bound service, and requests per replica or queue depth track demand much more directly. Then defence in depth for the case where I am wrong: a queue or load shedding so excess requests degrade gracefully rather than collapsing every request, and keeping some headroom rather than running at 90% utilisation, since utilisation and latency are not linearly related near saturation.
Then they probe: What dominates that 2 to 5 minutes in practice?
Practise this oneCloud fundamentals
1 question · SeniorSeniorWhen would you run a database yourself on VMs instead of using the managed service?
What most people say
“Self-managing is cheaper, so at scale you should always run your own database.”
It compares instance prices and ignores the expensive part. The cost of self-managing is a person who can restore a corrupted database under pressure at 3am, plus upgrade projects and backup verification, and that routinely exceeds the price difference.
The structure behind a strong answer
- 1
Name what managed actually buys. Backups, patching, failover, monitoring and 24/7 expertise, which is a whole role you do not have to hire.
- 2
Name what it takes away. Version choice, extension availability, low-level tuning, filesystem access and sometimes replication topology.
- 3
Count the true cost of self-managing. Not the instance bill but the on-call, the upgrade projects, and the expertise required to restore under pressure.
- 4
List the genuine exceptions. An unsupported extension or version, extreme performance tuning, regulatory or residency constraints, or scale where the price difference is enormous.
- 5
Test the recovery claim. The real question is whether you can restore to a point in time at 3am, and managed services usually win that decisively.
What gets you hired
My default is managed, and the reason is not laziness, it is that the managed service is buying an operational function rather than a server. Automated backups with point-in-time recovery, patching, failover, and someone else awake at 3am when a disk fails. Self-managing means my team owns all of that, including the part everyone underestimates, which is being able to actually restore under pressure, because an untested backup is a hope. So the price comparison people make is wrong: it is not instance cost versus managed cost, it is instance cost plus roughly a portion of an engineer permanently. That said, there are real exceptions. An extension or a version the managed service does not support, which is common with Postgres extensions. Performance requirements needing filesystem or kernel tuning the service does not expose. Regulatory constraints requiring specific residency or control. Or scale where the managed premium becomes millions rather than thousands, at which point hiring dedicated database engineers is genuinely cheaper. I would ask for one of those specific reasons, because "we can run it ourselves" is true and not sufficient.
Then they probe: How would you verify backups are actually usable?
Practise this oneStrategy
4 questions · PrincipalPrincipalYou join a 200-engineer company where every team built its own deployment tooling. What is your strategy?
What most people say
“Pick the best tooling, standardise on it, and require all teams to migrate by a set date.”
Mandate without adoption is how platform teams fail. Teams that were never asked will comply minimally, keep shadow tooling for what the standard does not cover, and the platform becomes a tax rather than a service.
The structure behind a strong answer
- 1
Understand before standardising. Find out why teams diverged. Usually the central option was slow or missing, and that cause will defeat any new standard too.
- 2
Find the common 80%. Most teams need the same handful of things. Standardise those and leave genuine differences alone.
- 3
Build a paved road, not a wall. Make the supported path the easiest path so adoption is pull, not push.
- 4
Prove it with willing teams first. Two or three volunteers, measurable improvement, then let their result do the persuading.
- 5
Migrate with help and a deadline. Do the migration work for teams where possible, then set a date for decommissioning the alternatives.
What gets you hired
I would resist announcing a standard in my first month, because the interesting question is why 200 engineers each built their own. Usually the answer is that the central path did not exist or was slower than doing it themselves, and if I do not fix that cause, my new standard becomes the eleventh tool. So first, discovery: what do teams actually run, where do they lose time, what does deploying feel like. Then find the common 80%, which is usually build, test, artifact, deploy, rollback, and standardise exactly that while leaving legitimate differences alone. Then a paved road that is genuinely faster than rolling your own, proven with 2 or 3 volunteer teams and measured, so I can say lead time went from 4 days to 1 rather than arguing about elegance. That result recruits the next teams. Only once adoption is voluntary and real do I set a decommission date, and I resource the migration rather than sending a deadline email. I would expect this to take 4 to 6 quarters, and I would say that up front rather than promise a quarter and lose credibility.
Then they probe: A senior team refuses and has good reasons. What now?
Practise this onePrincipalWhen would you tell a company not to use Kubernetes?
What most people say
“Kubernetes is the industry standard, so almost every company should use it eventually.”
It defers to popularity instead of reasoning about cost and benefit, and it is the mindset that puts a 3-service startup on a self-managed cluster where one engineer ends up permanently maintaining infrastructure instead of building product.
The structure behind a strong answer
- 1
Weigh the true cost. Kubernetes brings a permanent operational burden: upgrades, networking, security posture, and a steep learning curve for every engineer.
- 2
Check whether the benefits apply. It pays off with many services, real scaling needs, and multiple teams needing self-service. Few workloads means little return.
- 3
Name the simpler options. Managed container runtimes and platform services deliver most of the benefit with a fraction of the operational surface.
- 4
Consider the team, not just the workload. A team of 5 without platform experience will spend more time on the cluster than on the product.
- 5
Keep the decision reversible. Containerising is the durable investment. The orchestrator can change later at moderate cost.
What gets you hired
I would advise against it more often than people expect. Kubernetes is a permanent operational commitment: cluster upgrades, networking, RBAC, admission policy, and enough shared understanding that the whole team can debug it at 3am. That cost is worth paying when you have many services, real elasticity requirements, and several teams needing self-service isolation. A company with 3 services, predictable traffic, and 6 engineers gets almost none of that return and pays the full price, and typically one engineer quietly becomes the cluster person and stops shipping product. For them a managed container service or an application platform gives containers, autoscaling, and rolling deploys with a tiny fraction of the surface. The reason I am comfortable saying this is that the durable investment is containerising and having clean infrastructure as code, and that work transfers. Moving to Kubernetes in 2 years when the shape of the problem justifies it is a manageable migration. Adopting it early and drowning in it is not easily undone.
Then they probe: What signals tell you a company has grown into needing it?
Practise this onePrincipalThe business wants faster shipping, your on-call team is burning out from incidents. How do you resolve that?
What most people say
“Explain to the business that we need a feature freeze to focus on stability for a quarter.”
It concedes the false choice and spends political capital on a pause that does not fix the causes. Work resumes at the same rate afterwards, the underlying fragility is unchanged, and the team learns that stability requires stopping.
The structure behind a strong answer
- 1
Reject the false trade-off. High performers are faster and more stable together, because the same practices produce both.
- 2
Get the actual data. Change failure rate, incident causes, and where on-call time is really going, so the argument is about numbers not feelings.
- 3
Introduce an error budget. An agreed reliability target with an explicit consequence when it is exceeded, so the decision is made in advance rather than in an argument.
- 4
Attack the top causes. Incident causes usually concentrate, so fixing the top 2 recovers most of the on-call load.
- 5
Protect the humans now. Fix the rota, remove non-actionable pages and give recovery time, because a burnt-out team causes more incidents.
What gets you hired
I would refuse the framing, because the data on this is consistent: teams that deploy more often also restore faster and fail less, since small batches and good automation produce both. So the real question is what specifically is causing incidents. I would get numbers first: change failure rate, incident causes grouped, and how on-call time actually splits between real incidents and noise. Usually 2 causes produce most of the pain, and one of them is alert noise rather than genuine outages. Then I would propose an error budget: agree a target with the business, say 99.9%, and agree in advance that if we exceed the budget, work shifts to reliability until it recovers. That converts a recurring argument into a rule everyone signed, and crucially it also means that while we are inside budget the business gets to ship fast without me objecting, which is what makes it acceptable to them. Meanwhile I fix on-call immediately: delete non-actionable pages, size the rota properly, and give recovery time, because a tired team causes the next incident.
Then they probe: The business agrees the budget then wants to override it during a launch. What do you do?
Practise this onePrincipalYour team wants to build an internal developer platform. A vendor sells one. How do you decide?
What most people say
“Build it, because we understand our needs better than any vendor could.”
It is the answer engineers want and it consistently underestimates the multi-year maintenance cost. The internal platform gets built by 3 enthusiastic people, 2 of whom leave, and it becomes an unowned dependency nobody can modify.
The structure behind a strong answer
- 1
Ask whether it differentiates. Build what makes your company distinctive, buy the rest. Nobody wins customers with a superior internal deploy button.
- 2
Cost the build honestly. Not just initial engineering but ongoing maintenance, on-call, and the opportunity cost of the product work not done.
- 3
Name the bias in the room. Engineers systematically prefer building and underestimate maintenance, so account for that explicitly.
- 4
Evaluate lock-in and exit. How hard would leaving the vendor be, and does an open standard underneath keep the exit affordable.
- 5
Consider the hybrid. Buy the commodity layers, build the thin integration that fits your context, which is usually where the real value is.
What gets you hired
The first question is whether this differentiates us. Customers do not choose us because of our deploy tooling, so it is a cost centre, and the default for a cost centre is buy. Then I cost the build honestly, which people rarely do: it is not the 6 months to version one, it is 2 engineers permanently maintaining it, being on call for it, and the product work those engineers did not do, which over 3 years usually exceeds a vendor bill by a wide margin. I would also name the bias out loud, that engineers including me enjoy building platforms and systematically underestimate maintenance, so the estimate deserves scepticism. Against that, I check lock-in: how hard is exit, and can we keep an open standard underneath so migration stays possible. Usually the honest answer is hybrid: buy the commodity layers, build the thin integration that encodes our specific context, because that is where our knowledge actually matters and it is small enough to maintain. I would trial the vendor on 2 teams with defined success criteria before committing.
Then they probe: The vendor lacks one feature the team considers essential. How do you weigh that?
Practise this oneHave a devops 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.
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.