Choosing a Migration Strategy — Big Bang vs Incremental

8 min read

You have decided to migrate. Now the question is how. There are two strategies: Big Bang (convert everything at once) and Incremental (convert file by file over weeks or months). For the vast majority of test projects, incremental is the right choice — but understanding both helps you make an informed decision and avoid the traps that derail each one.

Big Bang migration

In a Big Bang migration you stop feature work, rename every .js file to .ts in one commit, then fix type errors until the project compiles cleanly. The whole suite is TypeScript by the end of the sprint.

When it works: small projects with under 50 files, a dedicated two-week window with no other deliverables, and a team that already knows TypeScript. If every developer on the team has done this before and the project is small, Big Bang is fast and produces a clean result with no mixed JS/TS period.

What goes wrong: on any medium-to-large project, the batch rename exposes hundreds of type errors simultaneously. The team is blocked — nothing compiles — and the errors compete for attention. New errors appear when fixing old ones (a type added to one file propagates to its callers, surfacing new errors there). Progress is hard to measure. CI is red for days. Feature work that was supposed to continue in parallel cannot be merged because the type-checking step fails on every branch. For a 150-file Playwright project, a Big Bang typically takes three to four weeks longer than estimated.

# The Big Bang approach in one command — don't do this for large projects
for f in $(find cypress -name "*.js"); do mv "$f" "${f%.js}.ts"; done
# Result: 847 TypeScript errors. CI blocked. Team unhappy.

Incremental migration

In an Incremental migration, JavaScript and TypeScript coexist in the same project. You use allowJs: true in tsconfig.json so the TypeScript compiler processes both file types. You convert files one at a time — or a handful at a time — while the rest of the project stays in JavaScript and keeps running.

The TypeScript compiler's allowJs option is what makes this possible. With it set, .js files pass through the compiler without type-checking. .ts files are type-checked. You get real value on every file you convert without the rest of the project blocking you.

// tsconfig.json — migration-mode config
{
  "compilerOptions": {
    "allowJs": true,
    "checkJs": false,
    "strict": false,
    "noImplicitAny": false,
    "esModuleInterop": true,
    "skipLibCheck": true
  },
  "include": ["**/*.js", "**/*.ts"],
  "exclude": ["node_modules"]
}

The recommended file order:

  1. New code first. From today, write all new files as .ts. The cost is one tsconfig and a quick install. The benefit compounds with every new file.
  2. Shared utilities and helpers. These are used everywhere — typing them propagates type information across the most files for the least work.
  3. Page objects. High reuse, clear interfaces, immediate IDE benefit for everyone on the team.
  4. Files with the most historical bugs. TypeScript's value is highest where errors happen most often.
  5. Individual test files last. There are more of them, they change frequently, and most of the type-safety benefit comes from the shared code they call.

Tracking progress: count .ts vs .js files weekly. A simple find cypress -name "*.ts" | wc -l gives you a metric to track against a quarterly goal.

Big Bang vs Incremental — comparing the real tradeoffs

Big Bang

  • Convert all files at once

  • All type errors appear simultaneously

  • CI blocked until all errors fixed

  • Feature work paused during migration

  • Clean result — no mixed JS/TS

  • Best for: <50 files, dedicated sprint

Incremental

  • Convert one file at a time

  • Type errors fixed before next file

  • CI stays green throughout

  • Feature work continues in parallel

  • Mixed JS/TS period (manageable)

  • Best for: most real-world projects

The strangler-fig pattern

Borrowed from software architecture, the strangler-fig pattern applied to TypeScript migration means: new code is always TypeScript, old code converts as it is modified. You never touch a file purely to convert it — you convert it when you're already there for a feature or bug fix. This keeps migration cost invisible: it's bundled into regular work.

The downside is pace. A project with stable, rarely-changed code may take a long time to reach full TypeScript this way. If that's a problem, supplement with dedicated conversion sessions for the highest-value files.

The six-phase incremental plan

A structured approach that works for most medium Playwright and Cypress projects:

  1. Phase 1: Install TypeScript, create tsconfig.json with allowJs: true, strict: false. Run npm run type-check — it should pass. CI still green.
  2. Phase 2: Write all new files as .ts. Fix import statements in converted files to use import/export instead of require.
  3. Phase 3: Convert shared utilities and page objects to .ts. Fix type errors one file at a time.
  4. Phase 4: Enable noImplicitAny: true. Fix the implicit any errors in converted files.
  5. Phase 5: Enable strictNullChecks: true. Fix null-safety errors.
  6. Phase 6: Enable strict: true. Rename remaining .js files to .ts. Remove allowJs: true. Migration complete.

Each phase is independently deployable. You can pause after Phase 3 for a month and resume with no lost progress.

Deciding for your project

Answer these three questions:

  • File count: under 50 .js test files → Big Bang is viable. Over 50 → Incremental.
  • Team availability: can the whole team pause feature work for two weeks? → Big Bang is viable. No → Incremental.
  • TypeScript experience: has the team done this before? → Big Bang is viable. Team is still learning → Incremental.

If any one answer points to Incremental, use Incremental. The projects where Big Bang fails are almost always ones where the team convinced itself it was "small enough" and was wrong.

⚠️ Common mistakes

  • Starting with the hardest file. The instinct is to tackle the most complex legacy module first to "get it out of the way." The result is a week of fighting type errors in a file nobody understands fully, demoralising the team before they see any benefit. Start with a simple, well-understood utility module.
  • Setting strict: true from day one in an incremental migration. You'll see hundreds of errors in files you haven't converted yet. Set strict: false and noImplicitAny: false to start, then tighten incrementally as coverage grows.
  • Forgetting to configure the test runner. Renaming files to .ts is not enough — Cypress, Playwright, and Jest each need a small configuration change to process TypeScript. Chapter 2 covers this for each framework.

🎯 Practice task

Map out the migration for a real project — or the sample Cypress project you built during JavaScript for QA.

  1. Run find . -name "*.js" | grep -v node_modules | wc -l to count JavaScript files.
  2. List your five highest-priority files for early conversion using the ordering from this lesson (utilities first, tests last).
  3. List the three riskiest files — the ones with the most complex logic, the most callers, or the most historical bugs.
  4. Decide: Big Bang or Incremental? Write one sentence justifying the choice based on your file count, team situation, and TypeScript experience.
  5. If you chose Incremental: write down which phase you'd stop at after two weeks to ship something useful and measure progress.

The next chapter covers the actual setup: installing TypeScript, writing a tsconfig.json that supports migration, and configuring your test runner.

// tip to track lessons you complete and pick up where you left off across devices.