# TypeScript Strict Mode: Why Enable It and How to Fix Every Error

By [Eoin Villarin](https://amvprosox.com/about). Published 2026-03-28, updated 2026-10-28. Canonical: https://amvprosox.com/blog/typescript-strict-mode-the-definitive-guide

> TypeScript 6 and 7 now turn strict mode on by default. Learn what each strict flag catches, how to migrate a large codebase step by step and how to fix common errors fast.

## Should I enable TypeScript strict mode, and how do I fix all the errors?

Yes, enable TypeScript strict mode: it turns null, implicit any and unsafe catch-variable bugs into compile errors, and TypeScript 6.0 made it the default in March 2026. For large codebases, migrate flag by flag, starting with noImplicitAny and then strictNullChecks, or file by file with a second tsconfig, and enforce it with tsc --noEmit in CI.

**Key takeaways**

- TypeScript 6.0, released in March 2026, made strict default to true, and TypeScript 7.0 kept that default.
- TypeScript's strict option currently enables nine flags, including strictNullChecks, noImplicitAny and useUnknownInCatchVariables.
- Migrate flag by flag: set strict false explicitly, enable noImplicitAny first, then strictNullChecks, then the rest.
- Prefer @ts-expect-error over @ts-ignore, because it fails once the underlying error is fixed.
- Validate external API, form and webhook data at the boundary with a Zod schema so types cannot drift.

**TypeScript strict mode** is the difference between a type checker that politely watches and one that actually stops bugs. With strict checks off, TypeScript lets `undefined` flow into places that crash at runtime, lets untyped parameters quietly become `any`, and treats every caught error as safe to read. Those are the exact bugs that show up as "Cannot read properties of undefined" in production logs.

In 2026 the question has also changed. TypeScript 6.0 turned `strict` on by default, and TypeScript 7.0 kept that default. Upgrading an older project now surfaces hundreds of errors at once, and the temptation is to set `"strict": false` and move on.

This guide explains what each strict flag does, which ones catch the most real bugs, how to migrate a large codebase without freezing feature work, and the code patterns that fix the most common errors quickly.

## Is TypeScript strict mode on by default now?

Yes. The [TypeScript 6.0 release](https://devblogs.microsoft.com/typescript/announcing-typescript-6-0/) in March 2026 made `strict` default to `true`, alongside other new defaults such as `module: esnext`, an empty `types` array and a `rootDir` fixed to the folder containing `tsconfig.json`. Projects that relied on the old default now have to write `"strict": false` explicitly to keep the lenient behavior.

![Black on and off rocker switch close-up, like one setting that enables many checks](https://amvprosox.com/api/img/1793146644889-25w145)
*One setting, nine separate safety checks. Photo: rawpixel, CC0*

Then the [TypeScript 7.0 release](https://devblogs.microsoft.com/typescript/announcing-typescript-7-0/) in July 2026 shipped the Go-based native compiler under the normal `typescript` package and `tsc` command, with full builds typically 8 to 12 times faster according to Microsoft. It behaves like 6.0 on existing code, including the strict default. Faster type checking also removes the old excuse that strict mode makes CI too slow.

- **New projects:** you get strict mode automatically. Leave it on.

- **Projects with `"strict": true` already:** nothing changes except new flags that future versions may add to the strict family.

- **Older projects with no `strict` setting:** upgrading flips you into strict mode overnight. Use the migration plan below instead of switching it off.

If you also run TypeScript files directly with Node.js type stripping, strict typing matters even more, because Node removes types without checking them. I cover that workflow in my [Node.js 22 features guide](https://amvprosox.com/blog/nodejs-22-new-features-every-developer-should-know).

## What does strict: true actually enable?

The `strict` option is a shorthand for a family of checks. According to the [official tsconfig reference](https://www.typescriptlang.org/tsconfig/#strict), it currently turns on nine flags, and you can still disable any of them individually.

| Flag | What it catches | Typical fix |
| --- | --- | --- |
| strictNullChecks | Using values that may be null or undefined | Optional chaining, ??, narrowing |
| noImplicitAny | Parameters and variables silently typed as any | Add annotations or better inference |
| useUnknownInCatchVariables | Reading properties of caught errors without checks | instanceof Error guard |
| strictFunctionTypes | Unsafe function parameter assignments | Correct callback signatures |
| strictBindCallApply | Wrong arguments to bind, call, apply | Fix argument lists |
| strictPropertyInitialization | Class fields never assigned in the constructor | Initialize, make optional or use ! |
| noImplicitThis | this with an implicit any type | Arrow functions or a this parameter |
| alwaysStrict | Emits JavaScript strict mode in every file | Usually nothing |
| strictBuiltinIteratorReturn | Built-in iterator results typed as any | Handle the undefined return value |

## Which strict flags catch the most real bugs?

Not all nine flags are equal. Three of them account for most of the errors you will fix, and most of the production crashes they prevent.

### strictNullChecks

Without it, `null` and `undefined` are assignable to every type, so the compiler cannot warn you when an API returns nothing. With it, a value typed `User | undefined` must be checked before you read `user.email`. Teams that turn it on for the first time usually find a long list of real crashes waiting to happen, especially around array lookups, optional API fields and DOM queries.

### noImplicitAny

Every implicit `any` is a hole where type checking stops. A parameter like `function total(items)` accepts anything, and every call site loses its safety. This flag forces an annotation wherever TypeScript cannot infer a type, which also makes code easier for the next developer to read.

### useUnknownInCatchVariables

JavaScript can throw anything: strings, objects, `undefined`. This flag types the `catch` variable as `unknown`, so you must check what it is before reading `err.message`. It is a small change that makes error handling and logging far more reliable.

## How to migrate a large codebase to TypeScript strict mode

Turning on every flag in one commit works for small projects. For anything with more than a few hundred errors, you need a plan that lets feature work continue while the error count goes down. I treat the error count as a metric and track it weekly.

![Calculator and pen on grid paper for tracking the error count week by week](https://amvprosox.com/api/img/1793146646076-zylayn)
*Migrate folder by folder and track the error count weekly. Photo: rawpixel, CC0*

### Option A: flag by flag

- Set `"strict": false` explicitly so the TypeScript 6 or 7 upgrade does not flood the build.

- Enable `noImplicitAny` first. Its errors are mechanical and fast to fix, and better annotations make the next step easier.

- Enable `strictNullChecks`. This is the big one, so split the work by folder or feature.

- Enable `useUnknownInCatchVariables` and `strictPropertyInitialization`.

- Remove the individual flags and set `"strict": true`.

```
{
  "compilerOptions": {
    "strict": false,
    "noImplicitAny": true,
    "strictNullChecks": true
  }
}
```

### Option B: file by file with a second tsconfig

Create a `tsconfig.strict.json` that extends your main config, turns `strict` on and lists only the files you have migrated. Run it in CI next to the normal check. New files go in the strict list from day one, and the list grows until it covers the whole project.

```
{
  "extends": "./tsconfig.json",
  "compilerOptions": { "strict": true, "noEmit": true },
  "include": ["src/lib/**/*", "src/features/checkout/**/*"]
}
```

Some teams use the community `typescript-strict-plugin` instead, which marks unmigrated files with a `//@ts-strict-ignore` comment. It works, but tools that depend on the compiler API need TypeScript 6 until the new API lands in TypeScript 7.1, so check compatibility before you upgrade. The second tsconfig approach has no such dependency.

## How to fix the most common strict mode errors

Most strict errors fall into a handful of shapes. Learn these patterns and you will clear the bulk of them quickly.

### "Object is possibly undefined"

```
type User = { id: string; profile?: { displayName?: string } }

function greet(user: User | undefined) {
  // Error: 'user' is possibly 'undefined'.
  // return `Hello, ${user.profile.displayName}`

  const name = user?.profile?.displayName ?? 'there'
  return `Hello, ${name}`
}
```

Optional chaining (`?.`) stops at the first missing value, and nullish coalescing (`??`) supplies a default only for `null` or `undefined`, unlike `||`, which also replaces `0` and empty strings.

### Narrowing with type guards

```
function isDefined(value: T | null | undefined): value is T {
  return value != null
}

const skus = cartLines.map((line) => line.sku).filter(isDefined)
```

Since TypeScript 5.5, simple callbacks such as `.filter((x) => x !== undefined)` infer the type predicate automatically, so you often do not need the helper at all.

### Errors in catch blocks

```
try {
  await saveOrder(order)
} catch (err) {
  const message = err instanceof Error ? err.message : String(err)
  logger.error({ message, orderId: order.id })
}
```

### Uninitialized class properties

Initialize the field, make it optional, or assign it in the constructor. Use the definite assignment assertion (`private client!: ApiClient`) only when a framework sets the value for you, and leave a comment saying which one.

> The non-null assertion operator (`!`) is a promise to the compiler, not a fix. Every one you add is a place where strict mode can no longer protect you, so keep them rare and searchable.

## How to validate external data at the boundary

Strict mode only guarantees what TypeScript can see. Data from an API, a form, a webhook or `localStorage` arrives as `unknown` at runtime, whatever your types claim. Parse it once at the edge of your app and the rest of your code can trust the types.

```
import * as z from 'zod'

const Order = z.object({
  id: z.string(),
  email: z.email(),
  total: z.number().nonnegative(),
  notes: z.string().optional(),
})
type Order = z.infer

const result = Order.safeParse(await res.json())
if (!result.success) throw new Error('Invalid order payload')
const order: Order = result.data
```

This pairs naturally with strict mode, because the schema and the type can never drift apart. I cover the full pattern, including shared schemas between client and server, in my guide to [type-safe APIs with TypeScript and Zod](https://amvprosox.com/blog/typescript-zod-type-safe-api-validation).

## How to keep strict mode from regressing

Once the error count reaches zero, protect it. Strict mode erodes quietly through `as any` casts and blanket `@ts-ignore` comments unless something stops them.

![Row of ticket turnstiles in a station, like a type check gate in a CI pipeline](https://amvprosox.com/api/img/1793146647306-moluef)
*A type check gate in CI keeps strict mode from eroding. Photo: chris.rycroft, CC BY 2.0*

- **Type check in CI:** run `tsc --noEmit` on every pull request and fail the build on any error.

- **Lint the escape hatches:** typescript-eslint rules like `no-explicit-any` and `ban-ts-comment` (with required descriptions) make every exception visible in review.

- **Prefer `@ts-expect-error`:** unlike `@ts-ignore`, it fails once the underlying error is fixed, so stale suppressions get cleaned up.

- **Go further when ready:** `noUncheckedIndexedAccess` and `exactOptionalPropertyTypes` are not part of `strict`, but they catch array index and optional property bugs that strict mode misses.

Coding agents are good at the mechanical part of this work, such as adding annotations and null checks across hundreds of files. I explain how I review that output safely in my post on [using AI coding agents on client projects](https://amvprosox.com/blog/ai-coding-agents-freelance-developer-workflow).

## Frequently asked questions

### Should I set strict to false after upgrading to TypeScript 6 or 7?

Only as a temporary step. Setting `"strict": false` explicitly keeps an old project building after the upgrade, but you should then enable individual flags, starting with `noImplicitAny` and `strictNullChecks`, until you can switch strict back on. Leaving it off permanently hides the bugs the new default was meant to catch.

### Does TypeScript strict mode slow down compilation?

Not in any way you will notice. Strict checks add a small amount of analysis, but the bigger factor is the compiler itself, and TypeScript 7.0's native compiler is reported to be 8 to 12 times faster on full builds than the JavaScript-based one. Strict mode costs far less time than debugging the null errors it prevents.

### Is it safe to use the non-null assertion operator?

It is safe only when you know something the compiler cannot, such as a value a framework assigns before use. Each `!` disables strict null checking at that point, so a wrong assumption becomes a runtime crash. Prefer narrowing, defaults or runtime validation, and keep remaining assertions rare and commented.

### Does strict mode affect the JavaScript output?

Almost entirely no. Strict mode changes type checking, not emitted code. The one exception is `alwaysStrict`, which adds the `"use strict"` directive to non-module files. ES modules are already strict, so for most modern projects the output is identical with or without strict mode.

## Make strict mode your default

TypeScript strict mode is now the default for good reason: it turns whole classes of runtime crashes into compile errors you fix once. Migrate flag by flag or file by file, fix the common patterns with optional chaining, narrowing and guarded catch blocks, validate data at the boundary, and lock the result in with CI.

If you have a large codebase that needs to move to TypeScript 7 and strict mode without stalling your roadmap, [send me a message through my contact form](https://amvprosox.com/#contact-form). You can see the kind of projects I build and maintain in my [case studies](https://amvprosox.com/case-studies).
