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
134: typeof Type Guards
I was working on a configuration parser the other day and ran into a classic TypeScript annoyance. I had a variable that could either be a string (the name of a setting) or a number (the value of that setting). In my head, it was simple, but the compiler wasn't having it.
The "Property Does Not Exist" Wall
Let's look at what I tried first. I wanted a function that would format this setting for a log file. If it's a string, I want it uppercase; if it's a number, I just want to append a unit to it.
function formatSetting(input: string | number) {
console.log(input.toUpperCase());
}
As soon as I typed .toUpperCase(), TypeScript threw a red squiggle under the whole thing. The error basically said: "Property 'toUpperCase' does not exist on type 'string | number'. Property 'toUpperCase' does not exist on type 'number'."
It's technically correct, of course. If input happens to be 42, calling toUpperCase() would crash the program. TypeScript is protecting me from my own laziness here, but now I'm stuck. I know how to check types in plain JavaScript, so I figured I'd just use a standard if statement.
Letting TypeScript Observe the Check
I decided to wrap the string logic in a check. I used the typeof operator, which we've all used in JS for years. I didn't think anything special would happen—I just wanted the code to run without crashing.
function formatSetting(input: string | number) {
if (typeof input === 'string') {
// I'm inside the block now. Let's try again.
console.log(input.toUpperCase());
} else {
console.log(input + "px");
}
}
Here is the "aha!" moment. Inside that if block, the red squiggle is gone. I hovered my mouse over input, and TypeScript now tells me it's a string. Not a string | number, but specifically a string.
This is what we call a Type Guard. TypeScript isn't just ignoring the type; it's actually performing "narrowing." It sees that the only way the code can reach that line is if typeof input === 'string' evaluates to true. Therefore, it logically concludes that for the duration of that block, input must be a string.
The Logic of the Else Block
The coolest part is what happens in the else block. I didn't explicitly check if the input was a number. I just said "if it's not a string, do this."
Because I defined the input as string | number, and I've already ruled out string, TypeScript is smart enough to realize that the only remaining possibility is number. If I tried to call .toUpperCase() inside the else block now, TypeScript would scream at me again, because it knows for a fact that input is a number at that point in the execution flow.
It's a very clean way to handle polymorphic data without having to use awkward type assertions (like using as string), which—in my opinion—you should avoid whenever possible because they tell the compiler to "shut up and trust me," which is how bugs sneak into production.
📋 Practical Task
Build a Notification Payload Validator
You are building a notification system where a "payload" can either be a string (a direct message) or a number (an error code). Create a function called processNotification that accepts a union type string | number.
- If the payload is a
string, return the string prefixed with"Message: ". - If the payload is a
number, return the string"Error Code: "followed by the number. - Use a
typeoftype guard to ensure TypeScript recognizes the type in each branch of your logic.
There are no comments for now.