Interview prep · 150 questions

SDET interview questions

Architecture, system design for test, and the coding round. These are the questions that decide whether you are hired as an SDET or as an automation engineer with a different title.

50 questions

What's the difference between a QA engineer and an SDET?

Role & Scopejuniormid

The honest answer is that titles vary wildly between companies, so I'd define it by what the job actually demands.

The honest answer is that titles vary wildly between companies, so I'd define it by what the job actually demands.

A QA engineer is primarily accountable for finding defects and assessing quality. The core skills are test design, risk assessment, and product judgement. Automation may be part of the role, usually at the level of consuming a framework someone else built.

An SDET is a software engineer whose product is quality infrastructure. You build the frameworks, the test harnesses, the CI pipelines, the test-data systems and the tooling that other engineers use. You're expected to pass a real coding interview, review production code, and design systems. You also, crucially, still need the QA judgement — knowing what to test is worthless if you can only build, and being able to build is worthless if you test the wrong things.

The distinction that matters in practice: an SDET is measured partly on the leverage they create. Writing 200 tests is QA work. Building the framework and CI pipeline that lets 20 developers write and trust their own tests is SDET work, and it scales far further.

My view is that the best SDETs are the ones who came from testing. Plenty of engineers can build a harness; far fewer know which failure modes are worth harnessing against.

Key points
  • QA: accountable for finding defects — test design, risk, product judgement
  • SDET: software engineer whose product is quality infrastructure (frameworks, harnesses, CI, test data)
  • SDET is expected to pass a real coding interview and review production code
  • The differentiator is leverage: building what lets 20 devs test themselves beats writing 200 tests yourself
  • The best SDETs keep the QA judgement — building the wrong harness well is still failure
They'll ask next
  • Which part of that do you enjoy more, and why?
  • Should developers write their own tests? Then what's your job?
Link to this question

Explain the test pyramid. What happens when it's inverted?

Test Architecturejuniormidsenior

The pyramid describes the proportions of tests at each layer: a wide base of fast unit tests, a narrower middle of integration/API tests, and a thin top of end-to-end UI tests.

The pyramid describes the proportions of tests at each layer: a wide base of fast unit tests, a narrower middle of integration/API tests, and a thin top of end-to-end UI tests.

The shape is driven by economics, not aesthetics. As you climb, tests get slower, more brittle, more expensive to maintain, and dramatically worse at pinpointing the cause of a failure. A unit test fails and names a function. A UI test fails and could be the front end, the API, the database, the environment, the network, or the test itself.

The inverted pyramid — the 'ice cream cone' — is mostly UI tests with almost no unit coverage. It's what you get when testing is bolted on afterwards by a separate team, because the only interface they own is the UI. The symptoms are always the same: a suite that takes hours, high flakiness, terrifying maintenance costs, and feedback so slow it arrives after the developer has moved on. Eventually people stop trusting it, and stop reading the results.

The nuance I'd add, because interviewers listen for it: the pyramid is a heuristic, not a law. For a thin front end over rich services, the middle layer might reasonably be the widest. The durable principle isn't the shape — it's catch each bug at the cheapest layer that can catch it.

Key points
  • Wide base of unit tests, narrower integration/API, thin layer of E2E UI
  • Rationale is economic: higher layers are slower, flakier, costlier, and worse at localising failures
  • Inverted 'ice cream cone' = mostly UI tests: slow, flaky, expensive, untrusted
  • It happens when testing is bolted on by a separate team who only own the UI
  • It's a heuristic, not a law — the real principle is: catch each bug at the cheapest layer that can
They'll ask next
  • Your company has an inverted pyramid. What's your 6-month plan?
  • When is a diamond or trophy shape more appropriate than a pyramid?
Link to this question

Design a test automation framework from scratch. Walk me through your architecture.

Test Architecturesenior

I'd start by asking questions, because a framework designed without constraints is a guess: what's the stack, what's the release cadence, how many engineers will use this, is there an API, what's the…

I'd start by asking questions, because a framework designed without constraints is a guess: what's the stack, what's the release cadence, how many engineers will use this, is there an API, what's the current pain.

Then I'd design in layers, each with a single responsibility.

Core/runtime — driver and browser lifecycle, context management, base fixtures. Nothing product-specific.

Clients — an API client layer and, if needed, a DB client. This is deliberately first-class, because state should be created via API wherever possible, not by clicking through the UI.

Pages/components — locators and interactions only. No assertions.

Test data — factories producing unique entities per test, so parallelism is safe by construction rather than by luck.

Config — environment resolved from environment variables, never from code edits. One suite runs against local, staging and CI unchanged.

Tests — scenarios and assertions, and nothing else.

Reporting — failure hooks that automatically attach screenshot, DOM, console logs, failed network calls and traces.

CI integration — tiered triggers: fast API smoke on every PR, sharded regression on merge, full matrix nightly.

The two decisions I'd defend hardest: test independence (every test creates its own state) and layer discipline (a locator in a test file is a bug). Everything else is negotiable; those two are what determine whether this suite survives two years.

Key points
  • Ask about constraints first — cadence, stack, team size, existing pain
  • Layers: core/runtime → API & DB clients → pages → test data factories → config → tests → reporting → CI
  • API client is first-class: create state via API, not by clicking through the UI
  • Config from environment variables so one suite runs everywhere unchanged
  • The two non-negotiables: test independence, and layer discipline
They'll ask next
  • What breaks first when this grows to 2,000 tests?
  • How does test data work when 30 tests run in parallel?
  • Why that structure and not a simpler one?
The trap

Jumping straight into folder names. The interviewer is scoring whether you ask about constraints and can justify each layer — not whether you can recite a directory tree.

Link to this question

How would you design a test strategy for a microservices architecture?

System Design for Testsenior

The defining constraint of microservices is that end-to-end testing does not scale. With thirty services deployed independently, spinning up the whole system for every change is slow, fragile, and…

The defining constraint of microservices is that end-to-end testing does not scale. With thirty services deployed independently, spinning up the whole system for every change is slow, fragile, and produces failures nobody can attribute. So the strategy has to push verification down and out.

Per service: unit tests owned by the developers, plus component tests that exercise the service in isolation with its dependencies stubbed. This is where the bulk of coverage lives.

Between services: contract testing. Each consumer declares what it expects from a provider; the provider verifies it can satisfy every consumer's contract in its own pipeline. This is the key move — it catches breaking changes without requiring both services to be deployed together, which is exactly the problem end-to-end testing has.

Integration: a small number of tests against real dependencies for the paths where the interaction itself is the risk — a payment flow crossing four services, say.

End-to-end: a deliberately tiny set. Only the critical business journeys, run against a staging environment. Treat every one of these as expensive, because it is.

Production: synthetic monitoring, health checks, and observability, because at this scale you accept that some issues will only surface in production and design to detect them in minutes.

The cultural half matters as much: each team owns the quality of its own service. A central QA team that tries to test thirty services becomes the bottleneck, and then the scapegoat.

Key points
  • E2E doesn't scale with independent deploys — push verification down and out
  • Per service: unit + component tests with stubbed dependencies, owned by the dev team
  • Between services: contract testing — catches breaking changes without co-deploying
  • Tiny, deliberate E2E set for critical journeys only; treat each as expensive
  • Production: synthetic monitoring and observability — accept that some issues surface only there
  • Culturally: each team owns its service's quality, or QA becomes the bottleneck and the scapegoat
They'll ask next
  • How does contract testing actually catch a breaking change?
  • Who owns the end-to-end tests in that model?
Link to this question

Design a test data management system.

System Design for Testsenior

I'd start from the failure modes it must prevent, because those define the requirements: tests colliding on shared data, tests that only pass in a specific order, data that expires or drifts, and the…

I'd start from the failure modes it must prevent, because those define the requirements: tests colliding on shared data, tests that only pass in a specific order, data that expires or drifts, and the nightmare of production data in a test environment.

Requirements: on-demand data creation; guaranteed uniqueness so parallel runs can't collide; realistic shape; fast; self-cleaning; and no personal data from production, ever.

Design. The core is a factory layer — code that builds domain entities (createUser(tier='gold', withOrders=3)) with sensible defaults and explicit overrides, so a test states only what it cares about. Uniqueness comes from UUID or run-id suffixes baked into the factory, not left to each engineer to remember.

Creation goes through the API where one exists, because that keeps you honest against the real contract and doesn't couple you to the schema. A direct-DB path exists as a fallback for states the API can't produce.

For isolation I'd choose the strongest option the architecture permits, in order of preference: a per-test tenant or namespace; a transactional rollback; or, failing both, unique-data-plus-cleanup.

Cleanup must be resilient: tag everything with a run ID and reap by tag on a schedule, because tests that crash mid-run never execute their teardown. Relying on happy-path cleanup is how environments fill with junk.

And for production-like data: synthesise or anonymise. Copying production is a data-protection incident waiting to happen.

Key points
  • Derive requirements from failure modes: collisions, order-dependence, drift, production data leakage
  • Factory layer with sensible defaults and overrides; uniqueness (UUID/run-id) built in, not left to engineers
  • Create via API where possible; direct DB as a fallback for states the API can't reach
  • Isolation, best first: per-test tenant/namespace → transactional rollback → unique data + cleanup
  • Reap by run-ID tag on a schedule — crashed tests never run their teardown
  • Never copy production data — synthesise or anonymise
They'll ask next
  • How do you handle data that takes days to reach the state you need (a 90-day dormant account)?
  • What's your approach when the API can't create the state but the DB can?
Link to this question

Design a system to run 10,000 tests in under 10 minutes.

System Design for Testsenior

Sequentially this is impossible, so the entire answer is about parallelism — and the prerequisite for parallelism is isolation, not infrastructure.

Sequentially this is impossible, so the entire answer is about parallelism — and the prerequisite for parallelism is isolation, not infrastructure.

First, reshape what's being run. Ten thousand UI tests is the wrong problem to solve; I'd ask why. If most of them verify business logic, push them to the API or unit layer where they run in milliseconds. Solving the wrong distribution with more machines is expensive stupidity.

Then make tests parallel-safe, which is the actual hard part: no shared mutable state, unique data per test, no order dependence, idempotent setup. Without this, more workers just means more collisions.

Then shard. Split the suite across N workers. Naive sharding by file is uneven — one shard finishes in a minute, another takes twelve, and your wall-clock is the slowest shard. So shard by historical duration, balancing total runtime per worker. That single change often halves wall-clock time.

Then scale the infrastructure: containerised runners, a browser grid in Kubernetes or a cloud device farm, scaled elastically so 200 workers exist for ten minutes and then don't.

Then trim the fat: reuse authenticated sessions via API rather than logging in through the UI 10,000 times, seed state via API, disable animations, run headless.

Finally, fail fast: run the highest-value and historically-flakiest tests first so a broken build is known in ninety seconds, not nine minutes.

Key points
  • Sequential is impossible — but parallelism requires isolation first, infrastructure second
  • Challenge the shape: 10,000 UI tests is usually the wrong distribution — push logic down to API/unit
  • Parallel-safety: no shared state, unique data, no order dependence
  • Shard by historical duration, not by file — wall-clock is set by the slowest shard
  • Elastic containerised runners; API-based auth and state setup instead of UI clicks
  • Run highest-value and flakiest tests first to fail fast
They'll ask next
  • How do you handle a test that genuinely can't run in parallel?
  • What's your cost ceiling, and how would you optimise against it?
Link to this question

Design a test reporting and analytics dashboard. What would you track?

System Design for Testsenior

The design question I'd ask first is who is this for, because a dashboard that serves everyone serves no one. A developer needs to know why their build is red, right now.

The design question I'd ask first is who is this for, because a dashboard that serves everyone serves no one. A developer needs to know why their build is red, right now. An engineering manager needs trends. Neither wants the other's view.

Data model: every test execution recorded as an event — test ID, suite, outcome, duration, error signature, commit SHA, branch, environment, worker, retry count, and artifact links. That's the raw table; everything else is a query over it.

For developers, on a failed build: what failed, the error, the screenshot, the trace, and — the highest-value feature by far — has this test failed before, and how often? That single piece of history distinguishes 'you broke something' from 'this test is a known flake', which is the difference between an hour of investigation and thirty seconds.

For the team, as trends: flake rate per test and overall; suite duration over time (silent degradation is the killer); pass rate; mean time to fix a red build; and the number of tests that have never failed in a year — the candidates for deletion.

For the organisation: escaped defects mapped back to coverage gaps. That's the one metric that connects testing effort to business outcome.

The principle throughout: only surface a metric if someone would act differently because of it. Everything else is decoration that makes the dashboard slower and less trusted.

Key points
  • Design per audience: developer (why is my build red now?) vs manager (trends) — one view serves neither
  • Data model: one event per test execution — outcome, duration, error signature, commit, env, retries, artifacts
  • Killer developer feature: has this test failed before? Distinguishes real breakage from known flake instantly
  • Team trends: flake rate, suite duration over time, MTTR on red builds, never-failed tests (deletion candidates)
  • Org level: escaped defects mapped to coverage gaps — the metric that links testing to business outcome
  • Only surface metrics that would change a decision
They'll ask next
  • How would you compute a useful 'error signature' for grouping failures?
  • What's the single metric you'd put on a wall screen?
Link to this question

How would you test a URL shortener?

System Design for Testmidsenior

I'd answer in layers, because the interviewer is testing whether I can decompose a system rather than list clicks.

I'd answer in layers, because the interviewer is testing whether I can decompose a system rather than list clicks.

Functional core: a long URL in produces a short URL; visiting the short URL redirects to the original with the correct status code (301 permanent vs 302 temporary is a real design decision with caching consequences — I'd ask which they intended, because it changes what I assert). Same URL submitted twice — does it return the same short code or a new one? That's a product decision, and if nobody has decided, I've just found a requirements gap.

Edge cases: invalid URLs, URLs without a scheme, extremely long URLs, URLs with query strings, fragments and unicode, a short code that doesn't exist (must 404, not 500), an expired link, a custom alias that's already taken, and a short URL pointing at itself.

Data: collision handling when the hash generates a duplicate code, and what happens under concurrent creation of the same URL.

Non-functional: redirect latency (this is the whole product — a slow redirect is a broken product); throughput under load; caching behaviour; what happens when the database is unavailable.

Security: can it be used for open redirects to phishing sites, is there rate limiting to stop mass generation, can I enumerate other people's codes by incrementing, is the analytics endpoint leaking who clicked what.

That security section is where most candidates stop too early.

Key points
  • Decompose into layers: functional, edge cases, data, non-functional, security
  • Ask about 301 vs 302 — it's a real design decision with caching consequences
  • Same URL twice: same code or new one? If nobody has decided, that's a requirements gap you just found
  • Edges: no scheme, unicode, fragments, nonexistent code (404 not 500), taken alias, self-referential link
  • Non-functional: redirect latency IS the product; collisions under concurrent creation
  • Security: open redirect to phishing, rate limiting, code enumeration — where most candidates stop too early
They'll ask next
  • Two users submit the same long URL at exactly the same moment. What should happen?
  • How would you load-test the redirect path specifically?
Link to this question

What is contract testing, and when is it worth the investment?

API & Contract Testingsenior

Contract testing verifies that a consumer's expectations of a provider match what that provider actually delivers — without deploying both services together.

Contract testing verifies that a consumer's expectations of a provider match what that provider actually delivers — without deploying both services together.

Mechanically (using Pact as the reference model): the consumer's tests run against a mock provider and, in doing so, generate a contract — a machine-readable record of the requests it makes and the responses it expects. That contract is published to a broker. The provider then replays those expectations against its real implementation in its own pipeline. If the provider changes a field type or removes a key that a consumer depends on, the provider's build goes red — before deployment, and without any shared environment.

That's the whole value proposition, and it's precisely the problem end-to-end testing solves badly at scale.

When it's worth it: many services, independent deploy cadences, and multiple teams. This is exactly the situation where integration bugs are expensive and end-to-end environments are unreliable.

When it isn't: a monolith, or one service with a single consumer you deploy together anyway. Then it's ceremony — you've added a broker, a workflow, and a maintenance burden to solve a problem you don't have. Integration tests are simpler and sufficient.

Being able to say clearly when not to use it is what distinguishes someone who has run it from someone who has read about it.

Key points
  • Verifies consumer expectations against provider reality without co-deploying the two services
  • Consumer tests generate a contract → published to a broker → provider replays it in its own pipeline
  • A breaking provider change turns the provider's build red before deployment
  • Worth it with many services, independent deploys, multiple teams
  • Not worth it for a monolith or a single co-deployed consumer — then it's pure ceremony
They'll ask next
  • What does contract testing NOT catch?
  • Who owns the contract when the consumer and provider teams disagree?
Link to this question

What would you test in an API, and how would you prioritise?

API & Contract Testingmidsenior

I'd organise it by what breaks users, and prioritise accordingly.

I'd organise it by what breaks users, and prioritise accordingly.

Contract and schema first. Field names, types, required-ness, response shape. A field silently changing from integer to string breaks every consumer and returns a cheerful 200 while doing so — a value assertion often misses this; a schema assertion catches it every time.

Authorisation, not just authentication. Authentication asks are you logged in. Authorisation asks are you allowed to touch this specific record. The classic and still-ubiquitous vulnerability is changing the ID in the URL and reading someone else's data. I test that explicitly, for every resource, with a token belonging to a different user. This is the highest-value security test an SDET can automate.

Business logic on the happy paths, then the negative paths: missing required fields, wrong types, boundary values, malformed JSON.

Error contract consistency — do failures return a predictable structure, or does one endpoint return JSON and another an HTML stack trace? Inconsistent errors break clients.

Idempotency of PUT and DELETE, so retries are provably safe.

Side effects — did the database actually change, and did the downstream event fire? An API that returns 201 and silently fails to persist is the worst kind of bug.

Then pagination edges, rate limiting, and performance under a realistic payload.

Key points
  • Schema/contract first: type changes break consumers while still returning 200
  • Authorisation over authentication: swap the ID, use another user's token — the highest-value security test to automate
  • Business logic happy paths, then negatives: missing fields, wrong types, boundaries, malformed JSON
  • Consistent error contract; idempotency of PUT/DELETE so retries are safe
  • Side effects: verify the DB actually changed and downstream events fired
They'll ask next
  • How do you verify a downstream event was published?
  • What's the difference between a 401 and a 403, and how do you test each?
Link to this question

How do you test JWT or OAuth2 authentication?

API & Contract Testingmidsenior

I'd separate the happy path — which is easy and which everyone tests — from the failure paths, which is where the vulnerabilities actually live.

I'd separate the happy path — which is easy and which everyone tests — from the failure paths, which is where the vulnerabilities actually live.

Happy path: obtain a token, call a protected endpoint, assert 200 and correct data. Fine, but it proves almost nothing about security.

The tests that matter:

No token → must be 401. Malformed token → 401, not a 500 (a stack trace here is an information leak). Expired token → 401; I manufacture one rather than waiting, by minting a token with a past expiry or manipulating the clock. Token with a valid signature but tampered payload — change the user ID or role claim and re-send; if the server accepts it, signature verification is broken, which is catastrophic. Token signed with the wrong key, or with alg: none if the library is old — a genuine historical vulnerability worth probing.

Wrong scope or role → 403, not 401, and definitely not 200. Another user's valid token accessing this resource → 403. That's the authorisation test, and it's the one that finds real bugs.

Refresh flow: does the refresh token work, is it single-use, is it revoked on logout, and does an old access token stop working after logout — or does it remain valid until expiry, which is a design decision the team may not realise they've made.

In the framework, I'd fetch tokens once per role in a fixture rather than logging in per test.

Key points
  • The happy path proves almost nothing — the failure paths are the test
  • No token → 401; malformed → 401 not 500 (a stack trace is an information leak)
  • Expired token: manufacture one, don't wait for it
  • Tampered payload with valid signature — if accepted, signature verification is broken (catastrophic)
  • Wrong scope → 403; another user's valid token on your resource → 403 (the real authorisation test)
  • Refresh flow: single-use? revoked on logout? does the old access token die or live until expiry?
They'll ask next
  • How do you generate an expired token without waiting for expiry?
  • What's the security risk if logout doesn't invalidate the access token?
Link to this question

What is idempotency, and why does it matter for testing?

API & Contract Testingmidsenior

An operation is idempotent if performing it multiple times has the same effect as performing it once. GET is idempotent (and safe — it changes nothing).

An operation is idempotent if performing it multiple times has the same effect as performing it once. GET is idempotent (and safe — it changes nothing). PUT is idempotent: setting a resource to a given state twice leaves it in that state. DELETE is idempotent: deleting something already deleted leaves it deleted. POST is not: two POSTs create two resources.

Why it matters to a tester, in three ways.

It's directly testable behaviour, and it's frequently wrong. Send the same PUT twice, assert the end state is identical and no duplicate side effects occurred. Send DELETE twice — the second should be a clean 404 or 204, not a 500.

It determines whether retries are safe, which matters enormously in distributed systems. Networks fail after the server has processed a request but before the response arrives. The client retries. If that operation isn't idempotent, the user is charged twice. This is not hypothetical — it's one of the most common serious defects in payment systems, and testing it means simulating a response failure after processing.

It shapes your own test design. A test that can safely re-run without cleanup is one built on idempotent setup. Non-idempotent setup is a source of order-dependence and flakiness.

The question behind the question is usually: do you understand what happens when the network fails halfway?

Key points
  • Same operation repeated = same result. GET/PUT/DELETE idempotent; POST is not
  • Directly testable: PUT twice → identical state, no duplicate side effects; DELETE twice → clean, not 500
  • It's what makes retries safe — a non-idempotent payment endpoint double-charges when the network drops the response
  • Test it by simulating a response failure AFTER the server has processed the request
  • It also shapes test design: idempotent setup makes tests safely re-runnable
They'll ask next
  • How would you implement an idempotency key for a payment endpoint?
  • How do you test the case where the response is lost but the server processed the request?
Link to this question

Explain the difference between a mock, a stub, a spy and a fake.

Mocking & Test Doublesmidsenior

They're all test doubles — stand-ins for real dependencies — but they differ in what they do and what they let you assert.

They're all test doubles — stand-ins for real dependencies — but they differ in what they do and what they let you assert.

A stub returns canned answers. It has no logic and no memory. You stub the payment gateway to always return 'approved' so you can test what your code does next.

A mock is a stub that also has expectations about how it is called. You assert that chargeCard was invoked exactly once, with these arguments. The verification is part of the mock's contract, which means mocks test interactions, not just outcomes.

A spy wraps a real object, letting the real behaviour happen while recording the calls. Useful when you want genuine behaviour but also want to assert that something was invoked.

A fake is a working, lightweight implementation. An in-memory database that really stores and retrieves, but in a hash map rather than on disk. It behaves correctly; it's simply not production-grade.

The distinction that actually matters in practice: mocks assert on interactions, and that couples your test to the implementation. If you assert that chargeCard was called with three specific arguments, a legitimate refactor that changes the call signature breaks your test even though the behaviour is unchanged. Over-mocking is one of the main reasons test suites become an obstacle to refactoring rather than a safety net for it.

So I lean toward stubs and fakes, and reach for mocks only when the interaction itself is the thing being verified.

Key points
  • Stub: canned responses, no logic, no memory
  • Mock: a stub with expectations about how it's called — asserts on interactions
  • Spy: wraps a real object, real behaviour happens, calls are recorded
  • Fake: a working lightweight implementation (in-memory DB)
  • Mocks couple tests to implementation — over-mocking makes suites block refactoring instead of enabling it
  • Prefer stubs and fakes; use mocks only when the interaction IS the thing under test
They'll ask next
  • Give an example where asserting on an interaction is genuinely the right thing to do.
  • What are the symptoms of an over-mocked test suite?
Link to this question

When should you mock a dependency, and when should you use the real thing?

Mocking & Test Doublessenior

The rule I use: mock what you don't own and can't control; use the real thing when the integration itself is the risk.

The rule I use: mock what you don't own and can't control; use the real thing when the integration itself is the risk.

Mock when: the dependency is a third party whose sandbox is slow, rate-limited or unreliable; when you need to force failure paths that are hard to trigger for real (a declined card, a timeout, a 500, a malformed response, a slow response that exercises your retry logic); when the real thing costs money per call; or when it makes tests non-deterministic.

Those failure paths are the crucial argument. You cannot reliably ask a payment sandbox to time out on demand — but timeouts are exactly where the serious bugs are. Mocking is the only way to test them systematically.

Use the real thing when: the integration is the risk you're trying to cover. A mocked database tells you nothing about whether your query is correct or your migration works. A mocked message queue won't tell you your serialisation is wrong. And critically — a mock can never tell you the provider changed their contract. It will happily keep passing against an API shape that no longer exists, which produces the worst kind of failure: a green suite and a broken production.

So the mature architecture is layered: mocks for breadth, speed and failure paths; a thin layer of real integration tests running less often to catch contract drift.

Key points
  • Rule: mock what you don't own or can't control; use the real thing when the integration IS the risk
  • Mock to force failure paths you can't trigger for real — timeouts, declines, 500s, malformed responses
  • Use real dependencies for databases, queues, and anything where serialisation or queries are the risk
  • The fatal mock weakness: it never notices the provider changed their contract — green suite, broken production
  • Mature architecture: mocks for breadth and failure paths + a thin real-integration layer for contract drift
They'll ask next
  • How would you find out the third party changed their API before your users do?
  • What's the risk of a test suite that mocks the database?
Link to this question

What is service virtualisation and how does it differ from mocking?

Mocking & Test Doublessenior

Mocking usually happens inside your test process — you substitute an object or intercept a call in code.

Mocking usually happens inside your test process — you substitute an object or intercept a call in code. Service virtualisation stands up a separate running service that impersonates the real dependency over the network: same protocol, same endpoints, realistic responses, and often realistic latency and error behaviour.

The practical difference is scope and fidelity. A mock serves one test process. A virtualised service can be shared by an entire environment — the front end, the integration tests, the manual testers and three other services can all point at it. Your application makes a genuine HTTP call and doesn't know it isn't real, which means you're testing your actual networking, serialisation, timeout and retry code rather than bypassing it.

When it earns its cost: when the real dependency is unavailable (a partner's system that only exists in production), expensive per call, rate-limited, or simply not built yet — virtualising an API from its spec lets your team build and test against it before the provider has written a line of code. That parallelisation of work is often the strongest business case.

Also: reproducing exotic conditions on demand — 30-second latency, 503s, malformed payloads — across a whole environment, not just one test.

The risk is identical to mocking, only larger: your virtual service can drift from reality and you'd never know. It must be regenerated from the provider's contract, and backed by a small number of real integration tests.

Key points
  • Mocking is in-process; virtualisation runs a real service over the network that impersonates the dependency
  • Your app makes a genuine call — so networking, serialisation, timeouts and retries are actually exercised
  • Shareable across a whole environment, not just one test process
  • Best case: dependency unavailable, expensive, rate-limited, or not built yet (virtualise from the spec and work in parallel)
  • Same risk as mocking but bigger — it must be regenerated from the contract and backed by real integration tests
They'll ask next
  • How would you keep a virtualised service from drifting out of sync with reality?
  • What tools have you used for this?
Link to this question

Explain load, stress, soak and spike testing.

Performance Testingmidsenior

They answer different questions, and using the wrong one wastes a week.

They answer different questions, and using the wrong one wastes a week.

Load testing asks: does the system meet its performance targets under expected traffic? You model realistic usage — say the Black Friday projection — and verify response times and error rates stay within the agreed SLOs. This is the baseline, and it's the one most teams actually need.

Stress testing asks: where does it break, and how? You push past expected load until something fails. The valuable finding isn't the number where it breaks — it's the manner of failure. Does it degrade gracefully, shedding load and returning clean 503s? Or does it fall over, corrupt data, and refuse to recover once traffic drops? A system that recovers automatically is a very different risk profile from one that needs a human at 3am.

Soak testing asks: does it survive sustained load over time? Run at normal load for eight, twelve, twenty-four hours. This is the only way to find memory leaks, connection-pool exhaustion, disks filling with logs, and slow degradation — problems entirely invisible in a ten-minute load test.

Spike testing asks: what happens on a sudden vertical jump? Traffic goes from 100 to 10,000 users in thirty seconds — a product launch, a viral post, a marketing email. Does autoscaling react in time, or does the system die before the new instances are ready? That lag is where real incidents live.

Key points
  • Load: does it meet targets under expected traffic? (the baseline most teams actually need)
  • Stress: where and HOW does it break — graceful degradation vs data corruption and no recovery
  • Soak: sustained load over hours — the only way to find memory leaks and connection-pool exhaustion
  • Spike: sudden vertical jump — does autoscaling react before the system dies?
  • The valuable output of stress testing is the failure MODE, not the breaking number
They'll ask next
  • You run a soak test and memory climbs steadily. What do you do next?
  • Which of these would you run in CI, if any?
Link to this question

Why is average response time a misleading metric? What should you use instead?

Performance Testingmidsenior

Because an average hides the users who are suffering, and those are the only ones who matter.

Because an average hides the users who are suffering, and those are the only ones who matter.

If 95 requests return in 100ms and 5 return in 10 seconds, the average is around 600ms — which looks acceptable, and is a lie. Five percent of your users just waited ten seconds. On a checkout page, that's revenue walking out the door, and your dashboard is green.

Averages are also skewed by outliers in a way that can cut both ways, and they tell you nothing about the shape of the distribution, which is where the information is.

So I use percentiles. p50 (the median) tells you the typical experience. p95 and p99 tell you what the unlucky tail is experiencing. The p99 is where you find the users hitting a cold cache, an unindexed query, a garbage-collection pause, or a slow shard.

The rule of thumb worth stating: your p99 is somebody's p100. At scale, the 1% is thousands of real people, and disproportionately your heaviest users — the ones with the most data, the most orders, the most to lose.

I'd also always pair latency with throughput and error rate, because latency alone is meaningless: a system can achieve beautiful response times by rejecting most of the traffic.

Key points
  • An average hides the suffering tail: 95 fast + 5 terrible requests still averages 'fine'
  • Use percentiles: p50 = typical experience, p95/p99 = what the unlucky users get
  • The p99 is where cold caches, unindexed queries and GC pauses surface
  • Your p99 is somebody's p100 — at scale that's thousands of real, usually heavy, users
  • Always pair latency with throughput and error rate — a system can look fast by rejecting traffic
They'll ask next
  • Your p50 is 80ms and your p99 is 9 seconds. Where do you start investigating?
  • What SLO would you set for a checkout endpoint, and why?
Link to this question

A load test shows response times degrading. How do you find the bottleneck?

Performance Testingsenior

Systematically, from the outside in, and I'd resist the urge to guess — performance work punishes intuition.

Systematically, from the outside in, and I'd resist the urge to guess — performance work punishes intuition.

First, characterise the degradation. Is it linear with load, or is there a cliff at a specific concurrency? A cliff usually means a hard limit was hit — a connection pool exhausted, a thread pool saturated, a rate limiter engaging. Linear degradation suggests a resource saturating gradually.

Then locate the layer. Look at metrics across the stack simultaneously: load balancer, application servers, database, cache, downstream services. CPU, memory, disk I/O, network, and — the one people forget — connection pool utilisation, which is a spectacularly common culprit and invisible unless you look for it.

Then use tracing. With distributed tracing, take a slow request and see exactly where the time went. This turns a guessing game into a measurement. Without it, you're bisecting blindly.

The usual suspects, in rough order of how often they turn out to be the answer: an N+1 query pattern that's fine with 10 rows and catastrophic with 10,000; a missing database index exposed by data volume; connection pool exhaustion; a lock or contention point serialising work; an external dependency slowing down; and garbage collection pauses.

Then fix one thing, re-run, and measure. Changing three things at once and seeing improvement teaches you nothing about which one worked.

Key points
  • Characterise first: a cliff at a specific concurrency = a hard limit (pool, threads, rate limiter); linear = gradual saturation
  • Look across all layers at once — including connection pool utilisation, the commonly missed culprit
  • Distributed tracing turns guessing into measurement: see exactly where a slow request spent its time
  • Usual suspects: N+1 queries, missing indexes at volume, pool exhaustion, lock contention, GC pauses
  • Change one thing, re-run, measure — fixing three at once teaches you nothing
They'll ask next
  • What's an N+1 query and why does it only hurt at scale?
  • How would you prove a fix actually worked?
Link to this question

Why would you containerise your test environment?

Containers & Infrastructuremidsenior

Three reasons, and the first one alone justifies it.

Three reasons, and the first one alone justifies it.

Reproducibility. The container pins the browser version, the driver version, the language runtime, the libraries and the OS. The image that runs on my laptop is byte-for-byte the image that runs in CI. That kills 'works on my machine' as a category of problem — and 'works on my machine' is responsible for an enormous share of the hours engineers waste on test failures that were never product bugs at all.

Isolation. Each run gets a clean environment. No leftover state, no cached credentials, no leftover browser profile, no test data from last Tuesday quietly making today's test pass for the wrong reason.

Scalability. Containers are how you spin up fifty parallel workers on demand and destroy them ten minutes later. That elasticity is what makes a large suite finish quickly without maintaining a farm of idle machines.

In practice that means a Dockerfile pinning your exact stack, and often Docker Compose or Kubernetes to stand up the whole environment — the app, a database seeded to a known state, a mock of the third-party payment provider, and the browsers — as one reproducible unit, then tear it all down.

The payoff worth naming: a new engineer clones the repo, runs one command, and has an identical working environment in minutes rather than spending their first two days debugging their local setup.

Key points
  • Reproducibility: pinned browser, driver, runtime and OS — the same image locally and in CI. Kills 'works on my machine'
  • Isolation: a clean environment per run, no leftover state or stale test data
  • Scalability: spin up 50 workers on demand, destroy them after — no idle machine farm
  • Compose/Kubernetes can stand up the whole world: app + seeded DB + mocked third party + browsers
  • Real payoff: a new joiner is productive in minutes, not after two days of setup debugging
They'll ask next
  • What goes in your Dockerfile for a Playwright test suite?
  • How do you handle a test that needs a real database?
Link to this question

Design a CI/CD pipeline for a team shipping daily. What runs where?

CI/CD Pipelinessenior

The organising principle is fast feedback where it's cheap, thorough coverage where it's affordable. Every stage has a time budget, and a stage that exceeds it gets restructured, not tolerated.

The organising principle is fast feedback where it's cheap, thorough coverage where it's affordable. Every stage has a time budget, and a stage that exceeds it gets restructured, not tolerated.

On every commit / pull request — target under 10 minutes, ideally under 5. Linting and static analysis. Unit tests. A fast API smoke suite. Build the artifact. This is a blocking gate: it must pass to merge. The time budget is a correctness property, not a nicety — if the PR gate takes 40 minutes, developers batch their work, context-switch away, and start looking for ways around it. A slow gate becomes theatre.

On merge to main — deploy to staging, then run the broader regression: full API suite plus the critical-journey UI tests, sharded across parallel workers. Also blocking: a red main is an emergency, not a ticket.

Nightly — the full cross-browser matrix, long end-to-end journeys, soak and load tests, security scans. Slow and expensive, so it runs when nobody is waiting.

Pre-release / on demand — the complete suite plus performance benchmarks against production-like data.

Post-deploy — smoke tests against production, synthetic monitoring, and an automatic rollback trigger if health checks fail.

The two things I'd insist on: artifacts published on every failure so triage needs no local reproduction, and quality gates that actually block — a gate that can be clicked past isn't a gate.

Key points
  • Principle: fast feedback where cheap, thorough coverage where affordable — every stage has a time budget
  • PR gate (<10 min, blocking): lint, unit, fast API smoke, build
  • A slow PR gate is a correctness bug — developers route around it and it becomes theatre
  • Merge to main: deploy to staging + sharded regression, also blocking
  • Nightly: full matrix, long E2E, soak/load, security scans. Pre-release: everything + perf benchmarks
  • Post-deploy: production smoke, synthetic monitoring, automatic rollback on failed health checks
They'll ask next
  • Your PR pipeline hits 25 minutes. What do you cut, and how do you decide?
  • What's your rollback strategy if the post-deploy smoke test fails?
Link to this question

How do you make a test suite safe to run in parallel?

Parallel Executionmidsenior

Parallelism doesn't create problems — it reveals them. If tests fail when run in parallel, they were already coupled; sequential execution was hiding it.

Parallelism doesn't create problems — it reveals them. If tests fail when run in parallel, they were already coupled; sequential execution was hiding it. So the work is eliminating shared mutable state, not tuning the runner.

Test data isolation is the big one. Every test creates its own data with globally unique identifiers — a UUID-suffixed email, a run-scoped order reference. Two tests registering test@example.com will collide, and the failure will look random and time-dependent, which is the worst kind to debug.

No shared fixtures that mutate. A session-scoped logged-in browser is fast until one test logs out and twenty others fail mysteriously. Read-only shared fixtures are fine; anything mutable must be per-test.

No order dependence. If test B needs the user that test A created, they're one test wearing a trenchcoat. Each must set up its own preconditions.

Isolate at the strongest available level. Best is a per-test tenant, namespace or schema. Next best is a transactional rollback. Failing both, unique data plus tagged cleanup.

Watch external limits — a third-party sandbox with a rate limit will start throwing 429s under parallelism and you'll blame your tests.

The proof: run the suite in random order, and run it in parallel. Anything that breaks was never independent. I'd wire that into CI, because independence decays silently otherwise.

Key points
  • Parallelism reveals coupling, it doesn't cause it — sequential runs were hiding the bug
  • Unique data per test (UUID/run-scoped) — shared identifiers cause random-looking, time-dependent failures
  • No mutable shared fixtures; a session-scoped logged-in browser is a classic trap
  • Isolation, best first: per-test tenant/namespace → transactional rollback → unique data + tagged cleanup
  • Watch third-party rate limits — 429s under parallelism look like test bugs
  • Prove it by running randomised and parallel in CI; independence decays silently
They'll ask next
  • How do you handle a test that genuinely cannot run in parallel?
  • What's the right level of parallelism, and how would you find it?
Link to this question

How would you shard a large test suite across workers efficiently?

Parallel Executionsenior

The key insight is that wall-clock time is set by the slowest shard, not by the average. So the goal isn't equal test counts — it's equal duration.

The key insight is that wall-clock time is set by the slowest shard, not by the average. So the goal isn't equal test counts — it's equal duration.

Naive sharding splits by file or alphabetically. That produces wildly uneven shards: one worker gets ten fast unit tests and finishes in a minute; another gets three end-to-end journeys and takes twelve. Your suite now takes twelve minutes and eleven of your workers are idle. You paid for parallelism and got very little.

The fix is duration-based sharding: record each test's historical runtime, then bin-pack tests into shards so each shard's total predicted duration is roughly equal. It's a straightforward greedy algorithm — sort tests longest-first, assign each to whichever shard currently has the least work. That single change often halves wall-clock time with no extra infrastructure.

Refinements worth mentioning: keep the timing data fresh, since tests get slower as they grow. Handle new tests with no history by assigning them a default. And where tests must run together — a class-scoped fixture that's expensive to build — shard at that boundary rather than splitting them.

I'd also put the historically flakiest and highest-value tests first within each shard, so a broken build fails in ninety seconds rather than nine minutes. Fast failure is worth real money in engineer attention.

Key points
  • Wall-clock = the slowest shard, so balance by DURATION, not by test count
  • Naive file/alphabetical sharding leaves most workers idle while one grinds
  • Duration-based bin-packing: sort longest-first, assign to the least-loaded shard — often halves wall-clock
  • Keep timing data fresh; give new tests a default estimate
  • Shard at fixture boundaries when tests must share expensive setup
  • Run flakiest/highest-value tests first so broken builds fail in 90 seconds, not 9 minutes
They'll ask next
  • How would you collect and store the timing data?
  • What happens to your sharding when someone adds 200 new tests?
Link to this question

Given an array of integers, find if any value appears more than once. What's your approach and complexity?

Coding & DSAjuniormid

I'd talk through the trade-off rather than jumping to code, because that's what's being assessed.

I'd talk through the trade-off rather than jumping to code, because that's what's being assessed.

Brute force: compare every element with every other. Two nested loops. O(n²) time, O(1) space. It works, and I'd say so — a working solution beats a broken clever one — but it degrades badly.

Sorting: sort the array, then check adjacent pairs. O(n log n) time, O(1) extra space if sorting in place. Better, and it's the right answer if memory is the binding constraint.

Hash set: iterate once; for each element, check if it's already in the set — if so, return true; otherwise add it. O(n) time, O(n) space. This is the answer they want, and the reasoning is the point: we trade memory for time, because a set gives us O(1) average lookup.

def has_duplicate(nums):
    seen = set()
    for n in nums:
        if n in seen:
            return True
        seen.add(n)
    return False

The edge cases I'd raise unprompted, because as a tester this is my actual advantage: empty array, single element, all elements identical, negative numbers, and — the one people miss — very large inputs where O(n) memory becomes the problem, which would push me back to sorting.

Stating the complexity trade-off out loud is what distinguishes a passing answer from a good one.

Key points
  • Walk the options: brute force O(n²) → sort O(n log n), O(1) space → hash set O(n) time, O(n) space
  • Hash set is the expected answer; the point is articulating the memory-for-time trade
  • Say the complexity out loud — the reasoning is what's scored, not the code
  • Raise edge cases unprompted: empty, single element, all identical, negatives, memory limits at scale
  • As a tester, edge cases are your differentiator in a coding round — use them
They'll ask next
  • Now return the duplicated values themselves, not just true/false.
  • What if the array is too large to fit in memory?
The trap

Silently coding the optimal solution without discussing alternatives. The interviewer is scoring your reasoning process, and silence gives them nothing to score.

Link to this question

How much data structures and algorithms knowledge does an SDET actually need?

Coding & DSAmidsenior

Enough to pass the interview, and — separately — enough to be useful in the job. The two aren't the same, and it's worth being honest about that.

Enough to pass the interview, and — separately — enough to be useful in the job. The two aren't the same, and it's worth being honest about that.

For the interview, at most companies the bar is real but not extreme: comfortable with arrays, strings, hash maps and sets; able to reason about time and space complexity out loud; able to solve easy-to-medium problems while explaining your thinking. Hash maps alone unlock a large share of these problems. You are typically not expected to handle hard dynamic programming or graph theory, unless the company deliberately runs the same bar as its software engineers — some do, and you should ask which.

For the job, the honest answer is that you rarely implement a red-black tree. What genuinely recurs: choosing the right data structure so a test-data lookup is O(1) rather than O(n) inside a loop; understanding complexity well enough to know why a suite got slow; parsing and transforming logs and API responses efficiently; and the reasoning ability that DSA practice actually trains, which is decomposing a problem before writing code.

What I'd say to an interviewer: the value isn't in memorising algorithms. It's that the same discipline — restate the problem, work an example, consider the brute force, then optimise — is exactly how you approach designing a test framework or debugging a race condition. That's the transferable part.

Key points
  • Interview bar: arrays, strings, hash maps/sets, complexity reasoning, easy-to-medium problems explained aloud
  • Hash maps alone unlock a large share of typical questions
  • Ask the company whether SDETs face the same coding bar as SWEs — some do
  • On the job you rarely implement exotic structures; you constantly choose the right one and reason about complexity
  • The transferable value is the method: restate → example → brute force → optimise — the same approach used for framework design and debugging
They'll ask next
  • When did complexity actually matter in your test code?
  • How do you prepare for a coding round without just grinding LeetCode?
Link to this question

Walk me through how you'd approach a coding problem you've never seen in an interview.

Coding & DSAjuniormidsenior

I use a fixed method, because under pressure improvisation fails and process holds.

I use a fixed method, because under pressure improvisation fails and process holds.

Restate the problem in my own words and confirm I've understood. This catches misunderstandings while they're free, and interviewers routinely embed an ambiguity to see if you ask.

Clarify the constraints. How large is the input? Can it be empty? Are there duplicates, negatives, unicode? Is it sorted? Can I mutate it? These questions are not stalling — they're the job, and as a tester they're my natural strength.

Work a small example by hand, out loud. This often reveals the shape of the solution and always reveals whether I've actually understood the problem.

State a brute force solution and its complexity, immediately. This guarantees I have something, and it gives the interviewer a baseline to help me optimise from. Candidates who sit silently searching for the elegant answer often produce nothing at all.

Optimise deliberately, naming the trade-off: 'I can trade O(n) space for O(n) time using a hash map.'

Then code it, narrating as I go.

Then test it, and this is where I have an edge over a pure developer candidate — I walk through the edge cases explicitly: empty input, single element, duplicates, maximum size, the boundaries.

The method matters more than the answer. An interviewer will forgive a suboptimal solution; they will not forgive silence.

Key points
  • Restate the problem — interviewers deliberately embed ambiguity to see if you ask
  • Clarify constraints: size, empties, duplicates, negatives, sorted, mutable — a tester's natural strength
  • Work a small example by hand, out loud
  • State the brute force and its complexity immediately — never sit in silence hunting for elegance
  • Optimise while naming the trade-off; then code, narrating; then test the edge cases explicitly
  • The method is what's scored. A suboptimal answer is forgiven; silence is not
They'll ask next
  • You get stuck halfway. What do you do?
  • You realise your approach is wrong after 15 minutes. How do you recover?
Link to this question

Which design patterns are genuinely useful in test automation?

Design Patternsmidsenior

A handful earn their keep. The rest are usually pattern-fetishism that makes a framework harder to read.

A handful earn their keep. The rest are usually pattern-fetishism that makes a framework harder to read.

Page Object / Screenplay — encapsulating locators and interactions. The foundational one.

Factory — for creating browser instances or test data entities. DriverFactory.create('chrome') keeps browser-specific instantiation in one place, and a data factory (UserFactory.create(tier='gold')) gives you unique, valid entities with sensible defaults. This is the pattern I use most.

Builder — for complex test data with many optional fields. OrderBuilder().withItems(3).withExpiredCard().build() is dramatically more readable than a constructor with eleven positional arguments, and it lets each test specify only what it cares about.

Singleton — used for shared config or a driver instance, and this is the one I'd flag as dangerous. A singleton driver makes parallel execution impossible, which is precisely the wrong constraint to bake in. If someone tells me their framework uses a singleton WebDriver, I know their suite can't parallelise, and I'd say so.

Strategy — swapping implementations, such as different authentication mechanisms across environments.

Facade — a simple API over messy setup, so tests read cleanly.

The principle I'd close on: patterns are a vocabulary, not a goal. If a pattern makes the framework harder for a new joiner to read, it has cost more than it delivered.

Key points
  • Page Object, Factory (drivers and test data), Builder (complex data with optional fields) — the ones that earn their keep
  • Builder beats a constructor with eleven positional arguments and lets tests state only what they care about
  • Singleton is a trap: a singleton WebDriver makes parallel execution impossible
  • Strategy for swappable implementations (auth per environment); Facade to hide messy setup
  • Patterns are a vocabulary, not a goal — one that hurts readability has cost more than it gave
They'll ask next
  • Why exactly does a singleton driver break parallelism?
  • Show me a builder you'd write for test data.
Link to this question

How do the SOLID principles apply to test code?

Design Patternssenior

They apply directly, because test code is code — and the suites that rot are invariably the ones that ignored them.

They apply directly, because test code is code — and the suites that rot are invariably the ones that ignored them.

Single Responsibility. A page object handles one page. A test verifies one behaviour. The most common violation is a page object that also asserts, or a 'utils' class that has become a landfill for everything nobody knew where to put.

Open/Closed. Adding a new browser should mean adding a new factory implementation, not editing a growing if/else chain in the driver setup. When you find yourself modifying core code to add a variant, the design is wrong.

Liskov Substitution. Any page object subclass must be usable wherever its base is. If AdminDashboardPage breaks code expecting a DashboardPage, your inheritance is wrong — and in test frameworks, deep inheritance hierarchies are a common and painful mistake. Composition usually beats inheritance here.

Interface Segregation. Don't force a test to depend on a fat interface it barely uses. A small, focused API client is easier to mock and easier to read.

Dependency Inversion. Tests depend on abstractions, not concretions. If a test instantiates a WebDriver directly, it can never run against a different browser or a mock. Inject the dependency instead — this is what makes a framework configurable and testable.

The unifying point: the reason we hold test code to this standard is that it gates every release. Bad test code doesn't just annoy engineers — it eventually stops the team from shipping.

Key points
  • SRP: a page object handles one page, a test verifies one behaviour — the classic violation is a 'utils' landfill
  • Open/Closed: add a browser by adding a factory impl, not by editing an if/else chain
  • Liskov: deep inheritance in page objects is a common, painful mistake — prefer composition
  • Interface Segregation: small focused clients are easier to mock and read
  • Dependency Inversion: inject the driver; a test that constructs its own can never be reconfigured
  • Test code gates every release — bad test code eventually stops the team shipping
They'll ask next
  • Show me how dependency injection changes a page object's design.
  • When is inheritance the right call in a test framework?
Link to this question

What do you look for when reviewing production code as an SDET?

Code Reviewsenior

I bring a perspective developers often don't, so I focus where I add value rather than duplicating their review.

I bring a perspective developers often don't, so I focus where I add value rather than duplicating their review.

Testability. Can this be tested without standing up the world? Are dependencies injected or hard-coded inside the class? Is there a seam to substitute the payment gateway, or is it constructed inline? Untestable code isn't a testing problem — it's a design defect, and the cheapest moment to say so is now.

Error handling. What happens when the downstream call fails, times out, or returns malformed data? Developers write the happy path fluently; the failure paths are where I find bugs. A bare catch that swallows the exception is my most common comment.

Edge cases, which is my professional deformation and my actual value: nulls, empty collections, zero, negatives, concurrent access, and — perennially — boundary conditions on comparisons.

Observability. If this fails in production at 3am, will there be a log line with enough context to diagnose it? Are the right things instrumented?

Test coverage of the change itself: are there tests, do they assert meaningfully, and could they actually fail?

Backwards compatibility for API changes — is a field being removed or retyped that a consumer depends on?

I'm not there to compete on algorithms or style. I'm there to ask the questions the person who wrote it is least likely to ask themselves, which is exactly the value of a second perspective.

Key points
  • Testability: are dependencies injected or hard-coded? Untestable code is a design defect, flagged cheapest now
  • Error handling — the happy path is fluent, the failure paths hide the bugs; swallowed exceptions are the top comment
  • Edge cases: nulls, empties, zero, negatives, concurrency, comparison boundaries
  • Observability: at 3am in production, will the logs let anyone diagnose this?
  • Backwards compatibility on API changes — a removed or retyped field breaks consumers
  • Add the perspective the author is least likely to have; don't duplicate the dev review
They'll ask next
  • Give me an example of a change you'd reject purely on testability grounds.
  • How do you push back on a senior developer without friction?
Link to this question

Who should write unit tests — developers or QA?

Role & Scopemidsenior

Developers, and I'd say that without hesitation.

Developers, and I'd say that without hesitation.

The reasons are practical, not political. Unit tests are written against the code's internal structure, so they need intimate knowledge of the implementation, and they need to be written as the code is written — not weeks later by someone reverse-engineering intent. They're also the fastest feedback loop a developer has; taking that away from them makes them slower and makes the code worse.

And there's a subtler point: writing a unit test forces you to confront your own design. If a function is painful to unit test, it's usually badly designed — too many dependencies, doing too much. That signal is only useful if it reaches the person who can act on it immediately. Outsource unit testing to QA and you've broken the feedback loop that improves the design.

So what does the SDET do? Everything that makes developers' testing better and everything above the unit layer. Build the frameworks and harnesses they use. Review their tests — asking 'can this actually fail?' is a high-value contribution. Own the integration, contract, end-to-end and performance layers. Own the CI pipeline and the quality gates. And bring the risk judgement that says this area needs deeper coverage.

The failure mode I'd warn against is QA writing unit tests because developers won't. That doesn't fix the culture — it subsidises it, and the developers never build the habit.

Key points
  • Developers — they need implementation knowledge and they need the feedback loop as they code
  • A hard-to-unit-test function is a design smell; that signal is only useful to the person who can act on it now
  • SDET owns what makes dev testing better: frameworks, harnesses, test review ('can this fail?')
  • SDET owns integration, contract, E2E, performance, CI and the quality gates
  • Warning: QA writing unit tests because devs won't subsidises the dysfunction instead of fixing it
They'll ask next
  • Developers on your team refuse to write unit tests. What do you actually do?
  • How do you review a unit test well without knowing the codebase deeply?
Link to this question

What's the difference between line, branch and mutation coverage?

Coverage & Qualitysenior

They measure progressively more meaningful things.

They measure progressively more meaningful things.

Line coverage: which lines executed during the test run. It's the weakest metric, because executing a line proves nothing about whether you verified it. A test with no assertions at all can achieve 100% line coverage. This is the number most often quoted and least often deserved.

Branch coverage: whether every branch of every conditional was taken — both the true and the false path of each if. Stronger, because a line inside an if can be covered while the else-path never runs. It catches a real class of gap.

Mutation coverage: the honest one. A tool deliberately introduces small faults into your source code — flips a > to >=, changes a + to a -, replaces a return value — and re-runs your tests. If the tests still pass, the mutant survived, meaning your tests do not actually detect that bug. Mutation score is the percentage of mutants your suite kills.

This is the metric that exposes the lie in the other two. A suite can have 95% line coverage and a terrible mutation score, which tells you precisely what's wrong: the tests execute the code but don't meaningfully assert on it. That's an enormous amount of expensive, comforting, useless test code — and no other metric would have told you.

The cost is that mutation testing is slow. I'd run it periodically on critical modules, not on every commit.

Key points
  • Line coverage: which lines ran. Weakest — a test with zero assertions can hit 100%
  • Branch coverage: were both paths of each conditional taken? Catches a real class of gap
  • Mutation coverage: inject faults, re-run tests. A surviving mutant = your tests can't detect that bug
  • High line coverage + low mutation score = tests that execute code without asserting on it
  • Mutation testing is slow — run it periodically on critical modules, not every commit
They'll ask next
  • Your team has 90% line coverage. What questions would you ask?
  • Would you set mutation score as a target? Why or why not?
Link to this question

Your manager wants 100% code coverage as a KPI. How do you respond?

Coverage & Qualitysenior

I'd push back, but with a constructive alternative rather than a lecture — because the underlying instinct is good, and I want to redirect it rather than kill it.

I'd push back, but with a constructive alternative rather than a lecture — because the underlying instinct is good, and I want to redirect it rather than kill it.

The problem: coverage measures execution, not verification. The moment 100% becomes a target, engineers hit it the cheap way — tests that call every function and assert nothing, or assert something trivially true. You get the number, you pay for the maintenance, and you gain no safety. This is Goodhart's law: a measure that becomes a target stops being a good measure.

There's also a diminishing-returns argument. The last 10% is typically getters, generated code, defensive branches that can't be reached, and error handlers for conditions that can't occur. The effort to cover it is substantial and the defects it finds are near zero — while the same hours spent on the payment module's edge cases would find real bugs.

What I'd propose instead: coverage as a signal, not a target. Look at which critical modules have thin coverage — that's actionable. Set a floor for new code (say, no PR may decrease coverage) rather than an absolute ceiling. And measure what the manager actually wants, which isn't coverage at all — it's fewer escaped defects. So track escaped defects, and mutation score on the critical paths, because those measure whether tests can actually catch bugs.

That reframing usually lands, because it gives them a better version of the thing they were asking for.

Key points
  • Coverage measures execution, not verification — 100% is achievable with zero assertions
  • Goodhart's law: the moment it's a target, it stops measuring anything real
  • The last 10% is getters, generated code and unreachable branches — high effort, near-zero defect yield
  • Counter-proposal: coverage as a signal; a floor on new code (no PR decreases it) rather than an absolute target
  • Measure what they actually want: escaped defects, plus mutation score on critical paths
  • Redirect the instinct rather than killing it — it gives them a better version of what they asked for
They'll ask next
  • What coverage number would you actually be comfortable with, and why?
  • How would you find the modules where coverage is genuinely dangerous?
Link to this question

What is mutation testing, and would you actually use it?

Coverage & Qualitysenior

Mutation testing tests your tests. A tool takes your source code and deliberately introduces small faults — a mutant — by flipping a comparison operator, negating a condition, altering a constant, or…

Mutation testing tests your tests. A tool takes your source code and deliberately introduces small faults — a mutant — by flipping a comparison operator, negating a condition, altering a constant, or replacing a return value. Then it runs your test suite against each mutant.

If a test fails, the mutant is killed: your suite detected that bug. If every test still passes, the mutant survived, which means there's a real bug you could introduce into that line and your test suite would not notice. Your mutation score is the percentage killed.

It's the only metric I know that directly answers the question everyone actually cares about: would my tests catch a bug if one were introduced? Line coverage cannot answer that. A suite with 95% line coverage and 30% mutation score is executing your code and verifying nothing — and mutation testing is the only thing that will tell you.

Would I use it? Yes, but selectively. It's computationally expensive: you're running the suite once per mutant, and there can be thousands. Running it on every commit across a whole codebase is impractical.

So I'd apply it where the stakes justify the cost — payment calculation, pricing rules, authorisation logic, anything where a silent wrong answer is expensive — and run it nightly or weekly rather than per-PR. Used that way, it's the sharpest tool available for finding tests that only pretend to protect you.

Key points
  • Inject small faults (flip >, negate conditions, change constants), re-run the suite; killed = detected, survived = undetected bug
  • The only metric that answers: would my tests catch a bug if one were introduced?
  • 95% line coverage with 30% mutation score = tests that execute but never verify
  • Expensive — one suite run per mutant, thousands of mutants
  • Apply selectively: payments, pricing, authorisation. Run nightly or weekly, not per-PR
They'll ask next
  • You run it and find your payment module scores 40%. What now?
  • What kinds of surviving mutant are acceptable to ignore?
Link to this question

What quality gates would you put in a pipeline, and what should actually block a merge?

CI/CD Pipelinessenior

The design rule: a gate must be fast, reliable and meaningful. Fail any of those three and it gets bypassed, and a bypassed gate is worse than none because it creates false confidence.

The design rule: a gate must be fast, reliable and meaningful. Fail any of those three and it gets bypassed, and a bypassed gate is worse than none because it creates false confidence.

Blocking on a pull request — because these are fast and trustworthy: compilation/build; linting and static analysis; unit tests passing; a fast API smoke suite; no new critical security findings from a dependency or SAST scan; and coverage not decreasing on new code. All of that should complete in under ten minutes.

Blocking on merge to main: the broader regression suite against a staging deployment. A red main is an emergency.

Non-blocking, but visible: nightly full cross-browser runs, performance benchmarks, flaky-test reports, and the full security scan. These are too slow or too noisy to gate a merge, but they must be owned — a report nobody reads is a report that doesn't exist.

The hard-won principles I'd add. A flaky gate is worse than no gate, because it teaches engineers that red means 'retry'. Fix or quarantine flakes before you make anything blocking. Gates must be fast — a 40-minute PR gate doesn't improve quality, it just teaches people to batch work and route around it. And an override must exist but be visible: an emergency bypass with an audit trail and a named person is honest; a gate that can be silently clicked past is theatre.

Key points
  • A gate must be fast, reliable and meaningful — fail any one and it gets bypassed
  • Block on PR: build, lint/static analysis, unit tests, fast API smoke, no new critical security findings, coverage not decreasing
  • Block on merge to main: regression against staging. Red main = emergency
  • Non-blocking but owned: nightly matrix, performance, flaky reports, full security scan
  • A flaky gate is worse than no gate — it teaches engineers that red means 'retry'
  • Overrides must exist but be visible and audited; a silently-bypassable gate is theatre
They'll ask next
  • A critical hotfix is blocked by a flaky test at 2am. What's your policy?
  • How do you stop the PR gate from creeping to 40 minutes over time?
Link to this question

How would you test a database migration?

Data & Messagingsenior

Migrations are among the most dangerous changes a team ships, because they're often irreversible and they touch data that cannot be regenerated.

Migrations are among the most dangerous changes a team ships, because they're often irreversible and they touch data that cannot be regenerated. I'd treat this with more care than a code change, not less.

Before: review the migration script itself. Is it reversible — is there a working down migration, and has anyone actually run it? Does it lock a table (a long lock on a large table under production load is an outage, even though the migration is 'correct')? Is it backwards compatible with the currently-deployed application code, which will keep running against the new schema during a rolling deploy?

That last point is the one that bites teams. The safe pattern is expand-and-contract: add the new column, deploy code that writes to both, backfill, deploy code that reads from the new one, and only then drop the old column — several deploys, never one.

Testing it: run it against a realistic volume of production-like data, not a dozen seed rows, because performance is the failure mode you won't otherwise see. Verify data integrity afterwards — row counts, no nulls where the code assumes values, foreign keys intact, no silently truncated fields. Test the rollback path by actually executing it. Test running it twice, because retries and partial failures happen and a non-idempotent migration turns a blip into an incident.

And test the application against both schema versions, since during deployment both will be live simultaneously.

Key points
  • Review first: is there a working down-migration, does it lock a large table, is it backwards compatible?
  • Rolling deploys mean old code runs against the new schema — expand-and-contract across several deploys, never one
  • Test against production-like VOLUME — performance is the failure mode a seed dataset hides
  • Verify integrity after: row counts, unexpected nulls, foreign keys, silently truncated fields
  • Actually execute the rollback; run the migration twice to prove idempotency
  • Test the app against both schema versions — during deploy, both are live
They'll ask next
  • Walk me through expand-and-contract for renaming a column.
  • The migration takes 4 hours on production data. What do you do?
Link to this question

How do you test an event-driven system using a message queue like Kafka?

Data & Messagingsenior

The defining difficulty is asynchrony: you make a request, get a 200, and the actual work happens somewhere else, later.

The defining difficulty is asynchrony: you make a request, get a 200, and the actual work happens somewhere else, later. Naive tests either assert too early and fail, or add a sleep(5) and become slow and flaky. Neither is acceptable.

Produce side: after triggering the action, assert that the correct event was published — right topic, right schema, right payload, right key (the key determines partitioning, which determines ordering, so a wrong key is a real bug). I'd consume from the topic in the test to verify this directly.

Consume side: publish an event onto the topic yourself and assert the consuming service reacts correctly. This is powerful because you can test the consumer in complete isolation from the producer.

Then the failure modes, which is where the real bugs are and where most candidates stop:

Duplicate delivery — most queues guarantee at-least-once, not exactly-once. So the same event will be delivered twice at some point. Is the consumer idempotent, or does it charge the customer twice? Test it by publishing the same event twice.

Out-of-order delivery across partitions. Malformed or unparseable messages — does the consumer crash and block the partition, or route to a dead-letter queue? Consumer failure mid-processing — is the offset committed before or after the work, and does that lose the message or reprocess it? Schema evolution — can an old consumer handle a new field?

And instead of sleeping, poll with a timeout for the expected end state.

Key points
  • The core difficulty is asynchrony — never sleep, poll for the expected end state with a timeout
  • Produce side: assert the right event, topic, schema, payload and KEY (the key drives partitioning and ordering)
  • Consume side: publish an event yourself and assert the consumer reacts — tests it in full isolation
  • At-least-once delivery means duplicates WILL happen: is the consumer idempotent, or does it double-charge?
  • Also test: out-of-order across partitions, malformed messages (dead-letter or blocked partition?), failure mid-processing, schema evolution
They'll ask next
  • How would you test that a consumer is genuinely idempotent?
  • The consumer commits its offset before processing. What's the bug?
Link to this question

What security testing should be part of a normal QA process?

Security Testingmidsenior

I'd separate what a QA/SDET function should own routinely from what genuinely needs a specialist penetration test — claiming you can replace a pen test is a red flag.

I'd separate what a QA/SDET function should own routinely from what genuinely needs a specialist penetration test — claiming you can replace a pen test is a red flag.

What belongs in the normal process, automated where possible:

Authorisation testing, and this is the highest-value item by far. Can user A access user B's resource by changing an ID? Can a standard user hit an admin endpoint directly, bypassing the UI that hides the button? This vulnerability class — broken access control — is consistently at the top of the OWASP list, it's easy to test, and it's easy to automate. Every resource, every role, every endpoint.

Input validation against injection: SQL injection, XSS payloads, command injection in any field that reaches a shell.

Authentication hardening: account lockout, whether error messages reveal that a username exists, password policy, session invalidation on logout, and whether an old token still works afterwards.

Dependency scanning in CI — most vulnerabilities enter through libraries, and this is nearly free to automate.

Secrets scanning in the repository, since a committed API key is a breach.

Transport and headers: HTTPS enforced, sensible security headers, no sensitive data in URLs or logs.

What I'd not claim to cover: sophisticated business-logic exploitation, chained vulnerabilities, or infrastructure attack surface. Those need a specialist, and pretending otherwise is how organisations get a false sense of safety.

Key points
  • Broken access control is the top item: swap IDs, use another user's token, hit admin endpoints directly. Easy to test, easy to automate
  • Input validation: SQL injection, XSS, command injection in anything reaching a shell
  • Auth hardening: lockout, username enumeration via error messages, session invalidation on logout
  • Automate dependency scanning and secrets scanning in CI — nearly free, and most vulns arrive via libraries
  • Be honest about the limit: chained exploits and infrastructure need a specialist pen test
They'll ask next
  • How would you automate an authorisation matrix across 40 endpoints and 5 roles?
  • A dependency scan reports 200 vulnerabilities. How do you triage?
Link to this question

What is observability, and how does it change how you test?

Observabilitysenior

Observability is the ability to understand a system's internal state from the signals it emits — traditionally logs, metrics and traces.

Observability is the ability to understand a system's internal state from the signals it emits — traditionally logs, metrics and traces. It's distinct from monitoring: monitoring tells you that something is wrong (the error rate spiked); observability lets you work out why without deploying new code to find out.

It changes testing in three ways.

Observability is itself testable, and mostly untested. If a service fails silently — no log, no metric, no alert — that's a defect, and I'd raise it as one. I'd verify that error paths actually emit something diagnosable, that trace IDs propagate across service boundaries (they very often don't, and you discover it during an incident at the worst possible moment), and that alerts actually fire when the condition they describe is true. An alert nobody has ever tested is a comforting fiction.

It makes tests diagnosable. If my end-to-end test fails and I have a trace ID, I can see exactly which service and which span was slow or errored. Without it, a failing E2E test just says 'something, somewhere, broke'.

It shifts some coverage right. At microservice scale you accept that not everything can be caught pre-production, so you invest in synthetic monitoring and production health checks to detect problems in minutes instead of days. Testing in production is not an admission of defeat — done deliberately, with feature flags and canaries, it's the mature response to a system too complex to fully replicate.

Key points
  • Monitoring says something is wrong; observability lets you find out why without deploying new code
  • Observability is testable and usually untested: silent failures, unpropagated trace IDs, alerts that never fire
  • A service that fails silently is a defect — raise it as one
  • Trace IDs make E2E failures diagnosable instead of 'something somewhere broke'
  • It shifts some coverage right: synthetic monitoring and canaries are a mature response to irreducible complexity, not a defeat
They'll ask next
  • How would you test that an alert actually fires?
  • What's the difference between logs, metrics and traces — and when do you reach for each?
Link to this question

How do feature flags change your testing strategy?

CI/CD Pipelinessenior

Feature flags decouple deploy from release. Code ships to production switched off, and is enabled later for some or all users.

Feature flags decouple deploy from release. Code ships to production switched off, and is enabled later for some or all users. That's enormously powerful, and it creates a genuine testing problem that teams consistently underestimate.

The problem is combinatorial explosion. With ten independent boolean flags there are 1,024 possible states of your application in production, and you have tested perhaps three of them. The bug that bites you is the interaction — flag A on and flag B off produces a state nobody tried.

How I'd handle it. Test the states that will actually exist: the current production configuration, and the target configuration you're rolling toward. Not the theoretical 1,024. Test both sides of any flag you're actively rolling out — on and off — because the off path must keep working during a rollback, and rollback is the whole point of the flag.

Treat flags as inventory with expiry. The real long-term danger isn't the new flag, it's the eighteen stale ones nobody removed, each doubling the state space and none of them tested. A flag should have an owner and a removal date, and I'd argue for that policy explicitly.

Use pairwise testing if the combination space genuinely matters, rather than pretending to test exhaustively.

The upside is real, though: flags enable canary releases and instant rollback without a redeploy, which is a far better safety net than any pre-release test suite. So this is a trade I'd take — with the discipline attached.

Key points
  • Flags decouple deploy from release — powerful, but they create combinatorial explosion (10 flags = 1,024 states)
  • Test the states that will actually exist: current production config and the target config
  • Always test both sides of a flag being rolled out — the off path is your rollback
  • The real danger is stale flags: each doubles the state space and none are tested. Flags need an owner and an expiry date
  • Pairwise testing where the combination space genuinely matters
  • Worth the trade: instant rollback without redeploy beats any pre-release suite as a safety net
They'll ask next
  • How would you enforce flag cleanup in practice?
  • A flag is enabled for 5% of users and errors spike. Walk me through your response.
Link to this question

What is canary or blue-green deployment, and how do you test with it?

CI/CD Pipelinessenior

Blue-green: two identical production environments. Blue is live; you deploy the new version to green, verify it, then switch the router to send all traffic to green.

Blue-green: two identical production environments. Blue is live; you deploy the new version to green, verify it, then switch the router to send all traffic to green. Rollback is switching back — near-instant, which is the entire value. The cost is running two full environments, and the hard part is shared state: the database is usually not duplicated, so your new version must be compatible with the schema the old one is still using.

Canary: you release the new version to a small slice of real traffic — 1%, then 5%, then 25% — while monitoring error rates, latency and business metrics. If the canary degrades, you roll back having exposed a fraction of users instead of all of them.

How this changes testing. Your monitoring becomes a test. The canary's value depends entirely on whether you can detect degradation quickly — which means the metrics and the automated rollback trigger are now part of your quality system, and they need to be tested. An automatic rollback that has never been proven to fire is not a safety net.

Automated smoke tests run post-deploy against the new version before traffic is shifted.

And it changes the philosophy honestly: you accept that some defects will reach production, and you optimise for detecting and reverting them in minutes. For a large distributed system, that's a more realistic goal than pre-release perfection — and admitting that in an interview is a senior signal, not a weakness.

Key points
  • Blue-green: two identical environments, switch the router, near-instant rollback. Hard part is the shared database schema
  • Canary: release to 1% → 5% → 25% of real traffic while watching error rate, latency and business metrics
  • Your monitoring becomes part of the test system — an automatic rollback that has never fired is not a safety net
  • Automated post-deploy smoke tests before traffic shifts
  • Philosophically: accept some defects reach production, optimise for detection and revert in minutes — that's realism, not defeat
They'll ask next
  • What metrics would trigger an automatic canary rollback?
  • How do you handle a database migration during a blue-green deploy?
Link to this question

How is testing a GraphQL API different from testing REST?

API & Contract Testingsenior

The differences flow from GraphQL's core design: a single endpoint, and the client decides the shape of the response.

The differences flow from GraphQL's core design: a single endpoint, and the client decides the shape of the response.

Status codes lose their meaning. GraphQL typically returns 200 even for errors, with the problem described in an errors array in the body. So a test asserting status == 200 proves nothing at all. You must assert on the response body — specifically, that errors is absent when you expect success, and present with the right code when you expect failure. Candidates who carry REST habits over write tests that pass while everything is broken.

The query space is effectively infinite. In REST, /users/1 returns a fixed shape you can assert against. In GraphQL, clients compose arbitrary field selections and nesting. You can't enumerate every possible query, so you test the queries your clients actually send, plus the schema itself.

New failure modes appear. Deeply nested queries are a denial-of-service vector — a client can request friends-of-friends-of-friends and bring the server down; test that query depth and complexity limits exist. The N+1 problem is endemic: a query for 100 users each with their posts can trigger 101 database calls, so test performance under realistic nesting, not just correctness. Field-level authorisation — a user might be permitted to query a User but not that user's email field; permissions are per-field, and that's a much finer-grained matrix than REST's per-endpoint model.

Introspection should usually be disabled in production, and that's worth checking.

Key points
  • GraphQL returns 200 even for errors — asserting on status code proves nothing. Assert on the errors array
  • The query space is infinite: test the queries clients actually send, plus the schema itself
  • Deep nesting is a DoS vector — verify depth and complexity limits exist
  • N+1 is endemic: 100 users with posts can mean 101 DB calls. Test performance under realistic nesting
  • Authorisation is per-FIELD, not per-endpoint — a much finer matrix than REST
  • Check that introspection is disabled in production
They'll ask next
  • How would you test that query depth limiting actually works?
  • How do you detect an N+1 problem in a GraphQL resolver?
Link to this question

What is chaos engineering, and would you use it?

System Design for Testsenior

Chaos engineering is the practice of deliberately injecting failure into a system to verify it behaves as designed under adverse conditions. Kill a node. Add 500ms of network latency.

Chaos engineering is the practice of deliberately injecting failure into a system to verify it behaves as designed under adverse conditions. Kill a node. Add 500ms of network latency. Make a dependency return errors. Fill a disk. Then observe whether the system degrades gracefully or collapses.

The philosophical point is important, and it's what the question is really probing. Every distributed system contains failure-handling code — retries, timeouts, circuit breakers, fallbacks — and that code is almost never tested. It was written on the assumption of a failure that has never been made to happen. So it is, in effect, unverified code sitting on the most critical path in your system, and you'll find out whether it works during your next incident.

Chaos engineering makes those failures happen deliberately, at a time you choose, with everyone watching — rather than at 3am on Black Friday.

Would I use it? Yes, but with strict preconditions, and I'd say so, because 'break things in production' without them is recklessness dressed as sophistication. You need: solid observability first, or you can't tell what happened. A hypothesis stated in advance — 'we believe killing one payment-service instance causes no user-visible failure' — so it's an experiment, not vandalism. A small blast radius, starting in staging, then a fraction of production traffic. And an abort switch.

Done that way, it's the only honest test of your resilience claims.

Key points
  • Deliberately inject failure — kill nodes, add latency, error out dependencies — and verify graceful degradation
  • The real insight: retry, timeout, circuit-breaker and fallback code is almost never tested and sits on the critical path
  • Better to trigger failure at a chosen time with everyone watching than at 3am on Black Friday
  • Preconditions: observability first, a stated hypothesis, a small blast radius, and an abort switch
  • Without those, it's recklessness dressed as sophistication
They'll ask next
  • What would you inject first in a system you'd just joined?
  • How do you get management approval to deliberately break production?
Link to this question

How do you decide whether a bug is worth fixing before release?

Role & Scopesenior

I frame it as a risk decision, and I'm careful about which parts of it are mine to make.

I frame it as a risk decision, and I'm careful about which parts of it are mine to make.

The inputs: user impact (how many users hit this path, and what happens to them), severity (data loss, security exposure and money problems are categorically different from cosmetic ones), workaround availability, fix risk (a rushed fix to a payment path the night before release can easily be more dangerous than the bug), and detectability (if it does reach production, will we know within minutes, or find out from a customer in three weeks?).

That last factor is undervalued. A bug that fails loudly and is instantly rollback-able is a very different risk from one that silently corrupts data.

My role is to make the risk legible — to state clearly what will happen, to whom, how often, and what the cost of fixing versus shipping is. The decision to ship with a known defect belongs to the business, not to me. Testers who try to own that decision end up as blockers and get routed around.

What I do insist on: the decision is explicit and recorded. A known bug that is consciously accepted, documented, and assigned a follow-up is a professional outcome. The same bug shipped because nobody wanted to raise it before the deadline is a failure of the process — and it's the one that ends up in the post-incident review.

My job is to ensure nobody can later say they didn't know.

Key points
  • Inputs: user impact, severity class (data/security/money vs cosmetic), workaround, fix risk, detectability
  • A rushed fix to a payment path the night before release can be riskier than the bug itself
  • Detectability is undervalued: a loud, rollback-able bug is a very different risk from silent data corruption
  • My job is to make the risk legible; the ship decision belongs to the business
  • Insist the decision is explicit and recorded — accepted risk is professional, unspoken risk is a process failure
They'll ask next
  • The business ships against your recommendation and it causes an incident. What do you do next?
  • How do you word a risk statement so it actually gets read?
Link to this question

A test fails intermittently in CI. Walk me through your debugging process.

Debuggingmidsenior

I'd resist the two bad instincts: adding a retry, or adding a longer wait. Both hide the problem, and if the underlying cause is a genuine race condition in the product, I've just concealed a…

I'd resist the two bad instincts: adding a retry, or adding a longer wait. Both hide the problem, and if the underlying cause is a genuine race condition in the product, I've just concealed a production bug from myself.

First, gather evidence rather than theories. Pull the failure artifacts — screenshot, DOM at failure, console logs, failed network requests, video or trace. Very often the answer is right there: a 500 from an API, a JavaScript exception, a redirect to an unexpected page.

Then quantify it. How often does it fail — 1 in 5, or 1 in 200? Does it correlate with a shard, a worker, a dataset, or with running in parallel versus alone? Correlation is the fastest route to a cause.

Then form a hypothesis and test it. Run it 50 times alone, then 50 times inside the full parallel suite. If it only fails in parallel, the cause is shared state or a data collision, not timing.

Then ask the crucial question: is the test wrong, or is the application genuinely non-deterministic? Sometimes the test is right, and you've found a real race condition — a double-submit that double-charges. Dismissing that as 'a flaky test' is how a real defect ships.

Only once I understand the cause do I fix it — and the fix should be a proper condition-based wait or an isolation fix, not a sleep and not a retry.

Key points
  • Don't add a retry or a longer wait — both hide the cause, possibly a real product race condition
  • Gather artifacts first: screenshot, DOM, console, failed network calls, trace. The answer is often already there
  • Quantify and correlate: how often, which shard, parallel vs alone, which dataset, what time
  • Test the hypothesis: 50 runs isolated vs 50 in parallel. Parallel-only failure = shared state, not timing
  • Ask whether the APP is non-deterministic — sometimes the test is right and you've found a real bug
  • Fix the cause with a condition-based wait or isolation, never a sleep or a retry
They'll ask next
  • It only fails when running in parallel. What does that tell you?
  • You've spent two days and can't reproduce it. What now?
Link to this question

How would you reduce a 3-hour test suite to under 30 minutes?

Test Architecturesenior

I'd measure before touching anything, because optimisation without data is superstition.

I'd measure before touching anything, because optimisation without data is superstition.

Profile first. Get per-test durations. Almost always a small minority of tests consume most of the runtime, and the fix is targeted rather than heroic.

Then, in order of impact per unit of effort:

Parallelise. This is usually the biggest single win, and the blocker is rarely infrastructure — it's test independence. Fix shared state and data collisions, then shard by historical duration so no worker is idle while another grinds. Three hours can become twenty minutes on twelve workers if the tests are genuinely independent.

Move tests down the pyramid. The 200 UI tests verifying business rules and validation messages are the real problem. Rewritten as API tests, they run in seconds rather than minutes, and they're more stable too. This is the fix with the best long-term payoff, and it's the one people avoid because it's real work.

Eliminate waste inside tests. UI logins replaced with API-injected sessions. State seeded via API rather than clicked through. Animations disabled. Headless. These are cheap and add up fast.

Delete tests. Duplicated coverage, and tests that have never failed in two years, are costing runtime for nothing.

Tier by trigger — not everything needs to run on every commit. A fast API smoke on each PR, the full suite on merge, the exhaustive matrix nightly.

The honest closing point: if the suite takes three hours, people have already stopped running it, so the real cost isn't the time — it's the safety you think you have and don't.

Key points
  • Profile first — a small minority of tests usually consume most of the runtime
  • Parallelise: the blocker is test independence, not infrastructure. Shard by duration, not by file
  • Move UI tests verifying business rules down to the API layer — best long-term payoff, and people avoid it because it's real work
  • Cut waste: API-injected sessions instead of UI logins, API-seeded state, no animations, headless
  • Delete duplicated and never-failing tests; tier by trigger (PR / merge / nightly)
  • Real cost of a 3-hour suite isn't time — it's that nobody runs it, so the safety is imaginary
They'll ask next
  • How do you decide a test is safe to delete?
  • You parallelise and now 30 tests fail randomly. What happened?
Link to this question

How would you introduce automation to a team that has none?

Role & Scopesenior

The failure mode I'd avoid is the one most people walk into: spending three months building an elaborate framework, delivering it, and finding nobody uses it and nobody trusts it.

The failure mode I'd avoid is the one most people walk into: spending three months building an elaborate framework, delivering it, and finding nobody uses it and nobody trusts it. Credibility has to come before architecture.

Understand first. What hurts? Where do defects escape? What does the release process actually cost in human hours? Talk to developers and support, and look at the escaped-defect history — that data tells you where automation would pay, and it also gives you the argument you'll need later.

Then deliver something small and undeniably valuable, fast. A smoke suite covering the two or three critical journeys, running automatically in CI on every merge, finishing in five minutes, and green. That's it. It catches real breakage within weeks and it earns the trust that funds everything else.

Then make it visible. When it catches a regression before release, say so — specifically and with the cost avoided. Automation programmes are killed by invisibility, not by technical failure.

Then expand by risk, layering downward: push checks to the API rather than building a UI monolith, which is the mistake that eventually collapses under its own maintenance cost.

Then hand it over. The end state isn't 'the SDET writes all the tests' — it's developers writing and trusting their own, with me owning the framework and the pipeline. That's leverage, and it's the difference between an automation engineer and an automation bottleneck.

Throughout, I'd be candid that automation won't reduce headcount. Promising that is how these programmes lose credibility the moment it isn't true.

Key points
  • Avoid the classic failure: three months building a framework nobody uses or trusts
  • Understand first — escaped-defect history tells you where automation pays and gives you the business case
  • Deliver a small, fast, green smoke suite in CI early: credibility before architecture
  • Make wins visible — automation programmes die of invisibility, not technical failure
  • Expand by risk, pushing checks down to the API rather than building a UI monolith
  • End state: developers write and trust their own tests; you own the framework and pipeline. That's leverage
They'll ask next
  • What would you have working at the end of your first month?
  • The developers won't write tests. What do you actually do?
Link to this question

How do you test a machine learning or AI-powered feature?

System Design for Testsenior

The fundamental break with normal testing is that the output is probabilistic, not deterministic. There is often no single correct answer, so assertEquals(expected, actual) simply doesn't apply.

The fundamental break with normal testing is that the output is probabilistic, not deterministic. There is often no single correct answer, so assertEquals(expected, actual) simply doesn't apply. You have to shift from asserting exact outputs to asserting properties, bounds and behaviours.

What I'd actually test:

The system around the model, which is usually where the real bugs are and where testing is most neglected. Does the API validate input? What happens on an empty input, an enormous one, an adversarial one, a different language? What happens when the model times out or is unavailable — is there a graceful fallback, or does the whole feature 500? Is the response cached correctly? These are deterministic, testable, and genuinely valuable.

Properties and invariants rather than exact values. A summariser's output must be shorter than the input and must not be empty. A classifier's confidence must be between 0 and 1. A recommender must never recommend an out-of-stock item. These hold regardless of the model's specific output.

Quality against a golden dataset, with a threshold rather than an assertion: accuracy on this held-out set must not drop below X. That converts a probabilistic system into a testable gate, and it catches regressions when the model is retrained.

Bias and fairness across demographic slices — a real, and frequently a legal, requirement.

Safety and adversarial inputs — prompt injection for anything LLM-based, and unsafe outputs.

And monitor in production, because model performance drifts as the world changes, even though the code is unchanged. That's a failure mode no pre-release test can catch.

Key points
  • Output is probabilistic — replace exact-value assertions with properties, bounds and behaviours
  • Test the system AROUND the model: input validation, timeouts, fallback when the model is down, caching. Deterministic and neglected
  • Invariants: summary shorter than input, confidence in [0,1], never recommend an out-of-stock item
  • Golden dataset with a threshold gate — catches regressions when the model is retrained
  • Bias/fairness across slices, and adversarial/prompt-injection safety
  • Monitor in production: models drift as the world changes even when the code doesn't
They'll ask next
  • The model is retrained weekly. How do you gate that in CI?
  • How would you test an LLM-powered chat feature for prompt injection?
Link to this question

What would you do in your first 90 days as an SDET on a new team?

Role & Scopesenior

I'd resist the urge to arrive with opinions, because a plan formed before understanding the context is just a plan to be wrong loudly.

I'd resist the urge to arrive with opinions, because a plan formed before understanding the context is just a plan to be wrong loudly.

Weeks 1–3: understand. Learn the product as a user. Read the architecture. Sit with developers, with support, and with whoever handles incidents. Study the escaped-defect history and the last few post-incident reviews — that's the most honest document about where quality actually fails, and it's usually not where people say it does. Run the existing test suite and see what it does and doesn't cover, and how much of it anyone trusts.

Weeks 3–6: find the constraint. There is usually one dominant bottleneck: a three-hour flaky suite nobody believes, no API test coverage, a manual regression eating a week per release, or an environment that's down half the time. Identify it with data rather than instinct, and get agreement that it is the constraint.

Weeks 6–12: deliver one visible, undeniable win against that constraint. Not a grand framework rewrite — one thing that measurably changes the team's daily experience. Quarantine the flakes and make the pipeline green and trusted again. Or build the API smoke suite that cuts regression from three days to two hours.

Then make the result visible, and use the credibility to fund the next thing.

What I would not do is rewrite the framework in month one because I dislike the previous engineer's choices. That's the fastest way to spend six months and arrive with nothing anyone asked for.

Key points
  • Weeks 1–3: understand — product, architecture, developers, support, escaped defects, post-incident reviews
  • Post-incident reviews are the most honest document about where quality actually fails
  • Weeks 3–6: find the single dominant constraint with data, and get agreement that it IS the constraint
  • Weeks 6–12: deliver one visible, undeniable win against it — not a grand rewrite
  • Make the result visible, and use the credibility to fund the next thing
  • Don't rewrite the framework in month one because you dislike the previous engineer's taste
They'll ask next
  • You find the suite is 80% flaky. Is that your constraint, or a symptom?
  • What if leadership wants a big framework rewrite and you disagree?
Link to this question

How do you know your test suite is actually good?

Coverage & Qualitysenior

Not by counting tests, and not by coverage percentage. Both are inputs, and both are trivially gameable.

Not by counting tests, and not by coverage percentage. Both are inputs, and both are trivially gameable.

The measures I actually trust:

Escaped defects. The number of bugs that reach users. This is the outcome the suite exists to prevent, and it's the closest thing to a scoreboard. Trending down over quarters is the strongest evidence you have.

Does it catch real regressions? I'd audit this directly: over the last six months, how many genuine defects did the suite catch before release? A suite that has never caught anything is either protecting a codebase nobody changes, or it isn't asserting on anything meaningful.

Mutation score on critical paths — because it's the only metric that answers would these tests catch a bug if one were introduced? High line coverage with a low mutation score means expensive tests that execute code and verify nothing.

Flake rate. A suite people don't trust has zero value regardless of coverage, because red stops meaning anything.

Feedback speed. If it takes three hours, developers have stopped waiting for it, and it's no longer influencing their behaviour.

Diagnosability — when it goes red, how long does it take to know why? A suite that takes an hour to triage per failure is imposing a tax nobody accounted for.

The uncomfortable question I'd put to any team, including my own: if I deliberately introduced a bug into the payment logic right now, would the suite catch it? If nobody is confident of the answer, coverage figures are just decoration.

Key points
  • Not test count, not coverage % — both are inputs and both are gameable
  • Escaped defects trending down is the real scoreboard
  • Audit directly: how many genuine regressions did the suite catch in six months? Never catching anything is a red flag
  • Mutation score on critical paths — the only metric that asks whether tests can catch an introduced bug
  • Flake rate and feedback speed: an untrusted or slow suite has near-zero value regardless of coverage
  • The question that settles it: if I introduced a bug in payments right now, would the suite catch it?
They'll ask next
  • How would you actually run that experiment safely?
  • Your suite has never caught a regression. What does that tell you?
Link to this question

Tell me about a time your testing missed something important. What happened?

Role & Scopemidsenior

This question is testing ownership and honesty, not whether you're infallible. Everyone has shipped a bug; the ones who claim otherwise are either lying or haven't shipped much.

This question is testing ownership and honesty, not whether you're infallible. Everyone has shipped a bug; the ones who claim otherwise are either lying or haven't shipped much.

The structure that works: a real incident, your genuine contribution to it, what you learned, and — crucially — the systemic change you made so it couldn't recur.

A credible shape: a defect escaped to production because coverage assumed a condition that reality didn't respect — a currency, a timezone, a role, a data state that never occurred in the test environment. Users hit it. There was an impact you can state plainly.

What matters is the second half. Not 'I was more careful afterwards' — that's not a fix, it's a promise. The fix is systemic: an automated test added for that exact case; the class of gap identified (we never tested with non-UTC timezones at all) and covered; the test environment changed so that data state can now exist; the escaped-defect review made a standing practice so the next gap is found by process rather than by luck.

Two things I'd avoid. Blaming others — 'the developers didn't tell me' reads as someone who won't own outcomes. And choosing a trivial example to look safe, which just signals that you either haven't done anything consequential or you're not being straight with me.

The strongest version admits real impact, owns the gap, and shows the process is now better because of it.

Key points
  • The question tests ownership and honesty, not perfection — everyone has shipped a bug
  • Structure: real incident → your genuine contribution → what you learned → the systemic change you made
  • 'I was more careful afterwards' is a promise, not a fix. Name the process change
  • Identify the CLASS of gap (we never tested non-UTC timezones at all), not just the single case
  • Don't blame others, and don't pick a trivially safe example — both are read as evasions
They'll ask next
  • What would have caught it earlier?
  • Has that class of defect happened again since?
The trap

Answering 'I can't think of one' or picking something risk-free. Both are transparent, and both cost you more than an honest failure would.

Link to this question

Where do you see the QA role going, and how are you preparing?

Role & Scopemidsenior

The clearest trend is that the separate QA department is disappearing, and quality is being absorbed into engineering teams.

The clearest trend is that the separate QA department is disappearing, and quality is being absorbed into engineering teams. That's not the death of testing — it's the death of testing as a downstream inspection stage. Someone still has to decide what's worth testing, build the infrastructure that makes testing cheap, and hold the risk judgement. That work is becoming more valuable, not less, because systems are getting more complex.

What that means concretely: the role is bifurcating. Pure manual execution is genuinely shrinking — and I'd be honest about that rather than pretend otherwise. What's growing is the engineering side (frameworks, pipelines, tooling, test infrastructure) and the judgement side (risk analysis, exploratory testing, quality strategy, knowing what not to test).

AI is the live question. It's already good at generating test cases and boilerplate, and it will get better. What it is not good at — and what I don't expect to be automated soon — is deciding what matters, understanding a business's actual risk, and exercising judgement about what to ship. So the skill to invest in is the judgement, and to let AI take the typing.

How I'm preparing: deepening the engineering side, because that's the part I can be out-competed on, and deliberately keeping the testing judgement I already have, because that's the part that's hardest to replace and the part most engineers lack.

Key points
  • The separate QA department is fading — quality is being absorbed into engineering teams
  • That kills testing as a downstream inspection stage, not testing itself
  • The role is bifurcating: engineering (frameworks, pipelines, infrastructure) and judgement (risk, exploratory, strategy)
  • Manual execution is genuinely shrinking — say so honestly rather than pretending
  • AI is good at generating cases and boilerplate; it's bad at knowing what matters and what to ship
  • Invest in the engineering skills you can be out-competed on, and keep the judgement most engineers lack
They'll ask next
  • How are you using AI in your own testing work today?
  • What skill are you deliberately building right now, and why that one?
Link to this question