# Split a large change into reviewable pull requests

> Create an additive sequence of refactor, contract, dormant implementation, migration, activation, and cleanup changes with safe merge points.

Canonical URL: https://www.devobs.io/articles/small-pull-request-dependency-stacks/
By: Nina Patel
Published: 2024-11-27T14:00:52.306Z
Updated: 2026-09-06T08:31:04.426Z
Section: Architecture

Split a cross-cutting feature by stable intermediate states: preparatory refactor, additive contract, dormant implementation, migration, activation, and cleanup. Each pull request should be independently buildable, testable, and safe to merge, while its description names dependencies and the final behavior it enables.

## Plan the merge states first

Draw the current system and desired system, then list states production can safely occupy between them. A database-backed feature might proceed as: add nullable schema; deploy code that dual-writes while reading old data; [backfill](https://www.devobs.io/articles/production-data-backfill-control-loop/); verify; switch reads behind a flag; stop old writes; remove the old column. The order comes from compatibility, not file layout.

GitHub describes [pull requests](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests) as proposals to merge changes with discussion, commits, checks, and review. Make each proposal tell one coherent story. A small diff that cannot run without an unmerged sibling is operationally larger than it looks. Prefer additive compatibility so the base branch remains healthy after every merge.

A preparatory refactor should preserve behavior and make the later change smaller: extract an interface, name a concept, or isolate one call site. Do not mix it with feature activation. The contract change adds a field, endpoint, event, or interface in a backward-compatible way. The implementation can remain dormant behind configuration with tests proving both paths.

## Keep dependency stacks shallow

Sometimes dependent branches are useful: PR B targets or builds on PR A, and PR C builds on B. Keep the stack short, mark the order prominently, and avoid asking reviewers to mentally subtract ancestors. Once A merges, rebase B onto the updated base, run checks, and refresh its displayed diff. Git's [rebase documentation](https://git-scm.com/docs/git-rebase) explains replaying commits onto a new base and warns about the consequences of rewriting shared history. Coordinate before force-updating a branch others use.

Do not create a chain of ten PRs that only works when merged all at once. If an intermediate interface is awkward but safe, say when cleanup removes it. If a migration must finish before activation, put a measurable gate in the rollout plan rather than relying on merge timing. Feature flags control runtime activation; branches control code review.

## Give every slice its own proof

The refactor needs equivalence tests. The additive contract needs compatibility tests with old clients or readers. The dormant implementation needs focused behavior tests and production telemetry that can distinguish paths. The migration needs idempotence, progress, reconciliation, pause, and rollback. Activation needs a canary and abort rule. Cleanup needs evidence that the old path has no callers or data.

Review descriptions should name the trigger and behavior after this PR, dependencies, safe merge state, rollout action, and validation. Avoid describing the whole epic identically in every PR. Link the slice plan, but make each diff understandable on its own. Reviewers can then approve local correctness without guessing which later patch repairs a temporary break.

## Slice one risky edge vertically

Choose the smallest end-to-end path that proves the architecture: one resource type, one event, or one tenant cohort. Implement its additive contract and dormant path before broadening coverage. This reveals whether the proposed seams are real.

For the next large change, write six rows—refactor, contract, implementation, migration, activation, cleanup—with inputs, output behavior, compatibility promise, test, and rollback for each. If a row leaves the base branch broken or requires an unseen future diff, redraw the boundary before opening the first pull request.

## Plan for partial rollback

A later slice may need rollback after earlier additive changes ship. State which combinations are supported: new schema with old code, dual-write code with backfill paused, and new readers with the flag disabled. Test those combinations explicitly in deployment order and reverse order. Removing compatibility code is safe only after the [rollback window](https://www.devobs.io/articles/rollback-compatible-release-design/) closes, old deployment versions disappear, and telemetry shows no old readers or writers remain.

Reviewed September 2026.

## Source references

- <https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests>
- <https://git-scm.com/docs/git-rebase>
