Tinker AI
Read reviews

Outcome

Codebase fully on strict mode in 3 weeks; ~2400 type errors fixed; revealed several real bugs along the way

4 min read

A team I worked with had been on TypeScript for years but never with strict: true. The codebase had accumulated implicit any, missed null checks, and other loose patterns. Migrating to strict mode revealed about 2400 type errors. Three weeks of work with Cursor; I estimated 8 weeks without.

This is a category — “incremental TypeScript strictness” — where AI tools shine. The work is mechanical, the patterns are well-understood, the risk is bounded.

The codebase

  • ~50k lines of TypeScript
  • 6 years old
  • React frontend + Node.js backend
  • strict: false, noImplicitAny: false, etc.
  • About 2400 type errors when strict was enabled

The plan

Incremental migration:

  1. Enable strict mode flags one at a time
  2. For each flag, fix all errors before moving on
  3. Use AI tools heavily for the fixes
  4. Verify behavior preservation

Order of flags matters. I used:

  1. noImplicitAny (biggest; tackle first)
  2. strictNullChecks (second biggest)
  3. strictFunctionTypes
  4. strictBindCallApply
  5. strictPropertyInitialization
  6. noImplicitThis
  7. alwaysStrict

Each flag’s fixes are roughly independent. Doing them sequentially keeps the work focused.

The .cursorrules

# TypeScript strict migration

We're migrating to strict mode incrementally. Current state: noImplicitAny
is being enabled. Other strict flags will follow.

When fixing type errors:
- Add explicit types to function parameters
- Avoid `as any` (use specific types or `as unknown as T` if needed)
- For values whose type is genuinely unknown, use `unknown`
- For complex types, prefer interfaces over inline types
- For React props, define interfaces named like `MyComponentProps`

When the original code's intent is unclear:
- Look at how the function is called to infer types
- If it's used in many places, choose the most general reasonable type
- If callers pass varied types, define a union or generic
- If callers don't seem to care, mark with TODO and use a permissive type

Do not change runtime behavior to fix type errors. If a fix would change
behavior, mark with TODO and ask.

The “do not change runtime behavior” line is critical. AI tools sometimes “fix” type errors by changing what the code does — adding null checks that change semantics, narrowing types in ways that exclude valid inputs. The rule pushes back.

The workflow

For each batch of errors:

> /add src/services/userService.ts (file with errors)
> /add src/types/user.ts (related types)

> The TypeScript compiler reports these errors in userService.ts:
> [paste errors]
> 
> Fix the errors by adding explicit types. Do not change runtime
> behavior. Do not use `as any`.

Cursor produced fixes. I’d review for:

  • Did the fix address the type error?
  • Did the fix preserve behavior?
  • Did the fix introduce a new pattern that’s reasonable?

About 80% of fixes were merge-ready. 20% needed iteration.

What surprised me

A few things:

The work surfaced real bugs. About 30 of the 2400 type errors revealed actual bugs. Cases where code accessed properties on potentially-null values, where parameters had wrong types compared to their usage, etc.

These weren’t introduced during the migration; they existed before. Strict mode revealed them.

Cursor’s pattern recognition was strong. After fixing 50-100 errors of a category, Cursor’s suggestions on the rest matched my preferred pattern. The model learned my style during the session.

Some errors were hard. About 5% of errors required real thinking. Generic types, conditional types, complex inheritance hierarchies. These took human attention.

Tests were stable. Throughout the migration, test pass rate stayed at 100%. The “don’t change runtime behavior” rule held.

What I had to be careful about

A few patterns I learned to watch for:

Cursor adding null checks that swallow errors. When a function parameter is T | null, Cursor sometimes adds if (param === null) return;. This silences the error but may not preserve intent. Verify what should happen for null.

Type assertions that hide problems. value as SpecificType makes the error go away. But if value isn’t actually that type, you’ve introduced a runtime hazard. Avoid in new code.

Generic types that lose information. <T extends string> is sometimes the wrong abstraction. For specific known cases, named types are better.

Refactoring scope creep. Cursor sometimes “improves” surrounding code while fixing errors. I’d review for “is this change in the diff related to the type fix?” and revert unrelated changes.

Productivity numbers

  • Estimated time: 8 weeks
  • Actual time: 3 weeks
  • Cursor cost: included in subscription
  • Type errors fixed: 2400+
  • Real bugs found: ~30
  • Behavioral regressions: 0 (test suite stable throughout)

The 5x speedup is the headline. Working through 2400 errors manually is tedious; with Cursor each error becomes 30 seconds instead of 5 minutes.

Worth the AI investment?

For TypeScript strict migrations specifically: yes. The work is exactly the kind AI tools handle well. Mechanical, well-defined, easy to verify.

For other migrations of similar shape (Python type hints, Ruby’s Sorbet types, etc.): the same pattern likely applies. Worth trying.

Pattern transfer

The pattern that worked here generalizes:

For mechanical migrations:

  1. Enable the new strictness incrementally
  2. Use AI tools for the bulk of the fixes
  3. Strong rules in .cursorrules to constrain AI behavior
  4. Manual review for the unusual cases
  5. Tests as the verification

This works for:

  • Type system upgrades (TypeScript strict, etc.)
  • Linter rule introductions
  • Style guide enforcement
  • API deprecation migrations
  • Framework upgrades

The AI tooling productivity gain compounds across these projects. Teams that have many such migrations on backlog can clear them with AI assistance much faster than they could without.

What I’d recommend

For teams with non-strict TypeScript codebases:

Don’t defer the migration. AI tools make it tractable. The longer you wait, the more errors accumulate.

Do it incrementally. One strict flag at a time. Less daunting; easier to maintain pace.

Use AI for the fixes. Manual fix-everything is unnecessarily tedious now.

Watch for the bugs. Strict mode reveals existing bugs. Treat them as wins, not just type fixes.

Maintain test pass rate. Throughout the migration, tests should keep passing. If they break, the migration introduced a regression.

Closing

TypeScript strict migration is one of those projects every team eventually does or wishes they had. AI tools change the calculus. What was a multi-month project becomes a multi-week project.

For teams considering strict mode: do it now. The investment is bounded; the payoff is ongoing better code; the AI tooling makes it tractable.

For teams already on strict: this case study doesn’t apply. You’re past the hard work.

For new TypeScript projects: start with strict mode. The migration cost is highest when accumulated; preventing accumulation is cheaper than fixing it later.