3 min read

TypeScript Strict Mode

The "strict": true option in tsconfig.json is a "meta-flag" that enables a wide range of type-checking behavior that results in stronger guarantees about program correctness. Enabling strict mode is highly recommended for all new projects.

When you enable strict, you automatically enable all of the following flags. You can also toggle them individually if you need a more granular migration path.

1. noImplicitAny

This flag raises an error on expressions and declarations with an implied any type.

  • Without strict: TypeScript falls back to any if it can't infer a type.
  • With strict: You must explicitly annotate the type.
// Error: Parameter 's' implicitly has an 'any' type.
function fn(s) {
  console.log(s.substr(3));
}

2. strictNullChecks

This is arguably the most important flag.

  • Without strict: null and undefined are valid values for every type (e.g., a number variable can hold null).
  • With strict: null and undefined are distinct types. If a variable can be null, you must declare it as a union type (e.g., string | null).
let name: string = "Alice";
// Error: Type 'null' is not assignable to type 'string'.
// name = null; 

let maybeName: string | null = "Bob";
maybeName = null; // OK

// You must check for null before using methods
// maybeName.toUpperCase(); // Error: Object is possibly 'null'.
if (maybeName) {
    maybeName.toUpperCase(); // OK
}

3. strictPropertyInitialization

Ensures that class properties are initialized in the constructor or by a property initializer.

class User {
  name: string; // Error: Property 'name' has no initializer and is not definitely assigned in the constructor.
  age: number;  // Error

  constructor() {
    // We forgot to set this.name and this.age
  }
}

Fix: Initialize them, make them optional (name?: string), or use the definite assignment assertion (name!: string) if initialized indirectly.

4. strictFunctionTypes

Enforces stricter checking for function types, specifically regarding parameter bivariance. It ensures that function arguments are contravariant (you can't pass a function that expects a specific subtype to a place that expects a base type).

function fn(x: string) {
  console.log("Hello, " + x.toLowerCase());
}

type StringOrNumberFunc = (ns: string | number) => void;

// Error: Type '(x: string) => void' is not assignable to type 'StringOrNumberFunc'.
// Types of parameters 'x' and 'ns' are incompatible.
let func: StringOrNumberFunc = fn;

5. noImplicitThis

Raises an error when this has the type any. This usually happens when using this inside a standalone function that isn't a method of a class.

function logId() {
    // Error: 'this' implicitly has type 'any' because it does not have a type annotation.
    console.log(this.id);
}

6. strictBindCallApply

Ensures that the built-in methods bind, call, and apply are invoked with the correct argument types for the underlying function.

function add(a: number, b: number) {
    return a + b;
}

// Error: Argument of type '"hello"' is not assignable to parameter of type 'number'.
add.call(undefined, 1, "hello");

7. useUnknownInCatchVariables

Changes the default type of a variable in a catch clause from any to unknown. This forces you to check the type of the error before using it.

try {
    // ...
} catch (err) {
    // Error: Object is of type 'unknown'.
    // console.log(err.message);

    if (err instanceof Error) {
        console.log(err.message); // OK
    }
}

8. alwaysStrict

Ensures that your files are parsed in ECMAScript strict mode, and emits "use strict" at the top of each source file.

programming/javascript/typescript/typescript programming/javascript/typescript/tsconfig