TypeScript's strict mode is a collection of compiler checks that, together, eliminate most of the common runtime type errors that make it into production. strictNullChecks prevents you from accessing properties on values that might be null or undefined. noImplicitAny forces you to declare types explicitly rather than letting them silently infer as any. strictFunctionTypes tightens how function parameter and return types are checked across assignments.
The problem is that enabling strict mode on a codebase that was written without it typically generates hundreds to thousands of type errors at once. On a 200,000-line TypeScript codebase that has been evolving for three or four years, the number can easily reach 2,000 or more. You cannot just flip the flag and start fixing errors piecemeal, because your CI is now broken for every developer on the team until every error is resolved.
The usual workarounds are: add // @ts-ignore comments everywhere (defeats the purpose), suppress specific checks with // @ts-nocheck at the top of each problem file (deferred debt, still broken), or fork a multi-week branch where a subset of the team fixes everything before merging (the rewrite problem again). None of these are satisfying.
Per-file tsconfig overrides: the incremental path
TypeScript 5.x supports per-file configuration overrides, and there is a less-known pattern that enables safe incremental strict mode migration: a separate tsconfig.strict.json that extends your base config and adds strict flags, combined with a build script that compiles each file against the strict config independently and reports errors per file.
This gives you an error surface per file rather than a project-wide error count. You can now ask: which files have zero strict mode errors already? Those can move to strict mode immediately. Which files have one or two errors each? Those are cheap to fix. Which files have 50 or more errors? Those need more careful review and can be deferred.
Pylon-Migrate uses this approach when it encounters a TypeScript strict mode migration job. The first step is not to start fixing errors. It is to build the per-file error map across the entire codebase and sort files into four buckets: already clean, one to five errors, six to twenty errors, twenty or more. Jobs start from bucket one (already clean, just needs the file-level strict annotation) and work forward.
What the fixes actually look like
The error patterns that come up in strict mode migrations are not all equally hard to fix. The most common ones are entirely mechanical. In our observation across a set of early-access repositories, roughly 60 percent of strict mode errors in mature TypeScript codebases fall into three categories that are safe to fix programmatically.
Implicit any on function parameters: a function that was written as function process(data) { rather than function process(data: ProcessInput) {. The fix is to add the type annotation. Pylon infers the correct type from the call sites and the function body. When the type is genuinely ambiguous (the function is called with multiple different shapes), Pylon uses a union type or falls back to a specific unknown annotation with a comment explaining the ambiguity.
Nullability on property access: user.profile.avatar.url where any of those properties might be undefined in some call paths. The fix depends on context: optional chaining (user.profile?.avatar?.url) works when the caller can handle undefined. A non-null assertion (user.profile!.avatar!.url) works when the caller has already validated the shape upstream. Pylon prefers optional chaining because non-null assertions are a suppression, not a fix. When optional chaining would change the return type of a function in a way that would cascade to callers, Pylon flags the file for human review rather than guessing at the right fix.
Return type mismatches: a function declared as returning string that sometimes returns undefined implicitly. The fix is either to explicitly return a default value or to update the return type to string | undefined and let callers handle it. Pylon chooses based on the existing call sites: if all callers already do null checks on the return value, updating the type is correct. If some callers assume a non-null return, updating the type would surface new errors in those callers, which is actually useful information.
The PR structure for strict migrations
Each PR in a strict mode migration job covers between five and fifteen files. This range is a deliberate trade-off. Fewer files per PR means slower progress but easier review. More files per PR risks burying a questionable type fix in a large changeset.
The PR description for each migration batch includes the per-file error count before and after, a list of which strict flags are now enabled for each file, and a section called "review focus" that calls out any fixes that required judgment rather than mechanical transformation. Those are the ones a reviewer should read carefully. The mechanical ones, adding a type annotation to a parameter that has an obvious type from context, can usually be approved on trust after glancing at the diff structure.
We do not enable strict mode for a file at the project configuration level. Instead, each migrated file gets a file-level JSDoc comment: // @strict-migrated 2025-01-15. This serves two purposes: it marks the migration date for audit purposes, and it lets the build system apply stricter checking to migrated files while leaving non-migrated files on the old rules. A file without the annotation is not broken; it is just not yet in the strict set.
What we do not automate
We are not claiming Pylon can fix every strict mode error automatically. The cases where it stops and reports rather than fixes are: type errors in generic function signatures where the correct constraint requires understanding the intended semantics, not just the call sites; errors where the fix would change the observable behavior of a function (not just the types); and files where the error count or error complexity exceeds a threshold that suggests the file's type model is fundamentally confused and needs a human to redesign it.
That last category, files where the type model is fundamentally confused, is worth discussing. A file that accumulates 50+ strict mode errors in a mature codebase is usually a file where the original author was working around type system limitations with implicit any to get something shipped. Those workarounds often encoded real design constraints: this function is intentionally polymorphic in ways that the type system cannot express without generics. Fixing those files mechanically risks losing the design intent. Pylon flags them and moves on.
Strict mode migration is one of those tasks that is genuinely well-suited to an incremental automated approach: the individual fixes are often mechanical, the scope per PR can be kept small, and the overall progress is easy to track. The alternative, a branch where one or two engineers spend four weeks fixing every error before merging, is exactly the kind of work that tends to rot in a long-running branch and never actually ship.