Stop Asking QA When We’re Deploying

Every release, it’s the same question. Not from one person — from product, from engineering leadership, sometimes from someone who wasn’t even in the sprint planning meeting. “When are we deploying to production?” They ask it like it’s a simple lookup, like there’s a spreadsheet cell somewhere with the answer in it. And they ask me. Not the devs who are three days late on Code Complete. Me.
I’ve been asked this by a VP of Product, a Product Manager, a VP of Engineering, a Dev Lead. Individual devs who were puzzled about why we weren’t on staging yet would DM me directly for updates. Even people on my own QA team, who should’ve understood our process better than anyone, would assume I was the one blocking the release for no reason.
I get it. QA is the last stop before the button gets pushed, so it feels like QA should know when the button gets pushed. But that logic only works if you believe testing is a formality — a box that gets checked once devs are “done.” It falls apart the second you remember that testing produces information, and information takes time to gather, and some of that time isn’t even in QA’s hands.
The handoff that isn’t a handoff
Here’s the mental model I think a lot of product and engineering leaders are running, whether they’d say it out loud or not: devs build the thing, devs hit Code Complete, the build lands in QA’s lap, and from that point forward it’s QA’s problem. Bugs are QA’s problem. Timeline is QA’s problem. If the release slips, it slipped because QA was slow.
I know they think this, because I hear it. Over and over, while devs are still building, someone will say QA still has tickets to test, like everyone working in parallel means the deadline never really applied to anyone in particular. Code Complete never gets enforced. There’s no real pressure on the devs to hit it. But the second delivery is late, suddenly there’s a big meeting. And the funny part is, they’ll tell you constantly: if you think the release is going to slip, flag it early. So you do. And then the meeting is about how you shouldn’t be sending warning signs unless you’re 100% sure. Bet your house on it or don’t say anything at all. Isn’t that ridiculous?
Nobody actually asks the devs when their tickets will be deployed to the QA environment. They just wait. And when those tickets show up late — which, let’s be honest, they usually do — nobody asks why. The clock only starts mattering once it’s QA’s turn to hold it.
That’s not how sprints work. Everyone on that ticket is part of the delivery timeline: the dev writing the code, the dev fixing what QA finds, and QA testing it. All three of those are the release. Not two of them plus a black box.
What a date actually needs to be built from
I’ve tried giving hour estimates for testing before. “This will take 6 hours to test.” People stare blankly, or worse, they nod and then get upset later when it took 10. Hours don’t mean anything to someone who doesn’t know what’s inside those hours. What they want is a date. But a date without the information behind it is just a number someone can later use against you.
So here’s what I think actually needs to happen instead. For every ticket in scope for a release, you’re tracking how much testing information exists for it. Not a vague “in progress” — an actual count. Say a ticket has 10 test cases scoped. 10 out of 10 passing means you have full information and it’s good news. 7 out of 10 with 3 blocked means you have partial information, and the 3 you’re missing are the risk. 0 out of 10 means you know nothing, and that’s a real state a release can be in — the business can absolutely decide to deploy with zero QA information, they just need to know that’s what they’re deciding.
I remember feeling genuinely proud of a product manager once, the moment he started asking how the features actually worked instead of asking for a date, and said he’d be comfortable shipping as long as the happy paths held up. That’s a real business decision against real risk. It meant we could deploy on time, because he wasn’t sitting around waiting for devs to fix low-priority bugs or waiting on QA to retest something that didn’t matter. He looked at the board, saw a release that didn’t meet the full readiness criteria, and decided he was fine with that. Every time he made that call, it went fine after we shipped. Maybe one day it won’t. But I wouldn’t blame him if it doesn’t, because he made that call with the best information QA could give him. If we all had infinite time and zero business pressure, none of us would ever ship anything short of perfect. Nobody has that.
The number of checks done isn’t the whole story though. A failed test isn’t just “one more thing to fix” — the second it fails, the timeline stops being a QA question and becomes a product and engineering question. Now you need someone from product to prioritize it against business goals, and engineering to weigh the technical risk of fixing it versus not. If they agree it needs a fix, engineering has to say when that fix lands, because until it does, QA is stuck. Same with a blocked ticket. QA can’t manufacture an unblock date out of nowhere. That date belongs to whoever owns the blocker.
This is the part that gets missed constantly. The estimate isn’t “QA says X date.” It’s “here’s what we know, here’s what we don’t, and here’s who owns closing the gap on what we don’t.”
A fix isn’t just a retest
There’s one more thing that trips people up, especially product leaders who assume testing gets faster the closer you get to release. Most people picture a bug fix like this: QA found it, dev fixed it, QA reruns that one case, done. That’s not what’s actually happening most of the time.
Most bugs get found one of two ways: a test case ran and the result didn’t match what we expected, or a test case couldn’t even finish because something broke partway through and blocked the rest of the steps. Either way, that’s coverage working the way it’s supposed to, not a gap. But the bug itself is new information — it just showed everyone a specific way the feature can break that nobody had written a case for before. So the fix needs more than a retest of whatever failed or got blocked. It usually needs a new case for that exact failure, and sometimes for the ones that look similar to it, because now you know to look. Say two of your ten scoped test cases are blocked by an open bug. The instinct is to think that once the bug’s fixed, you clear those two and you’re done. But depending on the size of the fix, you might need to retest all ten, not just the two, because the change could’ve broken something that was already passing. And if the bug revealed a scenario nobody had scoped, you might need to write and run cases you didn’t have before, on top of the retest.
I’ve worked with enough spaghetti code to know one change can ripple through an entire feature. One button with a handful of states tied to date configurations regressed over and over, fix after fix, because there were no unit tests to catch the breakage before it ever reached QA. A one-line change could mean retesting the whole feature, because that single line had dependencies running through half the codebase. And every one of those regressions taught us a scenario we hadn’t scoped the first time, so the test suite kept growing, not shrinking.
If you don’t want QA retesting or expanding coverage on something they consider risky, that’s a legitimate business decision to make — you can choose to limit it. But most leaders are too afraid to cut testing themselves. It’s easier to offload that pressure downstream and let QA absorb the blame for “taking too long.” Testing a fixed bug is not cheaper than testing it the first time. Sometimes it’s more expensive, because now you know things you didn’t know yesterday.
This is where it stops being theoretical. A VP of Product once pushed to cut a day from our staging estimate, on the logic that staging should always be faster than QA — same build, no new code, why would it take as long. On paper he wasn’t wrong. But at the time we weren’t deploying stable builds to staging. Features got rushed over before they were even done, because the assumption upstream was that you just get through staging and you’re basically finished. So staging was quietly doing testing that should’ve happened in QA, and I told him it would take the same amount of time, because it would. He wasn’t trying to understand the release, he was trying to find a day to take back. I wasn’t going to hand him one built on a number that didn’t reflect what we were actually doing.
Once we added the discipline to keep tickets in QA until they were actually ready, staging did get lighter, exactly the way he assumed it always should. But total time didn’t drop. The testing staging used to absorb just moved back to where it belonged. Same work, now visible in one place instead of hidden across two. He never came back and asked why QA suddenly “took longer.” He just stopped asking me to explain staging.
Doing the actual math
So what does this look like when someone’s standing in front of you asking for a date? A few real shapes a release can take, and different things you should be saying for each.
Say a release has 40 tickets and 220 checks planned across all of them. You’ve run 220 out of 220, everything’s working, zero open bugs. This is the easy one. “We’ve checked everything we planned to check — 220 out of 220 — and it’s all working, nothing serious open. The release meets the deployment readiness criteria today. Assuming product and engineering are ready to release, we could deploy today.” Notice the qualifier in there — assuming product and engineering are ready. You don’t own that call. You don’t know if there’s a deployment freeze, a known outage risk, or peak traffic happening right now that makes today a bad day to ship. That’s their call to make, not yours to assume.
Now say you’ve checked 205 out of 220, with two serious bugs open on two tickets — the 15 remaining checks are stuck behind those. The other 205 are solid, real information, no reason to doubt them. Here’s where the conversation has to change shape: “We’ve checked 205 out of 220. The remaining 15 are unknown, because two serious bugs on tickets X and Y are stopping us from checking them. We can’t say when we’ll have that information yet — that depends on product deciding whether these bugs are worth fixing before we ship, and engineering telling us when a fix would land if so. Once we have that fix date, checking the rest takes about four hours on top of it, or closer to a day if the fix touches shared code and we need to recheck everything we already confirmed working.” Notice what that sentence does — it hands the next decision to the people who actually own it, and it tells them the cost of each path instead of hiding it. It also doesn’t say “ready by” anything. It says when we’ll know, which is a different thing than when we’re ready to deploy. It’s the same move my PM made with the happy-path call, just with the fix required instead of accepted. Bugs don’t automatically mean a delay. They mean a decision, and it’s not QA’s decision to make.
Third shape: 190 out of 220 checked, but the missing 30 aren’t broken, they’re checks QA literally hasn’t been able to run yet, because three tickets are still sitting undeployed to the QA environment, or a dependency the feature relies on isn’t in place. This is the one people mix up with a bug-blocked release, and it’s not the same thing at all — there’s no bug to prioritize, there’s nothing for QA to even look at. “We’ve checked 190 out of 220. The remaining 30 aren’t broken or unknown in the usual sense — we haven’t been able to look at them at all, because three tickets still haven’t been deployed to QA. I can’t tell you when we’ll have that information until whoever owns that deployment gives me a date, because our clock on those three hasn’t started.” That line matters because it puts the missing days exactly where they belong, on the desk of whoever hasn’t shipped to QA, not on QA for “still testing.” If those same tickets are deployed and just waiting their turn in the queue, the answer looks completely different: “it’ll take about an hour to check the remaining 14%,” because now it’s just work, not a mystery.
The pattern underneath all of it: how much we’ve checked, what’s causing the gap, and who owns closing that gap. And notice none of these scripts say “ready by” — they say when we’ll have information. Whether that information clears the readiness criteria is a separate question, answered once you actually have it. Once you can say those things out loud, “when are we deploying” stops being a QA question and starts being whatever question it actually is.
Templates you can actually use
Here’s the shorthand version of all of it, written so you can copy it, drop in your own numbers, and send it. Five shapes, because most releases fall into one of these or some mix of them. Every one of them still gives an actual date, because that’s what people need to plan around — QA should never have to dodge the question entirely. The date is just always built the same way: when we’ll have the last piece of information, plus “assuming it still meets the readiness criteria once we’re there.” That’s not a hedge. That’s the honest shape of the answer, every time.
All green. We’ve checked everything we planned to, all of it’s working.
We’ve checked everything planned for this release — [X] out of [X] — and it’s all working, nothing serious open. Based on what we know, the release meets the deployment readiness criteria today. Assuming product and engineering are ready to release, that means we could deploy [today / date]. Flagging that the go/no-go call on timing (freezes, peak traffic, other risk) belongs to you, not QA.
Bugs are the gap. We’re missing information because something’s broken and needs a decision.
We’ve checked [X] out of [Y] scenarios we planned to check ([X]%). The remaining [Y-X] scenarios are broken or unknown, because [N] serious bug(s) on ticket(s) [IDs] are stopping us from checking them. We can’t say when we’ll have that information yet — that depends on product deciding whether these are worth fixing before we ship, and engineering telling us when a fix would land if so. Once we have a fix date from engineering, checking the rest takes about [time] on top of it, or [longer time] if the fix touches shared code and we need to recheck everything we already confirmed working. Assuming the release still meets the readiness criteria at that point, that’s when we’d be ready to deploy.
Blocked, not checked. We haven’t been able to look at something yet, not because anything failed.
We’ve checked [X] out of [Y] scenarios we planned to check ([X]%). We haven’t been able to look at the remaining [Y-X] at all — they’re blocked on [dependency / undeployed ticket / environment issue], owned by [team/person]. We can’t say when we’ll have that information until they give us a date. Once we have that date, checking the rest takes about [time] on top of it. Assuming the release still meets the readiness criteria once we’ve checked everything, that’s when we’d be ready to deploy.
Just needs time. Nothing’s broken, nothing’s blocked, we’re just still working through it.
We’ve checked [X] out of [Y] scenarios we planned to check ([X]%). Nothing’s broken and nothing’s blocked, we’re just still working through the rest. We expect to have full information by [date/time], assuming nothing new comes up. Assuming it still meets the readiness criteria at that point, that’s when we’d be ready to deploy.
The combo. Most real releases end up here. Say all of it, don’t average it into a single fake number.
We’ve checked [X] out of [Y] scenarios we planned to check ([X]%). Of what’s left: [A] are broken or unknown because of an open bug on [ticket], and need a product and engineering call before we can get a fix date; [B] haven’t been looked at yet because they’re blocked on [dependency], owned by [team]; [C] are simply still in progress and on track for [date/time] on their own. Once we have a fix date for [A] and an unblock date for [B], checking those takes about [time] on top of whichever of the two lands last. Assuming the build still meets the readiness criteria once everything’s checked, that’s when we’d be ready to deploy — the date depends on whichever of the three pieces resolves last, and two of them aren’t QA’s to set.
What the readiness check actually looks like
None of this works without agreement up front on what “ready” means. My team’s version: every ticket in scope is in Deploy to Staging, meaning it’s been tested for feature correctness, checked against the spec for gaps, and covered by regression. Targeted regression on the impacted areas is complete. No high severity bugs are open. Hit all three, and staging is ready. Once you’re there, you sanity check the tickets actually deployed the way they’re supposed to — visible, hidden behind a flag, whatever the ticket called for — run core regression, and if the same three criteria hold, you’re ready for production.
That’s the framework. It’s not complicated. What’s complicated is that most teams never write it down, so every release becomes a negotiation from zero.
The thing nobody wants to say in the room
The real issue isn’t that QA can’t give a date. QA can give you a very precise picture of what’s known, what’s unknown, and who’s holding the unknowns. The issue is that leadership doesn’t want a picture — they want an answer that lets them stop thinking about it. And when that answer turns out to be wrong, it’s a lot more comfortable to say QA missed a deadline than to admit nobody in the room asked the right questions at Code Complete.
You can have decades of engineering leadership experience and still ask QA “when are we deploying” like it’s a status update instead of a decision the business has to make with the information in front of it. That’s not a QA problem. It just smells like nobody wanting to own the decision.
