TypeScript
Completed
-
Section 1: Getting Started
-
Section 2: Basic Types
-
Section 3: Functions and Objects
-
Section 4: Advanced Types
-
Section 5: Object-Oriented TypeScript
-
Section 6: Working with Modules
-
Section 7: Tooling and Practice
-
Section 8: Type-Level Programming
-
Section 9: TypeScript with Backends
-
Section 10: Testing Typed Code
-
Section 11: Data Validation with Types
-
Section 12: Practical Projects
-
Section 13: Compiler Internals
-
Section 14: Configuration Deep Dive
-
Section 15: Enums, Symbols, and Special Types
-
Section 16: Working with Async Code
-
Section 17: TypeScript and the DOM
-
Section 18: Advanced Generics Practice
-
Section 19: Working with Third-Party Types
-
Section 20: Monorepo and Large-Scale Practices
-
Section 21: Common Pitfalls and Best Practices
-
Section 22: Interview Practice
-
Section 23: Handbook: Narrowing In Depth
-
Section 24: Handbook: Object Types In Depth
-
Section 25: Handbook: Classes In Depth
-
Section 26: Handbook: Modules In Depth
-
Section 27: Handbook: Declaration Files In Depth
-
Section 28: JSX and Namespaces
-
Section 29: Compiler Configuration Reference
-
Section 30: More Practice Exercises
-
Section 31: Handbook: Everyday Types Deep Dive
-
Section 32: Utility Types Full Reference
-
Section 33: Decorators Reference
-
Section 34: Mixins and Advanced OOP Patterns
-
Section 35: Iterators and Generators Typing
-
Section 36: More Type-Level Programming Practice
-
Section 37: TypeScript Ecosystem Tools
-
Section 38: TypeScript with Testing Frameworks
-
Section 39: TypeScript for Library Authors
-
Section 40: More Interview Practice
-
Section 41: Handbook: Functions In Depth
-
Section 42: Handbook: Type Manipulation Deep Dive
-
Section 43: More Real-World Patterns
-
Section 44: TypeScript Release Notes Highlights
-
Section 45: Final Practice and Review
138: instanceof Narrowing
Wait, how does TypeScript actually know the type changed just because I used instanceof?
It's all about what I call "type guards." At runtime, instanceof is a standard JavaScript operator that checks if an object's prototype chain contains the prototype property of the constructor you're testing against. TypeScript's compiler is smart enough to recognize this specific pattern.
When you wrap code in an if (obj instanceof ClassName) block, TypeScript says, "Okay, if this block executes, obj must be an instance of ClassName." It then narrows the type for you within that block, so you get full autocomplete and type safety for that specific class's properties.
class User {
name: string = "Anonymous";
}
class Admin extends User {
privileges: string[] = ["delete_user", "edit_settings"];
}
function printDetails(person: User) {
console.log(person.name); // Everyone has a name
if (person instanceof Admin) {
// TypeScript knows 'person' is an Admin here
console.log(person.privileges);
}
}
Can I use this to narrow down interfaces too?
Short answer: No. This is one of the most common points of frustration for people moving from other languages. Here's the deal: interfaces in TypeScript are "virtual." They only exist during development to help you catch bugs; they are completely erased when the code is compiled to JavaScript.
Since instanceof is a runtime check, it needs something that actually exists at runtime—like a class. If you try to use instanceof with an interface, the compiler will scream at you because there's no constructor function to check against in the final JS bundle. If you're stuck with interfaces, you'll want to use a "discriminated union" (adding a type string property to your objects) instead. I've found that's usually a more robust pattern anyway.
Does the order of my checks matter if I have a class hierarchy?
Absolutely. Think of it like a funnel. If you check for the base class first, you're catching everything, including the subclasses. If you do that, you're still stuck with the base type inside that block.
Look at this example with a payment system. If I check for Payment first, I can't access cardNumber because a PayPalPayment is also a Payment, but it doesn't have a card number.
class Payment {
amount: number = 0;
}
class CreditCardPayment extends Payment {
cardNumber: string = "";
}
class PayPalPayment extends Payment {
email: string = "";
}
function processPayment(payment: Payment) {
// WRONG ORDER:
if (payment instanceof Payment) {
// payment is narrowed to Payment, but we still don't know
// if it's CreditCard or PayPal.
}
// RIGHT ORDER: Check the most specific types first
if (payment instanceof CreditCardPayment) {
console.log(payment.cardNumber);
} else if (payment instanceof PayPalPayment) {
console.log(payment.email);
}
}
I always tell my juniors: go from the "leaves" of the inheritance tree back up to the "root."
📋 Practical Task
Implementing a Custom API Error Handler
You are building a network layer for an app. You have a base ApiError class and two specific subclasses: NetworkError (for connectivity issues) and ValidationError (for 400-level responses).
Your task is to complete the handleError function. Use instanceof to narrow the error type and log the specific property associated with each error: NetworkError has a retryAfter number, and ValidationError has a fields array of strings.
class ApiError {
message: string;
constructor(message: string) {
this.message = message;
}
}
class NetworkError extends ApiError {
retryAfter: number = 30;
}
class ValidationError extends ApiError {
fields: string[] = [];
constructor(message: string, fields: string[]) {
super(message);
this.fields = fields;
}
}
function handleError(error: ApiError) {
// TODO: Implement narrowing here
// 1. If it's a NetworkError, log the retryAfter value.
// 2. If it's a ValidationError, log the fields array.
// 3. Otherwise, just log the generic message.
}
// Test cases
handleError(new NetworkError("Timeout"));
handleError(new ValidationError("Invalid Input", ["email", "password"]));
handleError(new ApiError("Unknown error"));There are no comments for now.