The previous lesson made a strong case for TypeScript. This one makes an equally important case for holding off. TypeScript migration costs real time — weeks for a medium project, months for a large one — and it pays back only under the right conditions. Starting a migration at the wrong moment, on the wrong project, or without the right team support will cost more than it saves.
When the cost exceeds the benefit
The project has a short remaining lifespan. If a Cypress suite is being replaced by Playwright next quarter, or the product is being rebuilt from scratch, migration investment is pure waste. The rule: if the project won't outlive the migration effort by at least six months, skip it.
The tests are simple and self-contained. A 60-line Node.js script that seeds a database, or a five-test Playwright suite for a landing page, doesn't have the complexity that TypeScript addresses. There are no shared page objects to misuse, no test data factories to get wrong, no team of five people writing to the same files. TypeScript's benefits scale with project size.
The team has no TypeScript experience. Migration with a team that doesn't know TypeScript yet means the migration itself is also a training exercise. That's not always wrong — but it's twice the investment, and the team will make beginner mistakes that complicate the codebase mid-migration. A better sequence: run the TypeScript for QA course first, then migrate.
The codebase depends heavily on untyped third-party libraries. If three of your four key dependencies have no @types package and no bundled types, you'll spend weeks writing custom .d.ts files before you see any benefit. Check the libraries your project uses on npmjs.com or TypeSearch before committing.
CI and build tooling are already unreliable. Migration touches tsconfig.json, package.json, and often the test runner config. If CI breaks every other week for unrelated reasons, you won't be able to tell whether a new failure is from the migration or from the existing fragility. Fix the pipeline first.
The team is under delivery pressure. Migration is not a background task. It requires someone to own it, triaging type errors and unblocking colleagues who hit confusing compiler messages. Running a migration alongside a deadline-driven feature quarter is a reliable way to get a half-finished codebase — mixed JS and TS, any types everywhere, no one sure whether the setup is correct.
Calculating whether migration is worth it
Before starting, answer four questions:
- How many hours per month does the team spend on type-related bugs? Count the runtime errors, the wrong-argument debugging sessions, the post-refactor test failures. This is the benefit side.
- How many files does the project have? Rough estimate for a one-person migration: one to two hours per file (including fixing errors, updating imports, writing any missing type declarations). Multiply by 0.4 for a team effort.
- How mature is the TypeScript ecosystem for your stack? Cypress, Playwright, Jest, and Axios are all well-typed. If your stack is well-supported, setup costs are low. Custom frameworks with no type definitions mean high setup costs.
- Is there a dedicated window? A committed migration sprint (two weeks uninterrupted) delivers more than six months of "migrate a file whenever you're near it." Without a window, migration stalls.
If the answer to question 1 is "we basically don't lose time to type bugs," the ROI calculation probably doesn't close.
The middle ground: JSDoc type hints
If you want IDE support without the migration cost, JSDoc comments give you roughly 70% of the TypeScript benefit in JavaScript files — no renaming required.
// @ts-check ← add this to opt a JS file into type checking
/**
* @param {{ email: string; password: string; role: string }} user
* @returns {Promise<string>}
*/
async function createUser(user) {
const response = await api.post("/users", user);
return response.data.id;
}With // @ts-check at the top, VS Code type-checks the file using your JSDoc annotations. You get red squiggles for wrong argument shapes and autocomplete on the return type. The compiler honours /** @type {string} */ variable annotations. This is a reasonable stopping point for projects that want better IDE support without committing to a full migration.
Anti-patterns to watch for
"We're migrating because TypeScript is the industry standard." TypeScript is widely used, but "industry standard" is not a business justification. You need a specific, measurable problem it solves for your team.
Migrating as part of a larger refactor. "We'll move to TypeScript while we also restructure the page objects and upgrade Playwright." Each change adds risk and makes it harder to attribute failures. Do one thing at a time.
Starting with the hardest file. The natural impulse is to tackle the most complex file first. The practical advice is the opposite: start with the simplest utility module, get one file working cleanly, build the pattern, then move to more complex code.
⚠️ Common mistakes
- Underestimating untyped dependencies. A project with three untyped libraries doesn't just require a small amount of extra work — writing a complete
.d.tsfile for a complex library can take a full day per library. Audit this before committing. - Starting migration without team buy-in. If half the team doesn't understand why migration is happening, they'll work around the type system with
anytypes and// @ts-ignorecomments, producing a codebase that has TypeScript's cost without its benefits. - Treating JSDoc as a consolation prize. For some projects and some teams, JSDoc is genuinely the right answer. It doesn't require a compiler, doesn't change file extensions, and adds no build step. It's not "almost TypeScript" — it's a valid tool for a specific set of tradeoffs.
🎯 Practice task
Before migrating anything, run the ROI calculation on a real project.
- List every JavaScript test file in the project. Count them.
- Pick the five most complex files. Estimate, honestly, how long fixing type errors in each would take: half a day? a full day?
- List the project's key testing dependencies (framework, API client, assertion library). Search npm for an
@types/package for each. Note which are untyped. - Think back over the last three months. Write down three specific bugs that type checking would have caught. If you can't find three, note that — it's useful data for the ROI calculation.
- Based on your answers: write a one-paragraph recommendation. Migrate now, migrate after addressing specific blockers, or use JSDoc instead?
There's no wrong answer. The next lesson assumes you've decided to migrate and covers choosing between the two main strategies.