← Back to TypeScript Course | Chapter 23: Performance and Best Practices | Lesson 3 of 7

Type Narrowing Best Practices

Type narrowing lets TypeScript refine a broad type into a more specific type based on runtime checks. Good narrowing code uses clear guards and avoids unsafe assertions whenever possible.

Narrow Early

Check invalid or unsupported cases as early as possible inside a function — narrowing early reduces nesting and keeps the rest of the function's logic operating on an already-validated, simpler type.

Example: Narrow Early

typescript
function process(value: string | null) {
  if (value === null) return;
  console.log(value.toUpperCase());
}
process("hello");

Use Built-in Guards

Prefer standard runtime checks like typeof, instanceof, Array.isArray, and the in operator over custom logic wherever they apply — TypeScript understands all of these natively and they communicate intent clearly to other readers.

Example: Use Built-in Guards

typescript
function describe(value: string | number | string[]) {
  if (Array.isArray(value)) return "array";
  if (typeof value === "string") return "string";
  return "number";
}
console.log(describe([1, 2]));

Use Discriminants

For a family of related object variants, add a shared literal discriminant property such as kind or status — checking that one property gives TypeScript a completely reliable way to select the correct variant in a union.

Example: Use Discriminants

typescript
type Shape = { kind: "circle"; radius: number } | { kind: "square"; side: number };
function area(shape: Shape) {
  return shape.kind === "circle" ? Math.PI * shape.radius ** 2 : shape.side ** 2;
}
console.log(area({ kind: "square", side: 4 }));

Write Custom Type Guards

When built-in guards genuinely aren't enough, write a custom function that returns a type predicate like value is User — just keep these checks scrupulously accurate, since TypeScript trusts whatever the predicate claims without verifying it further.

Example: Write Custom Type Guards

typescript
interface User { name: string; }
function isUser(value: unknown): value is User {
  return typeof (value as User)?.name === "string";
}
const data: unknown = { name: "Ravi" };
if (isUser(data)) console.log(data.name);

Avoid Unnecessary Assertions

Avoid reaching for a type assertion just to silence an error — an assertion tells the compiler to trust you without any runtime check, so prefer a real guard whenever the value originates from users, APIs, storage, or any other untrusted source.

Example: Avoid Unnecessary Assertions

typescript
function getLength(value: unknown): number {
  if (typeof value === "string") return value.length;
  return 0;
}
console.log(getLength("hello"));
🔒

Chapter Quiz — Complete all 7 topics to unlock

0/7 topics done

Complete these topics first:

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.