# TypeScript Strict Mode: The tsconfig Flags That Actually Matter

> A practical breakdown of TypeScript strict mode — what each flag does, which ones strict enables, and what strict misses that you should add manually.

- **Published:** 2026-01-08
- **Author:** Clixo
- **Reading time:** 5 min read
- **Tags:** typescript, strict-mode, tsconfig, configuration, type-safety
- **Canonical URL:** https://clixo.sh/blog/typescript-strict-mode-tsconfig-flags-that-matter

`"strict": true` is the most common TypeScript configuration advice. Enable it, and TypeScript helps you more. But what does strict actually do, which bugs does it catch, and what does it miss? Most developers can answer the first question vaguely and not at all for the last two.

This guide breaks down each flag that `strict` enables and covers what strict does not enable — where you need to add flags explicitly to close real gaps.

## What TypeScript Strict Mode Enables

`strict: true` in your `tsconfig.json` is shorthand for eight specific compiler flags. Enabling `strict` is equivalent to setting all of them individually.

```mermaid
flowchart TD
  S["strict: true"] --> N1[strictNullChecks]
  S --> N2[noImplicitAny]
  S --> N3[strictFunctionTypes]
  S --> N4[strictBindCallApply]
  S --> N5[strictPropertyInitialization]
  S --> N6[noImplicitThis]
  S --> N7[alwaysStrict]
  S --> N8[useUnknownInCatchVariables]
```

### strictNullChecks

The most impactful flag. Without it, `null` and `undefined` are assignable to every type. With it, they are their own distinct types:

```typescript
// Without strictNullChecks
let name: string = null; // allowed

// With strictNullChecks
let name: string = null; // Error: Type 'null' is not assignable to type 'string'
let name: string | null = null; // correct
```

This catches the largest class of runtime errors — accessing a property on something that turned out to be null or undefined. If you only enable one flag, make it this one. `strict: true` includes it.

### noImplicitAny

Without this flag, TypeScript silently infers `any` when it cannot determine a type — particularly for function parameters:

```typescript
// Without noImplicitAny — TypeScript infers any for 'user'
function log(user) {
  console.log(user.name); // no error even if name does not exist
}

// With noImplicitAny
function log(user: User) { // must annotate explicitly
  console.log(user.name);
}
```

`any` disables type checking for that value. Implicit `any` is TypeScript silently giving up without telling you. This flag makes silence impossible — TypeScript either infers a real type or requires an explicit annotation.

### strictFunctionTypes

Makes function parameter types checked contravariantly. Without this, you can pass a function expecting a `Dog` where a function expecting an `Animal` is required, which allows type-unsafe code. With it, the check is strict in both directions.

This flag primarily affects higher-order functions and callbacks. Most developers do not notice it directly, but it closes a real soundness hole.

### strictBindCallApply

Makes `bind`, `call`, and `apply` type-checked. Without it, these methods accept any arguments and return `any`. With it, TypeScript verifies that the arguments you pass to `fn.call(thisArg, arg1, arg2)` match `fn`'s parameter types.

### strictPropertyInitialization

Requires that class properties are either initialized in the declaration or assigned in the constructor:

```typescript
class UserService {
  private db: Database; // Error: Property 'db' has no initializer

  // Fix: assign in constructor
  constructor(db: Database) {
    this.db = db;
  }
}
```

This catches the common pattern of declaring a class property but forgetting to initialize it, which would cause a runtime error the first time the property is accessed.

### noImplicitThis

Requires explicit typing of `this` in functions where TypeScript cannot determine its type automatically. Relevant mostly in class methods, event handlers, and functions passed as callbacks.

### alwaysStrict

Emits `"use strict"` at the top of every compiled JavaScript file. This is largely cosmetic in modern Node.js (which runs in strict mode by default) but ensures correct behavior in older environments.

### useUnknownInCatchVariables

Since TypeScript 4.4, catch clause variables default to `unknown` with this flag instead of `any`. This is correct — you do not know what was thrown:

```typescript
try {
  riskyOperation();
} catch (err) {
  // With this flag: err is unknown
  // Without: err is any (TypeScript lets you do anything with it)
  if (err instanceof Error) {
    console.error(err.message);
  }
}
```

## TypeScript Strict Mode: What It Misses

`strict: true` does not cover everything. These flags provide meaningful additional safety and need to be added separately.

### noUncheckedIndexedAccess

The most impactful flag not in strict. Without it, array index access returns `T`, not `T | undefined`:

```typescript
const items: string[] = [];
const first = items[0]; // string — TypeScript lies
first.toUpperCase();    // runtime crash

// With noUncheckedIndexedAccess:
const first = items[0]; // string | undefined — TypeScript is honest
```

This catches a very common class of runtime crash. The fix is usually a nullish coalescing or an explicit guard. Enable it, fix the errors, ship with confidence.

### noImplicitReturns

Catches functions that fall through without returning a value in some branches:

```typescript
function getLabel(status: string): string {
  if (status === "active") return "Active";
  // forgot to handle other cases — implicit return is undefined
}
```

With `noImplicitReturns`, the function above is a compile error. Add it.

### exactOptionalPropertyTypes

Without this flag, setting an optional property to `undefined` is the same as having it present with an undefined value — which is semantically different from omitting it:

```typescript
interface Config {
  timeout?: number;
}

const cfg: Config = { timeout: undefined }; // allowed without exactOptionalPropertyTypes
```

With it, the above is an error. You must either omit `timeout` or set it to a `number`. This matters when serializing to JSON or when the receiving code checks `"timeout" in cfg`.

## The Recommended Production Combination

```json
{
  "compilerOptions": {
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "noImplicitReturns": true,
    "exactOptionalPropertyTypes": true,
    "noFallthroughCasesInSwitch": true
  }
}
```

Start here. These flags together close the most common gaps between "TypeScript says it is fine" and "this crashes in production."

---

If your team wants to get TypeScript configuration right from the start — or tighten up an existing codebase — [Clixo](https://clixo.sh/#contact) can audit your setup and help you ship with higher confidence.

---

Clixo · 1141 W Bryn Mawr Ave, Itasca, IL 60143, US · [hello@clixo.sh](mailto:hello@clixo.sh)
[Start a build](https://clixo.sh/#contact) · [All services](https://clixo.sh/services) · [Agent guide (llms.txt)](https://clixo.sh/llms.txt)
