Incremental Implementation
Overview
Build in thin vertical slices — implement one piece, test it, verify it, then expand. Never attempt to implement an entire feature in one pass. Each increment should leave the system in a working, testable state. This is the execution discipline that makes large features manageable.
When to Use
- •Implementing any multi-file change
- •Building a new feature from a task breakdown
- •Refactoring existing code
- •Any time you're tempted to write more than ~100 lines before testing
When NOT to use: Single-file, single-function changes where the scope is already minimal.
The Increment Cycle
┌──────────────────────────────────────┐ │ │ │ Implement ──→ Test ──→ Verify ──┐ │ │ ▲ │ │ │ └───── Commit ◄─────────────┘ │ │ │ │ │ ▼ │ │ Next slice │ │ │ └──────────────────────────────────────┘
For each slice:
- •Implement the smallest complete piece of functionality
- •Test — run the test suite (or write a test if none exists)
- •Verify — confirm the slice works as expected (tests pass, build succeeds, manual check)
- •Commit — save your progress with a descriptive message
- •Move to the next slice — carry forward, don't restart
Slicing Strategies
Vertical Slices (Preferred)
Build one complete path through the stack:
Slice 1: Create a task (DB + API + basic UI)
→ Tests pass, user can create a task via the UI
Slice 2: List tasks (query + API + UI)
→ Tests pass, user can see their tasks
Slice 3: Edit a task (update + API + UI)
→ Tests pass, user can modify tasks
Slice 4: Delete a task (delete + API + UI + confirmation)
→ Tests pass, full CRUD complete
Each slice delivers working end-to-end functionality.
Contract-First Slicing
When backend and frontend need to develop in parallel:
Slice 0: Define the API contract (types, interfaces, OpenAPI spec) Slice 1a: Implement backend against the contract + API tests Slice 1b: Implement frontend against mock data matching the contract Slice 2: Integrate and test end-to-end
Risk-First Slicing
Tackle the riskiest or most uncertain piece first:
Slice 1: Prove the WebSocket connection works (highest risk) Slice 2: Build real-time task updates on the proven connection Slice 3: Add offline support and reconnection
If Slice 1 fails, you discover it before investing in Slices 2 and 3.
Implementation Rules
Rule 0: Simplicity First
Before writing any code, ask: "What is the simplest thing that could work?"
After writing code, review it against these checks:
- •Can this be done in fewer lines?
- •Are these abstractions earning their complexity?
- •Would a staff engineer look at this and say "why didn't you just..."?
- •Am I building for hypothetical future requirements, or the current task?
SIMPLICITY CHECK: ✗ Generic EventBus with middleware pipeline for one notification ✓ Simple function call ✗ Abstract factory pattern for two similar components ✓ Two straightforward components with shared utilities ✗ Config-driven form builder for three forms ✓ Three form components
Three similar lines of code is better than a premature abstraction. Implement the naive, obviously-correct version first. Optimize only after correctness is proven with tests.
Rule 0.5: Scope Discipline
Touch only what the task requires.
Do NOT:
- •"Clean up" code adjacent to your change
- •Refactor imports in files you're not modifying
- •Remove comments you don't fully understand
- •Add features not in the spec because they "seem useful"
- •Modernize syntax in files you're only reading
If you notice something worth improving outside your task scope, note it — don't fix it:
NOTICED BUT NOT TOUCHING: - src/utils/format.ts has an unused import (unrelated to this task) - The auth middleware could use better error messages (separate task) → Want me to create tasks for these?
Rule 1: One Thing at a Time
Each increment changes one logical thing. Don't mix concerns:
Bad: One commit that adds a new component, refactors an existing one, and updates the build config.
Good: Three separate commits — one for each change.
Rule 2: Always Compilable
After each increment, the project must build and existing tests must pass. Never leave the codebase in a broken state between slices.
Rule 3: Feature Flags for Incomplete Features
If a feature isn't ready for users but you need to merge increments:
// Feature flag for work-in-progress
const ENABLE_TASK_SHARING = process.env.FEATURE_TASK_SHARING === 'true';
if (ENABLE_TASK_SHARING) {
// New sharing UI
}
This lets you merge small increments to the main branch without exposing incomplete work.
Rule 4: Safe Defaults
New code should default to safe, conservative behavior:
// Safe: disabled by default, opt-in
export function createTask(data: TaskInput, options?: { notify?: boolean }) {
const shouldNotify = options?.notify ?? false;
// ...
}
Rule 5: Rollback-Friendly
Each increment should be independently revertable:
- •Additive changes (new files, new functions) are easy to revert
- •Modifications to existing code should be minimal and focused
- •Database migrations should have corresponding rollback migrations
- •Never delete something in one commit and replace it in the same commit — separate them
Working with Agents
When directing an agent to implement incrementally:
"Let's implement Task 3 from the plan. Start with just the database schema change and the API endpoint. Don't touch the UI yet — we'll do that in the next increment. After implementing, run `npm test` and `npm run build` to verify nothing is broken."
Be explicit about what's in scope and what's NOT in scope for each increment.
Increment Checklist
After each increment, verify:
- • The change does one thing and does it completely
- • All existing tests still pass (
npm test) - • The build succeeds (
npm run build) - • Type checking passes (
npx tsc --noEmit) - • Linting passes (
npm run lint) - • The new functionality works as expected
- • The change is committed with a descriptive message
Common Rationalizations
| Rationalization | Reality |
|---|---|
| "I'll test it all at the end" | Bugs compound. A bug in Slice 1 makes Slices 2-5 wrong. Test each slice. |
| "It's faster to do it all at once" | It feels faster until something breaks and you can't find which of 500 changed lines caused it. |
| "These changes are too small to commit separately" | Small commits are free. Large commits hide bugs and make rollbacks painful. |
| "I'll add the feature flag later" | If the feature isn't complete, it shouldn't be user-visible. Add the flag now. |
| "This refactor is small enough to include" | Refactors mixed with features make both harder to review and debug. Separate them. |
Red Flags
- •More than 100 lines of code written without running tests
- •Multiple unrelated changes in a single increment
- •"Let me just quickly add this too" scope expansion
- •Skipping the test/verify step to move faster
- •Build or tests broken between increments
- •Large uncommitted changes accumulating
- •Building abstractions before the third use case demands it
- •Touching files outside the task scope "while I'm here"
- •Creating new utility files for one-time operations
Verification
After completing all increments for a task:
- • Each increment was individually tested and committed
- • The full test suite passes
- • The build is clean
- • The feature works end-to-end as specified
- • No uncommitted changes remain