Aligning Linear Issue Granularity: INVEST Principles and Vertical Slicing in Practice
TL;DR
The reason issue granularity is inconsistent and a Backlog gets chaotic is that a single issue mixes “design decisions”, “DB changes”, “UI implementation”, and “tests”. By validating each issue’s quality against the INVEST principles, splitting them with vertical slicing, and screening them with a Definition of Ready before starting, you create a rhythm of 1 issue = 1–3 days = 1 PR.
The Root Cause of Inconsistent Granularity
In the previous article, I introduced “a 3-layer structure that separates problems from tasks”. Splitting into Project + Document + Issue resolves the problem of issues like “Overcoming ADHD” creeping into the Backlog.
But even after correctly carving out tasks, granularity inconsistency among the tasks themselves remains. Looking at the Backlog of the business management tool I’m developing, here’s what I had:
PROJ-01Data partitioning: handle and validate the addition of profilesPROJ-02Multi-assignee support and DB validationPROJ-03Is the data structure split this way correct? — design decision
At a glance, they all look like “tasks”. But on closer inspection, each contains multiple concerns:
- Design decision (which DB structure to choose)
- Migration implementation (ALTER TABLE)
- Updating seed data
- UI implementation
- E2E tests
This is a different dimension from the “problem vs. task” separation; it’s a state where granularity and scope are not aligned across tasks.
INVEST Principles: A Quality Checklist for Issues
The most practical framework for aligning issue granularity is the INVEST principles. They were originally proposed as quality criteria for user stories, but apply directly to Linear issues.
| Principle | Meaning | Check |
|---|---|---|
| Independent | Independent | Can it be started alone, without depending on other issues? |
| Negotiable | Negotiable | Is it described by outcome rather than by implementation? |
| Valuable | Valuable | On completion, does it provide clear value to a user or developer? |
| Estimable | Estimable | Is the scope clear enough that the workload can be imagined? |
| Small | Small | Can it be completed in 1–3 days? |
| Testable | Testable | Can completion/incompletion be judged pass/fail? |
Validating PROJ-02 from earlier against INVEST:
- I: DB changes and UI implementation are mixed → not independent ❌
- N: Described by the implementation method “DB validation” → not outcome-oriented ❌
- V: There is value ✅
- E: Scope is too broad to estimate ❌
- S: DB + UI + tests easily exceed a week ❌
- T: Completion criteria for “validation” are vague ❌
5 out of 6 fail. With this, even if you start, it won’t finish, and you can’t tell if it’s done.
Vertical Slicing: Don’t Split in the Wrong Direction
Once you know an issue is too big, you split it — and here, the direction of the split matters.
Horizontal slice — common but dangerous
graph LR
A["DB change"] --> B["Backend API"] --> C["Frontend UI"] --> D["Tests"]
Slicing by layer. “First the DB”, “then the API”, “finally the UI”. It looks reasonable, but has issues:
- Finishing only DB changes provides zero user value
- If you discover a DB design mistake during UI work, the rework is huge
- Each issue depends on the previous one, so parallelization is impossible
Vertical slice — recommended
graph LR
subgraph S1["Slice 1: extend DB"]
A1["DB: add column"] --> B1["Seed: confirm compatibility"]
end
subgraph S2["Slice 2: display the new attribute"]
A2["UI: show in list"] --> B2["E2E: confirm display"]
end
subgraph S3["Slice 3: multiple assignees"]
A3["UI: add button"] --> B3["API: create"] --> C3["E2E: multi-assignees"]
end
S1 ~~~ S2 ~~~ S3
Slicing vertically in units of user value. Each slice can be deployed independently and provides some value on completion.
Decomposing PROJ-02 vertically:
| Resulting issue | Type | Size |
|---|---|---|
| Add an attribute column to the table | Task | 0.5 days |
| Display the new attribute on the detail page | Feature | 1 day |
| Implement add/remove UI for attributes | Feature | 1–2 days |
| Allow assigning per-attribute owners | Feature | 1–2 days |
The first Task is already done — it took 0.5 days as planned. The remaining three can each be started independently, and each delivers user value upon completion.
Distinguishing Issue Types
Another tool for aligning granularity is clearly distinguishing issue types.
| Type | Definition | Size guideline | Title style |
|---|---|---|---|
| Feature | Adds a feature delivering user value | 1–3 days | ”Enable users to …” |
| Task | Executes a clear step | 0.5–1 day | ”Add …”, “Configure …” |
| Bug | Fix a defect in an existing feature | 0.5–2 days | ”Fix the issue where …” |
| Spike | Resolve uncertainty (time-boxed) | 0.5–1 day | ”Investigate and decide …” |
The crucial point is don’t mix Spike and Feature/Task. “Decide the multi-assignee DB design” (Spike) and “Add a column to the table” (Task) should be separate issues, with the Task arising from the Spike’s outcome.
In our example:
graph LR
S["🔍 Spike: Decide<br/>multi-assignee DB design<br/>(PROJ-03)"] --> T["🔧 Task: Add column<br/>to table<br/>(part of PROJ-02)"]
T --> F1["✨ Feature: Display<br/>new attribute"]
T --> F2["✨ Feature: Add/remove<br/>attribute UI"]
F1 --> F3["✨ Feature: Assign<br/>per attribute"]
F2 --> F3
Because Spike PROJ-03 was completed and the design decision was settled, the scope of subsequent Tasks/Features became clear. Without that order, you end up with “a giant issue containing both design and implementation”.
Definition of Ready: Pre-Start Screening
After breaking down issues, the Definition of Ready (DoR) is useful as a final check before pulling them into a sprint. Going through every item every time is heavy, so narrow it to four essentials:
- Outcome can be stated in one sentence — “Users can …”
- Acceptance criteria are 3 items or fewer — clear acceptance
- Finishes in 1–3 days — split if it exceeds that
- Dependent issues are explicit — don’t start while a blocker is unresolved
If even one of these four fails, continue refinement, or split off a Spike to investigate.
The “1 issue = 1 PR” Principle
Issues with consistent granularity naturally produce a “1 issue = 1 PR” relationship. This directly improves code review efficiency. Google’s engineering practices recommend “small CLs (Change Lists)”, which presupposes “issues are at the right granularity”.
Conversely, when one PR closes multiple issues, that’s a sign that issue granularity is too fine, or that unrelated changes have crept into the PR.
Summary
Three tools for aligning issue granularity:
- INVEST principles — Validate each issue’s quality across six items
- Vertical slicing — Split by user value, not by layer
- Definition of Ready — Screen with four items before starting
And distinguish issue types (Feature / Task / Bug / Spike). In particular, don’t mix Spikes with implementation issues.
Granularity consistency isn’t a one-and-done setting; it has to be confirmed in regular retrospectives — “Were there issues that exceeded 3 days?”, “Were there carry-overs?”. Embedding it as a shared team understanding rather than a tool configuration ends up being the most effective.
That’s all.