Decomposing into phases
How to cut a feature so that every cut point is a place you could stop.
Given a spec, the question is where to cut. The principle: each phase should end somewhere the codebase is coherent, the checks are green, and you could genuinely stop.
The default cut: vertical, thin, end to end
Cutting by layer — all the database work, then all the API, then all the UI — is the intuitive decomposition and usually the wrong one. Nothing is verifiable until the last phase, so every assumption made in phase one is tested in phase four.
Cut vertically instead. The narrowest path that works end to end, then widen.
| Horizontal (avoid) | Vertical (prefer) |
|---|---|
| 1. All tables and migrations | 1. One schedule type, hard-coded daily, no UI, prove the pipeline end to end |
| 2. All API endpoints | 2. Add weekly and monthly |
| 3. All UI | 3. Add the CRUD UI |
| 4. Wire together, discover problems | 4. Add retries and failure notification |
Vertical cuts mean phase one already answered the risky question — does the queue deliver, does the email attach, does streaming work — and later phases are elaboration rather than discovery.
Front-load the risk
Order phases by uncertainty, not by dependency alone. The phase most likely to invalidate the plan goes first, even if it is not the most natural starting point. Discovering in phase four that exports cannot stream through your email provider is a rewrite; discovering it in phase one is a design change.
A worked decomposition
Phase 1 Pipeline spike (riskiest) T1 schedules table + migration T2 hard-coded daily scheduler -> enqueue -> export -> email -> stop point: one account, one schedule, works end to end Phase 2 Real scheduling T3 findDueSchedules with timezone + DST handling T4 weekly and monthly rules -> stop point: correct scheduling, still no UI Phase 3 Management UI T5 CRUD API T6 settings screen -> stop point: shippable to a friendly customer Phase 4 Robustness T7 retries + admin notification T8 size limit + link fallback (blocked on open question)
Note that phase 3 is the first phase a user could see, and phase 4 is where the open question lives — deliberately last, so it does not block the work that does not depend on it.
Keep the plan file honest
The plan is a living artifact. After each session, mark what is done and note anything the session learned that changes later tickets. That file, plus the spec, is the entire memory of the project — and unlike a session, it does not degrade.
Watch out
Do not write all the tickets up front in detail. Write the next two or three. Later tickets will be wrong because earlier sessions will change what is true, and detailed wrong tickets are worse than sketched ones — they look authoritative.
Try it
Decompose your spec into phases. For each, write the stop point in one sentence. If a phase has no meaningful stop point, it is not a phase — merge or re-cut it.
Takeaways
- Cut vertically: thin end-to-end slices, not layer by layer.
- Order by risk — put the phase that could invalidate the plan first.
- Every phase ends at a coherent, green, stoppable state.
- Detail only the next two or three tickets.
Why is layer-by-layer decomposition tempting but usually wrong?
Because it matches how the codebase is organised, so it feels tidy. But nothing is verifiable until the final layer, which means every assumption from the first layer stays untested for the whole project — and when one is wrong, the correction is a rewrite rather than an adjustment.
A course by Pieter Zandbergen