🌱BeginnerAdvanced Beginner Types
Type Narrowing
typeof, instanceof, in, truthiness, equality narrowing, and user-defined type guards.
Overview
**Type narrowing** is how TypeScript refines a broad type to a specific one based on runtime checks. When you write `if (typeof x === "string")`, TypeScript knows inside the block that `x` is `string` you can call string methods without any cast.
This is one of TypeScript's most powerful and elegant features: it understands common JavaScript patterns and automatically narrows types based on them. It tracks narrowing through assignments, control flow branches, and even `throw`s.
The tools: `typeof` (primitives), `instanceof` (class instances), `in` (property existence), truthiness checks, equality (`===`), and **type predicates** (`is`) for custom checks.
Key Points
- •`typeof value === "string"` narrows to string in the if-branch
- •`value instanceof MyClass` narrows to MyClass in the if-branch
- •`"prop" in obj` narrows to types that have the "prop" property
- •Truthiness: `if (value)` narrows out null/undefined/0/""/false/NaN
- •Equality: `if (a === b)` narrows both to their common type
- •Type predicate: `function isString(x: unknown): x is string { ... }`
- •TypeScript tracks narrowing through assignments reassigning resets the narrowed type
Examples
typeof and instanceof narrowing
function process(value: string | number | boolean): string {
if (typeof value === "string") {
return value.toUpperCase(); // value: string
}
if (typeof value === "number") {
return value.toFixed(2); // value: number
}
return value ? "TRUE" : "FALSE"; // value: boolean
}
console.log(process("hello")); // HELLO
console.log(process(3.14)); // 3.14
console.log(process(true)); // TRUE
// instanceof narrows class types
class Cat { meow() { return "meow"; } }
class Dog { bark() { return "woof"; } }
function makeSound(animal: Cat | Dog): string {
if (animal instanceof Cat) {
return animal.meow(); // animal: Cat
}
return animal.bark(); // animal: Dog
}
console.log(makeSound(new Cat())); // meow
console.log(makeSound(new Dog())); // woof"in" narrowing and truthiness
interface Fish { swim: () => void }
interface Bird { fly: () => void }
function move(animal: Fish | Bird): void {
if ("swim" in animal) {
animal.swim(); // animal: Fish (has swim)
} else {
animal.fly(); // animal: Bird (doesn't have swim)
}
}
// Truthiness narrowing eliminates null/undefined
function getLength(text: string | null | undefined): number {
if (!text) return 0; // text is falsy: null, undefined, ""
return text.length; // text: string (non-empty)
}
console.log(getLength("hello")); // 5
console.log(getLength(null)); // 0
console.log(getLength("")); // 0User-defined type guards
interface Cat { type: "cat"; meow: () => string }
interface Dog { type: "dog"; bark: () => string }
type Pet = Cat | Dog;
// Type predicate: value is Cat
function isCat(pet: Pet): pet is Cat {
return pet.type === "cat";
}
function greetPet(pet: Pet): void {
if (isCat(pet)) {
console.log("The cat says:", pet.meow()); // pet: Cat
} else {
console.log("The dog says:", pet.bark()); // pet: Dog
}
}
const cat: Cat = { type: "cat", meow: () => "meow" };
const dog: Dog = { type: "dog", bark: () => "woof" };
greetPet(cat); // The cat says: meow
greetPet(dog); // The dog says: woof
// Generic type guard
function isNonNull<T>(value: T | null | undefined): value is T {
return value !== null && value !== undefined;
}
const items = ["hello", null, "world", undefined, "!"];
const strings = items.filter(isNonNull);
// strings: string[]
console.log(strings); // ["hello", "world", "!"]Common Mistakes
- Using `typeof null === "null"` `typeof null === "object"`; always check null with `=== null`
- Writing a type predicate that lies TypeScript trusts your `is` assertion; an incorrect predicate causes silent runtime failures
- Narrowing doesn't persist across assignments if you reassign `value` after a typeof check, the narrowing is lost
Note
Type predicates (`value is Type`) are powerful but should be used sparingly they bypass TypeScript's proof. Prefer discriminated unions and built-in narrowing where possible; use type predicates only when the check is genuinely complex.
Exercises
Write TypeScript code to solve each exercise. Click Run to compile and execute it right here, then mark as done.
Loading editor…
Completed: 0 / 5 exercises
Knowledge Check
Question 1 of 4