Type Narrowing: Your TypeScript Superpower 🦸
Imagine this: You have a magic box. Sometimes it has a toy car inside, sometimes a stuffed animal, and sometimes a book. Before you play, you need to know what’s inside. That’s exactly what Type Narrowing does in TypeScript!
The Story of the Mystery Box 📦
Picture yourself at a birthday party. There’s a mystery box on the table. You can’t just reach in blindly — you might expect a toy but grab a cactus!
TypeScript is like your smart friend who helps you peek inside the box safely. This peeking process? That’s Type Narrowing.
What is Type Narrowing?
Type Narrowing is when TypeScript figures out a more specific type from a broader one.
Think of it like this:
- 🎁 Broad type: “There’s something in the box”
- 🎯 Narrow type: “Oh! It’s definitely a teddy bear!”
function handleGift(gift: string | number) {
// gift could be string OR number
if (typeof gift === "string") {
// Now TypeScript KNOWS gift is a string!
console.log(gift.toUpperCase());
}
}
Why does this matter? Because once TypeScript knows the exact type, it lets you use special powers that only work for that type!
The Six Magic Spells of Type Narrowing ✨
Let’s learn each spell one by one!
1. The typeof Guard 🔍
The Simplest Spell!
typeof checks what kind of basic thing you have.
Real Life: Is this a word or a number?
function double(value: string | number) {
if (typeof value === "string") {
// It's a string! Repeat it twice
return value + value;
} else {
// It's a number! Multiply by 2
return value * 2;
}
}
double("hi"); // Returns "hihi"
double(5); // Returns 10
What typeof can detect:
"string"→ for words"number"→ for numbers"boolean"→ for true/false"undefined"→ for undefined"object"→ for objects (and null!)"function"→ for functions
graph TD A["Value arrives"] --> B{typeof check} B -->|"string"| C["Use string methods"] B -->|"number"| D["Use math operations"] B -->|"boolean"| E["Use in conditions"]
2. The instanceof Narrowing 🏷️
For Checking Object Types!
instanceof asks: “Were you made from this blueprint (class)?”
Real Life: Is this pet a Dog or a Cat?
class Dog {
bark() { return "Woof!"; }
}
class Cat {
meow() { return "Meow!"; }
}
function makeSound(pet: Dog | Cat) {
if (pet instanceof Dog) {
// TypeScript knows: it's a Dog!
console.log(pet.bark());
} else {
// Must be a Cat!
console.log(pet.meow());
}
}
When to use: When you’re working with classes and need to know which specific class an object came from.
3. The in Operator Narrowing 🔑
Checking if a Key Exists!
The in operator asks: “Does this object have this property?”
Real Life: Does this vehicle have wings? If yes, it’s a plane!
type Bird = { wings: number; fly(): void };
type Fish = { fins: number; swim(): void };
function move(animal: Bird | Fish) {
if ("wings" in animal) {
// Has wings? It's a Bird!
animal.fly();
} else {
// No wings? Must be a Fish!
animal.swim();
}
}
Perfect for: Telling apart objects that have different properties.
graph TD A["Animal arrives"] --> B{Has 'wings'?} B -->|Yes| C["It's a Bird! 🐦] B -->|No| D[It's a Fish! 🐟"]
4. Equality Narrowing ⚖️
Comparing Values!
When you check if something equals a specific value, TypeScript gets smarter.
Real Life: Is this fruit exactly an “apple”?
function process(
value: string | number | null
) {
if (value === null) {
// Definitely null!
console.log("Nothing here!");
return;
}
// Now value is string | number
// (null is eliminated!)
if (value === "special") {
// Exactly the string "special"
console.log("You found it!");
}
}
Both === and !== work:
function check(a: string | null) {
if (a !== null) {
// a is definitely a string now!
console.log(a.length);
}
}
5. Truthiness Narrowing 🌟
Checking if Something “Exists”!
In JavaScript, some values are “falsy” (act like false):
null,undefined0,""(empty string)false,NaN
Everything else is “truthy”!
function greet(name: string | null) {
if (name) {
// name is truthy = it exists!
// So it's a non-empty string
console.log("Hello, " + name);
} else {
// name is falsy (null or "")
console.log("Hello, stranger!");
}
}
⚠️ Watch Out!
Truthiness can be tricky with numbers:
function printCount(count: number | null) {
if (count) {
// Careful! This excludes 0 too!
console.log(count);
}
}
// Better approach:
function printCountSafe(count: number | null) {
if (count !== null) {
// Now 0 will work correctly!
console.log(count);
}
}
6. Control Flow Analysis 🌊
TypeScript’s Hidden Magic!
TypeScript watches your code like a detective. It tracks what you checked and remembers it!
function process(value: string | null) {
if (value === null) {
// Inside here: value is null
return; // Exit early!
}
// TypeScript remembers you returned
// if value was null...
// So here, value MUST be string!
console.log(value.toUpperCase());
}
The Flow Never Forgets:
graph TD A["value: string or null"] --> B{Is null?} B -->|Yes| C["Return early"] B -->|No| D["Continue..."] D --> E["value is string!"]
Complex Example:
function analyze(
input: string | number | null
) {
// Check 1: Remove null
if (input === null) {
return "Empty";
}
// Now: string | number
// Check 2: Remove number
if (typeof input === "number") {
return input * 2;
}
// Now: just string!
return input.toUpperCase();
}
TypeScript narrows the type at each step, like peeling layers of an onion!
Putting It All Together 🎯
Here’s a real-world example using multiple narrowing techniques:
type ApiResponse =
| { status: "success"; data: string }
| { status: "error"; message: string }
| null;
function handleResponse(res: ApiResponse) {
// 1. Truthiness: Check if exists
if (!res) {
console.log("No response!");
return;
}
// 2. Equality: Check status value
if (res.status === "success") {
// TypeScript knows: success type!
console.log(res.data);
} else {
// TypeScript knows: error type!
console.log(res.message);
}
}
Quick Summary 🎁
| Technique | Use When | Example |
|---|---|---|
typeof |
Checking primitives | typeof x === "string" |
instanceof |
Checking class instances | x instanceof Dog |
in |
Checking property exists | "wings" in animal |
=== / !== |
Comparing specific values | x === null |
| Truthiness | Checking if value exists | if (name) { } |
| Control Flow | TypeScript tracks automatically! | Early returns |
The Confidence Boost 💪
You now understand Type Narrowing — one of TypeScript’s most powerful features!
Remember the mystery box? You’re no longer guessing. You can:
- ✅ Peek inside with
typeof - ✅ Check the maker with
instanceof - ✅ Look for clues with
in - ✅ Compare exactly with
=== - ✅ Check existence with truthiness
- ✅ Trust TypeScript to track your flow
You’re not just writing code anymore — you’re writing safe code!
🚀 Pro Tip: When TypeScript complains about a type, think: “What check can I add to narrow this down?” Your answer is usually one of these six techniques!
