Breaking Work Into Tasks
The art of scoping work is one of the least taught and most important skills in software engineering. Break tasks too big and you never finish — the task sits at "in progress" for weeks, morale drops, and nobody knows how much is left. Break tasks too small and the overhead of tracking, reviewing, and deploying each one consumes more time than the work itself. The sweet spot is tasks that are small enough to finish in a day or two but large enough to represent meaningful progress.
Why Task Breakdown Matters
Badly scoped work is the root cause of most project delays. Not technical complexity. Not unclear requirements. Bad scoping.
What happens when tasks are too big:
- The task sits "in progress" for 2 weeks
- Nobody (including you) knows the real status
- Unexpected complexity hides inside the large task
- Code reviews are massive and painful
- Merging is risky because the diff is huge
- If priorities change, you lose all the unfinished work
What happens when tasks are too small:
- More time in Jira than in the editor
- PR overhead per line of code goes through the roof
- Context switching between tiny tasks
- Lost time writing tickets nobody will read
- The big picture gets lost in micro-tasks
The ideal task size for most engineering work is one that can be started and finished within 1-2 days, produces a reviewable and deployable artifact, and moves the project forward in a visible way.
Work Breakdown Structure
A work breakdown structure (WBS) is a hierarchical decomposition of work. You start with the goal and break it down into progressively smaller pieces until each leaf node is a task someone can pick up and do.
Example: Build a user notification system
Level 0 (epic):
Build user notification system
Level 1 (features):
1. Email notifications
2. Push notifications
3. In-app notifications
4. Notification preferences
5. Notification history
Level 2 (tasks for "Email notifications"):
1.1 Define notification event types and data schema
1.2 Build email template renderer
1.3 Integrate email sending service (SendGrid/SES)
1.4 Add email notification trigger to order events
1.5 Add email notification trigger to account events
1.6 Handle bounces and delivery failures
1.7 Add rate limiting to prevent email flooding
Level 3 (subtasks for "Build email template renderer"):
1.2.1 Set up template engine (Handlebars/Jinja)
1.2.2 Create base email template with header/footer
1.2.3 Create order confirmation template
1.2.4 Create password reset template
1.2.5 Write tests for template rendering
Stop decomposing when each item is 0.5-2 days of work. If you reach Level 4, you are probably over-decomposing.
Vertical Slices vs Horizontal Layers
The single most important scoping decision is whether to slice vertically or horizontally.
Horizontal Layers
Horizontal slicing means building one layer at a time across the whole feature:
Horizontal approach to notifications:
Week 1: Build the database schema for all notification types
Week 2: Build all the API endpoints
Week 3: Build the email sending infrastructure
Week 4: Build the frontend notification UI
Week 5: Connect everything together
The problem: nothing works until everything works. After 4 weeks you have a database, APIs, email infrastructure, and a UI — none of which are connected or testable end-to-end. Integration bugs surface in week 5, which becomes week 7.
Vertical Slices
Vertical slicing means building one thin path through all layers:
Vertical approach to notifications:
Week 1: Order confirmation email, end to end
(event -> template -> send -> deliver -> log)
Week 2: In-app notification for order updates
(event -> store -> API -> frontend display)
Week 3: Notification preferences
(UI -> API -> database -> filter logic)
Week 4: Push notifications
(event -> provider -> device delivery)
After week 1, you have a working feature that users can experience. Each subsequent week adds another working feature. You can ship after any week and have something valuable. Integration bugs surface immediately because every slice touches all layers.
When to Slice Horizontally
Horizontal slicing is not always wrong. It makes sense when:
- Building shared infrastructure that multiple features need
(e.g., a database migration before any feature can use the new schema)
- The layers have different owners and hard dependencies
(e.g., the platform team builds the API, the product team builds the UI)
- Performance or scaling work that crosses all features
(e.g., adding caching to all database queries)
But for feature work, default to vertical slices. Vertical slices give you shippable increments, early feedback, and faster bug detection.
The INVEST Criteria
The INVEST acronym is a useful checklist for well-scoped tasks:
I - Independent: Can be done without waiting for other tasks
N - Negotiable: The implementation approach is flexible
V - Valuable: Delivers something useful when completed
E - Estimable: You can roughly predict how long it will take
S - Small: Fits in 1-2 days of work
T - Testable: You know how to verify it is done
If a task fails the Independent criterion, it might block on another task and sit idle. If it fails the Valuable criterion, it is probably a horizontal slice that does not deliver user value on its own. If it fails the Estimable criterion, it is too vaguely defined — break it down further or do a spike first.
Handling Uncertainty
When you do not know how to break down a task, it is usually because you do not understand the problem well enough yet. The solution is a spike — a time-boxed investigation.
Spike pattern:
Task: "Integrate with payment provider X"
Problem: you have never used provider X and do not know
what is involved
Spike (time-boxed to 4 hours):
- Read provider X's API documentation
- Set up a sandbox account
- Make one successful test API call
- Document: what is easy, what is hard, what is unclear
After the spike:
- Task 1: Implement basic charge flow (1 day)
- Task 2: Handle webhook callbacks (1 day)
- Task 3: Implement refund flow (0.5 day)
- Task 4: Error handling and retry logic (1 day)
- Task 5: Add monitoring and alerting (0.5 day)
The spike turns an un-estimable blob into a list of concrete, estimable tasks. Time-box the spike strictly — the goal is not to build anything, just to learn enough to plan.
Real-World Example: The Refactor That Never Ended
A team decided to refactor their authentication system. The task was "Refactor auth." One task. Estimated at 2 weeks.
Week 2: still in progress. Week 3: "almost done." Week 4: the PR had 147 changed files. The code review took 3 days. Merging caused 12 conflicts. Two bugs shipped to production.
What should have happened:
Task 1: Extract auth middleware into its own module (1 day)
Task 2: Add integration tests for current auth behavior (1 day)
Task 3: Replace session-based auth with JWT in the middleware (2 days)
Task 4: Update user service to issue JWTs (1 day)
Task 5: Migrate API endpoints to use new middleware, batch 1 (1 day)
Task 6: Migrate API endpoints to use new middleware, batch 2 (1 day)
Task 7: Remove old session-based auth code (0.5 day)
Task 8: Update documentation (0.5 day)
Each task is reviewable, mergeable, and deployable independently. If priorities change after Task 4, you have a working system with the new auth for some endpoints and old auth for others. The refactor can be paused and resumed.
Scoping Rules of Thumb
Task size guidelines:
- If you cannot describe what "done" looks like, break it down further
- If the PR will be more than 400 lines, break it down further
- If the task takes more than 2 days, break it down further
- If the task depends on another unfinished task, flag the dependency
- If you say "and then" more than twice when describing the task,
each "and then" is a separate task
The "and then" test:
"I'll update the schema AND THEN add the API endpoint AND THEN
build the frontend form AND THEN add validation AND THEN
write the tests"
= 5 tasks, not 1
Common Pitfalls
- One giant task instead of many small ones — "build the feature" is not a task. It is a project. Break it into tasks that each take 1-2 days.
- Pure horizontal slicing for feature work — building the entire database layer, then the entire API layer, then the entire UI layer means nothing works until everything works. Slice vertically.
- Over-decomposing — breaking a 2-hour task into six 20-minute subtasks creates more overhead than it saves. Stop decomposing when tasks are 0.5-2 days.
- Not doing a spike when uncertain — trying to break down work you do not understand leads to bad breakdowns. Invest 4 hours in learning, then plan.
- Ignoring dependencies between tasks — if task 3 cannot start until task 1 is done, that is a critical dependency. Make it explicit or re-order to remove it.
Key Takeaways
- The ideal task size is 1-2 days: small enough to finish quickly, large enough to represent meaningful progress, and scoped enough for a clean code review.
- Default to vertical slices for feature work. Each slice goes through all layers and delivers a working, shippable increment.
- Use work breakdown structure to decompose projects hierarchically. Stop when leaf nodes are 0.5-2 days each.
- When you cannot break down a task, do a time-boxed spike to learn enough to plan. Do not estimate what you do not understand.
- Apply the "and then" test: if describing a task requires "and then" more than twice, each segment is a separate task.