DEV Community

Cover image for How to Enable Strict Mode and Why You Should Always Use It
Krunal Kanojiya
Krunal Kanojiya

Posted on

How to Enable Strict Mode and Why You Should Always Use It

TLDR

Strict mode in TypeScript turns on 8 safety checks at once with one line: "strict": true in your tsconfig.json. It catches null errors, missing types, uninitialized class properties, and more before your code ever runs. Always enable it on new projects. On older projects, turn on the 8 flags one at a time and fix errors as you go.


What is Strict Mode in TypeScript?

By default, TypeScript is pretty relaxed. It lets you skip types in some places. It does not check for null on every value. It allows some patterns that can crash at runtime.

Strict mode fixes all of that. It is a single switch that turns on a group of type-checking rules all at once.

Think of it like turning on every warning light in a car instead of just the engine light. You see more problems, but that means you fix them before they cause a crash.


How to Enable Strict Mode

Enabling strict mode takes one line in your tsconfig.json.

{
  "compilerOptions": {
    "strict": true
  }
}
Enter fullscreen mode Exit fullscreen mode

That is it. This one setting turns on 8 different checks at the same time.

You can also turn it on temporarily from the command line:

tsc --strict
Enter fullscreen mode Exit fullscreen mode

What Does strict: true Actually Turn On?

When you set "strict": true, TypeScript enables these 8 flags automatically:

Flag What It Checks
strictNullChecks Stops you from using null or undefined where a real value is expected
noImplicitAny Forces you to add types when TypeScript cannot figure them out
strictFunctionTypes Checks that function parameters match the expected types correctly
strictBindCallApply Makes .bind(), .call(), and .apply() type-safe
strictPropertyInitialization Makes sure class properties are set in the constructor
noImplicitThis Stops you from using this when its type is unclear
useUnknownInCatchVariables Makes the error variable in catch blocks unknown instead of any
alwaysStrict Adds "use strict" to every compiled JavaScript file

You do not need to set any of these one by one. "strict": true handles all of them.

Note: TypeScript may add new flags to the strict group in future versions. When you upgrade TypeScript, those new flags turn on automatically if you have "strict": true. This is a good thing. It means your code gets safer with every upgrade.


Why Each Flag Matters (With Code Examples)

1. strictNullChecks — Stop Null Crashes

This is the most important flag. Without it, TypeScript lets you use null and undefined anywhere. This causes runtime crashes.

Without strict:

function getLength(text: string): number {
  return text.length; // No error here
}

getLength(null); // Compiles fine. Crashes at runtime.
Enter fullscreen mode Exit fullscreen mode

With strict:

function getLength(text: string): number {
  return text.length;
}

getLength(null);
// Error: Argument of type 'null' is not assignable
// to parameter of type 'string'.
Enter fullscreen mode Exit fullscreen mode

Now TypeScript forces you to handle the null case before using the value:

function getLength(text: string | null): number {
  if (text === null) {
    return 0;
  }
  return text.length; // TypeScript knows text is a string here
}
Enter fullscreen mode Exit fullscreen mode

2. noImplicitAny — No Hidden any Types

Without this flag, TypeScript quietly gives some values the type any when it cannot figure out the type. any turns off type checking for that value. It defeats the whole point of using TypeScript.

Without strict:

function processItems(items) { // items is silently 'any'
  items.forEach(item => {
    console.log(item.name); // No type checking at all
  });
}
Enter fullscreen mode Exit fullscreen mode

With strict:

function processItems(items) {
// Error: Parameter 'items' implicitly has an 'any' type.
}
Enter fullscreen mode Exit fullscreen mode

Now you must add a type:

function processItems(items: string[]): void {
  items.forEach(item => {
    console.log(item); // TypeScript checks this correctly
  });
}
Enter fullscreen mode Exit fullscreen mode

3. strictPropertyInitialization — Class Properties Must Be Set

Without this flag, you can declare a class property and never set it. Then it is undefined at runtime and causes bugs.

Without strict:

class User {
  name: string; // Never set in constructor

  greet() {
    console.log(this.name.toUpperCase()); // Crashes at runtime
  }
}
Enter fullscreen mode Exit fullscreen mode

With strict:

class User {
  name: string;
  // Error: Property 'name' has no initializer and is not
  // definitely assigned in the constructor.
}
Enter fullscreen mode Exit fullscreen mode

Now you must either set it in the constructor or mark it as optional:

// Option 1: Set it in the constructor
class User {
  name: string;

  constructor(name: string) {
    this.name = name;
  }
}

// Option 2: Mark it optional with a default
class User {
  name: string = "Guest";
}

// Option 3: Mark it as possibly undefined
class User {
  name?: string;
}
Enter fullscreen mode Exit fullscreen mode

4. useUnknownInCatchVariables — Safe Error Handling

In older TypeScript, the error variable inside a catch block had the type any. That meant you could call any method on it without checking. This could crash.

Without strict:

try {
  doSomething();
} catch (error) {
  console.log(error.message); // error is 'any', no check done
}
Enter fullscreen mode Exit fullscreen mode

With strict:

try {
  doSomething();
} catch (error) {
  console.log(error.message);
  // Error: 'error' is of type 'unknown'.
}
Enter fullscreen mode Exit fullscreen mode

Now you must check what kind of error it is before using it:

try {
  doSomething();
} catch (error) {
  if (error instanceof Error) {
    console.log(error.message); // Safe to use
  } else {
    console.log("Unknown error occurred");
  }
}
Enter fullscreen mode Exit fullscreen mode

5. strictBindCallApply — Type-Safe Function Methods

This flag makes .call(), .bind(), and .apply() check that you pass the right argument types.

Without strict:

function greet(name: string): string {
  return `Hello, ${name}`;
}

greet.call(undefined, 42); // Wrong type, but no error
Enter fullscreen mode Exit fullscreen mode

With strict:

function greet(name: string): string {
  return `Hello, ${name}`;
}

greet.call(undefined, 42);
// Error: Argument of type 'number' is not assignable
// to parameter of type 'string'.
Enter fullscreen mode Exit fullscreen mode

6. noImplicitThis — Clear this Typing

Without this flag, using this inside a regular function is allowed even when TypeScript does not know what this points to.

Without strict:

function printName() {
  console.log(this.name); // 'this' is implicitly 'any'
}
Enter fullscreen mode Exit fullscreen mode

With strict:

function printName() {
  console.log(this.name);
  // Error: 'this' implicitly has type 'any' because it does
  // not have a type annotation.
}
Enter fullscreen mode Exit fullscreen mode

Fix it by adding an explicit this type:

function printName(this: { name: string }) {
  console.log(this.name); // TypeScript knows the shape of 'this'
}
Enter fullscreen mode Exit fullscreen mode

Two Extra Flags Worth Adding

These two flags are NOT included in strict: true. But they are worth turning on for even safer code.

noUncheckedIndexedAccess

When you access an array item by index, TypeScript normally assumes the item exists. This flag makes it return T | undefined instead.

const names: string[] = ["Alice", "Bob"];

// Without noUncheckedIndexedAccess
const first: string = names[0]; // TypeScript thinks this is always a string

// With noUncheckedIndexedAccess
const first: string | undefined = names[0]; // Safer, might be undefined
if (first !== undefined) {
  console.log(first.toUpperCase()); // Now safe to use
}
Enter fullscreen mode Exit fullscreen mode

noImplicitOverride

This flag makes you add the override keyword when you override a method in a subclass. It prevents silent bugs when the parent class changes.

class Animal {
  makeSound() {
    console.log("...");
  }
}

// Without noImplicitOverride: no error even if parent renames the method
class Dog extends Animal {
  makeSound() { // Silently breaks if parent renames makeSound()
    console.log("Woof");
  }
}

// With noImplicitOverride: you must write override
class Dog extends Animal {
  override makeSound() { // Clear intent, compiler verifies parent has this
    console.log("Woof");
  }
}
Enter fullscreen mode Exit fullscreen mode

Add them to your tsconfig.json alongside strict:

{
  "compilerOptions": {
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "noImplicitOverride": true
  }
}
Enter fullscreen mode Exit fullscreen mode

The Full Recommended Strict Config

Here is the complete set of strict options we recommend for all new TypeScript projects:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "CommonJS",
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "noImplicitOverride": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noImplicitReturns": true
  }
}
Enter fullscreen mode Exit fullscreen mode

Here is what the extra flags do:

Flag What It Does
noUnusedLocals Errors on variables that are declared but never used
noUnusedParameters Errors on function parameters that are never used
noImplicitReturns Errors when a function does not return a value on all paths

What the Same Code Looks Like Without and With Strict Mode

Here is a real example. This function has two silent bugs without strict mode.

Without strict mode (no errors shown):

function getUserCity(user) {
  return user.address.city;
}

const city = getUserCity(null);
console.log(city.toUpperCase());
Enter fullscreen mode Exit fullscreen mode

This code compiles with no errors. But it will crash at runtime because:

  • user could be null
  • user.address could be undefined
  • city could be undefined

With strict mode (all bugs caught at compile time):

// Error on line 1: Parameter 'user' implicitly has an 'any' type.
function getUserCity(user) {
  return user.address.city;
}
Enter fullscreen mode Exit fullscreen mode

TypeScript stops you right away. You now write a safe version:

type Address = {
  city: string;
};

type User = {
  address: Address | null;
};

function getUserCity(user: User | null): string {
  if (user === null || user.address === null) {
    return "Unknown";
  }
  return user.address.city;
}

const city = getUserCity(null);
console.log(city.toUpperCase()); // Always safe
Enter fullscreen mode Exit fullscreen mode

How to Enable Strict Mode on an Existing Project

If you are adding TypeScript to an older project, turning on strict: true all at once can show hundreds of errors. That is overwhelming.

Do it step by step instead.

Step 1: Keep strict off. Turn on flags one at a time.

{
  "compilerOptions": {
    "strict": false,
    "noImplicitAny": true
  }
}
Enter fullscreen mode Exit fullscreen mode

Step 2: Fix all the errors from that flag. Commit your changes.

Step 3: Add the next flag.

{
  "compilerOptions": {
    "strict": false,
    "noImplicitAny": true,
    "strictNullChecks": true
  }
}
Enter fullscreen mode Exit fullscreen mode

Step 4: Keep going until all 8 flags are on. Then replace them all with "strict": true.

Here is a good order to follow when migrating:

Step Flag to Enable Why This Order
1 noImplicitAny Easiest to fix, forces you to add types
2 strictNullChecks Most impactful, stops null crashes
3 strictPropertyInitialization Fixes class property bugs
4 strictFunctionTypes Catches function type mismatches
5 strictBindCallApply Usually few errors, easy to fix
6 noImplicitThis Quick fixes with this annotations
7 useUnknownInCatchVariables Makes catch blocks safer
8 alwaysStrict Adds "use strict" to JS output

Does Strict Mode Change the JavaScript Output?

No. Strict mode is a compile-time feature only.

All the checks happen when you run tsc. The compiled JavaScript output is identical whether strict mode is on or off.

The only exception is alwaysStrict. That flag adds "use strict" at the top of every compiled JavaScript file. But that is a JavaScript directive, not a TypeScript type change.


Common Errors You Will See After Enabling Strict Mode

Here are the errors developers hit most often after turning on strict mode.

"Object is possibly null"

const el = document.getElementById("app");
el.innerHTML = "Hello"; 
// Error: 'el' is possibly 'null'.

// Fix:
const el = document.getElementById("app");
if (el !== null) {
  el.innerHTML = "Hello";
}
Enter fullscreen mode Exit fullscreen mode

"Parameter implicitly has an 'any' type"

function double(n) { // Error
  return n * 2;
}

// Fix:
function double(n: number): number {
  return n * 2;
}
Enter fullscreen mode Exit fullscreen mode

"Property has no initializer"

class Cart {
  items: string[]; // Error
}

// Fix:
class Cart {
  items: string[] = [];
}
Enter fullscreen mode Exit fullscreen mode

"Not all code paths return a value"

function grade(score: number) {
  if (score >= 90) {
    return "A";
  }
  // Error: Function lacks ending return statement
}

// Fix:
function grade(score: number): string {
  if (score >= 90) {
    return "A";
  }
  return "B";
}
Enter fullscreen mode Exit fullscreen mode

FAQ

Q: Does strict mode make TypeScript harder to use?
At first, yes. You will see more errors. But each error points to a real bug. Over time, your code becomes more reliable and easier to maintain.

Q: Can I turn off just one strict flag?
Yes. You can override any individual flag even with strict: true. For example, if you want everything except alwaysStrict, set it to false after enabling strict:

{
  "compilerOptions": {
    "strict": true,
    "alwaysStrict": false
  }
}
Enter fullscreen mode Exit fullscreen mode

Q: Should I use strict mode on a team project?
Yes. Always. Agree on it at the start of the project so the whole team works with the same rules.

Q: Is strict: true the default?
No. TypeScript defaults to strict: false. You have to opt in. This is why you should always set it manually.

Q: Does strict mode slow down compilation?
A tiny bit. More checks take more time. But the difference is not noticeable on most projects.

Q: What is the difference between strict and strictNullChecks?
strictNullChecks is one of the 8 checks inside strict: true. Setting strict: true includes strictNullChecks plus 7 other checks. Always use strict: true instead of setting individual flags one by one on new projects.


What You Learned

  • "strict": true in tsconfig.json turns on 8 safety checks at once
  • strictNullChecks stops null crashes before they happen at runtime
  • noImplicitAny forces you to add proper types everywhere
  • strictPropertyInitialization catches unset class properties
  • useUnknownInCatchVariables makes error handling safer
  • Add noUncheckedIndexedAccess and noImplicitOverride for even more safety
  • On new projects, always start with strict: true
  • On existing projects, enable each flag one at a time and fix errors before adding the next

Buy Me A Coffee


Sources: TypeScript TSConfig Reference | TypeScript Official Docs — Strict Mode

Top comments (0)