WritingTypeScript Strict Mode: Useful tsconfig Flags — LinkIT
5 min readtypescript, strict-mode, tsconfig, configuration, type-safety

TypeScript Strict Mode: Useful tsconfig Flags

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

"strict": true enables a family of TypeScript checks. Additional flags can catch unchecked indexing, missing returns, and ambiguous optional properties that strict mode alone does not cover.

What TypeScript Strict Mode Enables

strict: true enables the strict-mode family of checks. The following list covers several of them; it is not exhaustive across compiler versions. TypeScript can add checks to that family, so review errors after an upgrade. See the strict option reference.

strictNullChecks

With strictNullChecks, null and undefined have distinct types and cannot be assigned wherever an ordinary value is expected:

// 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 potential access to null or undefined values before runtime. It is included in strict: true.

noImplicitAny

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

// 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 bypasses many type checks for a value. noImplicitAny reports cases where TypeScript would infer it without an annotation; explicit any remains permitted.

strictFunctionTypes

Checks function parameter compatibility more strictly, preventing unsafe substitutions such as a dog-only handler where any animal may be passed. The check applies to function syntax but excludes method syntax for compatibility. See strictFunctionTypes.

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:

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

Parses source files in ECMAScript strict mode and emits "use strict" where applicable. Strict mode changes runtime behavior; it is not merely cosmetic. See alwaysStrict.

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:

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

For potentially absent indexed values, noUncheckedIndexedAccess adds undefined to the result type:

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

Callers then need a guard or fallback where an indexed value might be absent.

noImplicitReturns

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

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:

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.

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

Try this configuration and review the resulting errors. Type checks help identify mistakes but do not replace runtime validation or behavioral tests.


Discuss a TypeScript configuration review with LinkIT.