Trade-Offs and When to Hand-Code Instead
Spec-driven development is a powerful default, but it is not free, and it is not always the right call. Writing a precise spec takes real thinking time. For some tasks that investment pays back many times over; for others, you would have been done already if you had just typed the code.
A mature practitioner knows when to reach for the agent and when to put it down. This lesson gives you that judgment so you do not turn a thirty-second edit into a ten-minute spec-writing exercise, or hand-code a sprawling feature that the loop would have handled cleanly.
What You'll Learn
- The real costs of the spec-first loop
- The signals that say "spec it and hand it off"
- The signals that say "just write it yourself"
- How to combine both in one task
- Why your judgment here improves with practice
The costs are real
Spec-driven development trades up-front thinking for back-end reliability. That trade is usually worth it, but be honest about what you are spending:
- Spec-writing time. A good spec for a feature can take ten or twenty minutes of careful thought. That is time you are not typing code.
- Round-trip latency. Plan-first, implement, verify, iterate — each step is a wait. For a tiny change, the loop's overhead dwarfs the work.
- Verification effort. Writing or reviewing tests is part of the cost. It is worth it for code that matters; it is overkill for a one-off script you will run once and delete.
None of these costs argue against the method. They argue for applying it where its benefits — reliability, checkability, scope control — are worth the overhead.
When to spec it and hand it off
Reach for the full loop when one or more of these is true:
- The task is well-defined but tedious. Boilerplate, CRUD endpoints, data transformations, a function with many edge cases. You know exactly what you want; you just do not want to type it all. The spec is fast to write and the agent saves you real keystrokes.
- Correctness matters and is checkable. Money, auth, data integrity, anything user-facing. The verification step earns its keep here.
- The edge cases are the hard part. When the happy path is trivial but the boundaries are fiddly, a spec forces you to think them through, and tests pin them down.
- You will hand this off or revisit it. A spec is durable documentation. If someone else (or future you) will touch this, the spec is an asset beyond the immediate code.
- The change is large but decomposable. A feature you can split into specced sub-tasks is ideal for the loop. Run it on each piece.
When to just write it yourself
Put the agent down and hand-code when:
- The change is tiny and you know it cold. Renaming a variable, flipping a boolean, a two-line fix. By the time you have written the spec, you could have made the edit twice.
- The intent is hard to express but easy to do. Some changes are tacit — a layout nudge, a tricky bit of state logic you can feel but not cleanly articulate. If specifying it precisely is harder than doing it, do it.
- It is deeply entangled with context only you hold. A change that depends on subtle, undocumented system behavior, a half-finished migration, or a conversation you had with a teammate may take longer to explain than to make.
- You have iterated three times with no progress. When the loop stalls — the spec keeps being not-quite-right, the agent keeps missing — that is a signal the task resists specification. Step in.
- The stakes of an unnoticed error are extreme and the change is small. Sometimes the fastest path to confidence is writing the handful of lines yourself and reading them carefully.
The decision in one view
Decision
Should I spec this for an agent or hand-code it?
- If Tiny, well-understood edit
Hand-code it
The spec costs more than the change
- If Well-defined but tedious or edge-case-heavy
Spec it and hand off
The loop's reliability pays for itself
- If Correctness-critical and checkable
Spec it, verify hard
Verification earns its keep
- If Intent is tacit / hard to express
Hand-code it
If specifying is harder than doing, do it
- If Large but decomposable
Spec each sub-task
Run the loop per piece
This is a judgment call, not a formula. The branches are heuristics; real tasks often touch several. The goal is to make the decision consciously instead of defaulting to "always use the agent" or "never trust the agent."
Combine both in one task
The most effective pattern is rarely all-agent or all-human. It is splitting one task along the line of what each side does best.
Imagine adding a feature with a fiddly algorithm at its core and a lot of routine plumbing around it — validation, serialization, an endpoint, error handling. A strong split:
- Hand-code the algorithmic core if it is tacit or you want tight control over it.
- Spec and hand off the plumbing, which is tedious, well-defined, and edge-case-heavy — exactly the loop's sweet spot.
Or the reverse: spec the well-understood core, hand-code the one piece that depends on context you cannot easily write down. Either way, you are not choosing a side. You are routing each part of the work to the approach that handles it best, and the spec for the agent-handled part still gives you verifiable output.
Your judgment compounds
Early on, you will mis-route some tasks — spec something you should have just typed, or hand-code something the loop would have nailed. That is the cost of learning, and it is cheap. Each time, notice which way you were wrong and recalibrate.
Over weeks, the decision becomes instant. You will glance at a task and know: this is a spec-it-and-walk-away, this is a thirty-second edit, this one splits down the middle. That instinct — knowing where the method helps and where it just adds ceremony — is what separates someone who uses an AI coding agent from someone who directs one.
Key Takeaways
- The spec-first loop costs spec-writing time, round-trip latency, and verification effort; apply it where those costs buy reliability.
- Spec and hand off when the task is well-defined but tedious, correctness-critical, edge-case-heavy, durable, or large-but-decomposable.
- Hand-code when the change is tiny and known, the intent is tacit, it is entangled with context only you hold, or the loop has stalled.
- The best pattern is often a split: route the algorithmic core and the routine plumbing to whichever approach fits each.
- Make the call consciously rather than defaulting; your routing judgment compounds with practice.

