Writing Tests That Actually Catch Things
Killer Test Questions are the brutal edge-case prompts you throw at your code to see if it holds up. They aren't pretty. They won't feel good. A well-crafted question like "what happens when the user submits an empty but whitespace-filled payload during a network retry" is the kind of thing that separates code that ships from code that quietly breaks in production three weeks later. Most people write happy-path tests and call it a day. That's how debt accumulates. I learned this the hard way back in 2019 when a payment endpoint that looked perfectly fine in CI failed only under a very specific race condition: two concurrent requests sharing a transaction ID, both passing validation, and then both writing to the same row. The existing test suite had zero coverage for concurrency. I wrote a small script using fake time and interleaved request dispatching, and it caught the bug within an hour. The fix took three days because we had already shipped the wrong assumption deeper into the architecture.
How to Write Killer Test Questions That Actually Matter
Start by mapping the boundaries. Not the function boundaries—those are obvious. Map the input space boundaries: null values, oversized payloads, malformed timestamps, expired tokens, locale differences in decimal separators, timezone crossings, permission escalation paths. For each boundary, ask a single question: can the system handle a realistic but broken version of this? The format doesn't need to be elaborate. A plain-text list works fine. Group questions by failure mode. A typical team I know produces around twelve to twenty per feature before merge. More than that and you're usually duplicating effort or hunting for edge cases that will never occur in the deployed environment. Fewer than six and you almost certainly missed something that matters. Here's what the process looks like in practice. You write a test case, run it, and it passes. Good. Then you ask: what assumption did I just make that would break this? If the test passed because a mock swallowed an error, the killer question is "what happens when the service we're mocking returns a non-2xx status code." If the test passed because you stubbed the database, the killer question is "what happens when two rows collide on the unique constraint." That's how you find the gaps. It's tedious. It works.
For Killer Test Questions, I recommend keeping them in the same file as the test suite but clearly separated from normal assertions. Prefix them with a tag like [KILLER] so you can run them independently. That way you can run the full suite in CI and the killer set locally before a release, cutting review time from what used to take two hours to about fifteen minutes of actual manual inspection.
Get the Full Details

Where This Breaks Down
Killer Test Questions don't scale to distributed systems the way people think they do. Concurrency bugs, eventual consistency issues, and network partitions are fundamentally harder to reproduce with a question-based approach. In those cases you need property-based testing or chaos engineering, not a well-phrased question. I've seen teams treat the killer question framework as a silver bullet and ship microservices with no resilience testing. That's a mistake. Use the questions for application-layer logic. Use different tooling for infrastructure-layer problems. There's also a diminishing return problem. The first pass of questions catches eighty percent of the real bugs. The second pass catches another ten. The third pass mostly finds theoretical scenarios that would require a specific sequence of failures across unrelated services. Those matter less than you'd expect unless your deployment topology is genuinely unpredictable. The workflow I use now takes about forty minutes per medium-complexity feature. You spend ten minutes listing boundaries, twenty minutes writing the questions as test stubs, and ten minutes running the killer subset and iterating on the ones that fail. If it takes longer than that, you're probably overthinking the problem or including too many low-probability scenarios that don't belong in the suite.
A Few Things Beginners Miss
Most people write killer questions about the function they're testing. They should be writing them about the contract the function sits inside. A validation method might pass every boundary check on its own and still produce wrong behavior when called from the parent controller because the parent silently swallows an exception that the validator was supposed to surface. Test the interface, not just the implementation. Another common error is treating a killer question as solved once it fails and gets patched. The question itself stays in the suite. That single question becomes regression protection for whatever you introduced. I keep a running log of every killer question that caught a real bug and what it found. Reviewing that log quarterly tells you whether your test strategy is actually improving or just getting louder. If you're starting from scratch and want a practical example, look at how you'd test a simple user registration flow. Your normal tests check that valid input creates an account. Your killer questions ask what happens with a duplicate email across two simultaneous requests, what happens when the password hash function receives an input over sixteen kilobytes, what happens when the confirmation email bounces and the retry loop triggers again during a database migration, and what happens if the authorization service returns a timeout instead of a definitive allow or deny. Each of those is a question. Each deserves a test. None of them is interesting until something actually breaks because you didn't write it.
The approach is unglamorous. That's the point. But I've never seen a team that adopted it consistently end up with the same class of production incidents that used to eat their weekends.
