Every migration gets pitched the same way. We’ll move off the old thing and onto the new thing. Replace the database, swap the framework, retire the vendor. It sounds like a switch you flip: old system off, new system on, done by Friday.
It is never a switch. A migration is a stretch of time during which both systems are alive at once, and you have to keep both working while you walk every dependent across — one at a time, on a schedule most of them didn’t agree to. The new system is the part you can see and estimate. The stretch in the middle, where you run two of everything, is the part that actually costs you. That’s the part nobody scopes.
Here’s the shape of the mistake. You estimate building B. Building B is honest work and you’re reasonably good at estimating it. What you don’t estimate: discovering everyone who quietly depends on A, convincing those owners to move, and surviving the period where a bug could be in A, in B, or in the seam between them. The build is a fraction. The crossing is the thing.
Then there’s the long tail, and the long tail is where the schedule actually lives. The first 80% of callers move in the first sprint or two — they have owners, they’re paying attention, the new thing is better. The last 20% are the ones with no owner: the batch job someone wrote three years ago, the integration marked “deprecated” that still fires at quarter-end, the one internal tool whose maintainer left. Those stragglers don’t care about your timeline, and they are the ones who set your real end date. If you plan around the 80% that moves fast, you will be wrong by the 20% that doesn’t.
And then — the part that turns a migration into a permanent tax — nobody deletes A. Deleting the old system is unglamorous and a little scary. It’s the work with no demo. So migrations stall at “both running, indefinitely,” and now you own two systems forever and the maintenance of both. A migration that never finishes is strictly worse than one you never started: you paid to build B and you still pay for A.
So scope the crossing, not the build. A few decisions that help:
Estimate the dual-run, not the new thing. The question isn’t “how long to build B.” It’s “how long will A and B both be alive, who is on the hook for that period, and what breaks if it runs twice as long as we hope.” That number is the project.
Make A’s dependents visible before you start. You can’t migrate what you can’t see. Instrument the old system to log every caller for a few weeks first; the list you get back is almost always longer and stranger than the one in your head. (This is the observability lesson wearing a migration costume.)
Find the last 20% first, not last. The ownerless stragglers are your critical path. Go hunting for them at the beginning, while you still have political capital and attention, instead of discovering them in month six.
Give A a kill-date with a name on it. “Deprecate A by Q3” with nobody accountable means A lives forever. A real date, owned by a real person, with the deletion itself tracked as the deliverable.
Define “done” as “A is deleted.” Not “B works.” B working is the middle of the project. The migration is finished when the old thing is gone and you’re carrying one system again.
Sources
- The 'strangler fig' pattern (Martin Fowler)
Liked this? Get the next one in Working Theory.
Going weekly in August (it's in beta now). One genuinely interesting read on building, the brain, and the science most people missed.
Subscribe →