When to NOT Use AI: The Tasks It Makes Slower
A model is fast at generation and adds nothing to verification — so its value is (time to write − time to verify − the round trip). Seven categories where that maths goes negative, and why it still feels fast the whole time.
Hessam Rastegari
Senior Android Developer · 12 years shipping Android
TL;DR — The value of reaching for a model is time to write − time to verify − the round trip. It accelerates only the first term and contributes nothing to the second, so on any task where verification is the expensive half, the maths goes negative. Seven categories where it does: when the specification is the hard part, small edits in code you know, debugging state you can't paste, load-bearing code that's expensive to check, learning a codebase you'll own, decisions where "close enough" compounds (naming, API shape, data model), and genuinely new API surface. Plus one that isn't about the task at all — when you're tired.
The formula
Everything in this post follows from one asymmetry: a model makes generation faster and verification no faster at all. You still have to read the output, decide whether it's right, and own it in production.
value = time to write − time to verify − round-trip cost
The round-trip cost is the part nobody counts: describing the problem precisely enough, reading the response, re-prompting when it missed a constraint, then context-switching back to what you were doing. It's small but fixed, and on small tasks a fixed cost is the whole budget.
So the useful question is never "can it do this?" — it obviously can, and it will produce something confident either way. The question is: is writing this the expensive half?
1. When the specification is the hard part
If you cannot state what "correct" means, you cannot check the answer — and now you're reviewing plausible code against a standard you haven't defined. This is the most expensive failure mode on the list, because plausible code passes casual review.
The tell: you're writing a long prompt and discovering, as you write it, that you don't know what the feature should do at the boundaries. That discovery is the actual work. Do it in a design doc, or on paper, or in a conversation with the person who wants the feature.
Once the spec exists, generating the code is often a genuinely good use of the tool. The order matters, not the tool.
2. Small edits in code you already know
Renaming a concept across four files. Adding a parameter. Extracting a function. If you can type it in two minutes, the loop of describe → read → verify is three, and you've also lost your place in what you were doing.
There's a rough threshold worth internalising: familiar code, under ~20 lines, well-understood change → just type it. Your IDE's refactoring tools are deterministic, instant, and correct, which is three advantages a model doesn't have.
3. Debugging with state you can't paste
A race condition. An ANR on one OEM's Android 13 build. A memory leak visible only in a heap dump. A failure that needs the actual device, the actual data, the actual timing.
The model has your code and not your state, so it does the only thing available to it: it pattern-matches to the most common cause of failures that look like yours, and states it confidently. Sometimes that's right. When it's wrong it's expensive, because a specific, plausible, wrong hypothesis is much more distracting than no hypothesis at all — you'll spend an hour disproving it instead of five minutes reading the trace.
Where it is useful in debugging: after you have the evidence. "Here's a 3,000-line thread dump, group the threads by what they're blocked on" is traversal, and traversal is its strength.
4. Load-bearing code that's expensive to verify
Money arithmetic. Permission and entitlement logic. Anything cryptographic. A database migration that will run exactly once, against real user data, at 3am.
Generation is cheap here and verification is brutal — you need to reason through every edge case regardless of who wrote it, and reading code you didn't write is slower than reading code you did. The economics are inverted relative to boilerplate.
This isn't "never". It's "the generated draft saves you very little, and the confidence it creates is a liability." If you use it, review it as adversarially as you'd review a stranger's PR touching billing.
5. A codebase you're going to own for years
There's a real trade here, and it's easy to get wrong because the cost is deferred.
The struggle of working something out is how it gets encoded. Skipping it gives you working code today and, in six months, an on-call engineer who has never built a model of this system — and that engineer is you, at 3am, with a customer waiting.
For code you'll own long-term, use it to traverse (find the file, trace the path, summarise the history) and write the logic yourself. For code you'll touch once and never again, the trade goes the other way.
6. Anywhere "close enough" compounds
Naming. Public API shape. The data model. Module boundaries.
A model produces the statistically typical version of these, which is frequently fine and occasionally subtly wrong for your domain — and the failure doesn't stay contained. A slightly-off abstraction gets six files built on top of it before anyone notices, and by then the fix is a refactor rather than a rename.
The general rule: the more expensive a decision is to reverse, the less of it should be delegated. Reversibility, not difficulty, is the right axis.
7. Genuinely new surface
A library released after the model's training data. Your company's internal DSL. A framework that changed its API in a major version.
The failure here is specific and worth recognising by sight: it produces fluent, idiomatic, confident code against APIs that do not exist. It doesn't degrade gracefully into "I'm not sure" — it interpolates, and the interpolation looks exactly like the real thing.
If you're using it against unfamiliar new surface anyway, keep a fast feedback loop — compile after every suggestion. A tight loop converts this from a time sink into a decent way to explore.
And the one that isn't about the task: when you're tired
Reviewing plausible code takes more discipline than writing code. At 6pm on a Friday you have the least of it, and generation is precisely the tool that requires the most.
I've merged worse code at the end of a long week with a model than without one, and the mechanism is obvious in hindsight: it removed the friction that would otherwise have made me stop.
Where it's overwhelmingly worth it
For balance, because this isn't an argument against the tool:
- Boilerplate with a compiler check — mappers, DTOs, parcelables, test fixtures.
- Traversal in a large codebase — find every caller, trace this path, tabulate this class.
- First drafts of tests, especially the edge cases you'd have skipped.
- Unfamiliar but well-established API surface, with a fast compile loop.
- Translation — a shell script to Kotlin, a JSON schema to data classes.
- The blank page, when starting is the blocker.
The pattern connecting all of them: verification is cheap, because the compiler, the test suite or a quick read settles it in seconds.
The rule
A model is fast at the half of the work that was already fast. Before you reach for it, ask which half this task is: if the writing is expensive and the checking is cheap, use it without hesitation. If the checking is the expensive part, you're buying speed on the wrong side of the ledger — and it will feel fast the entire time, because what it removed was friction, not work.