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
127: Avoiding Type Assertion Overuse
I've seen this pattern in almost every professional codebase I've joined. Someone is fighting with the compiler, they're in a rush to hit a sprint goal, and they hit them with the "trust me" button: the type assertion. You know the one—the as keyword.
Let's look at a scenario I actually ran into last week. I was working on a user preferences module where we pull a JSON string from localStorage. If you've done this before, you know that JSON.parse returns any, which is where the trouble usually starts.
The temptation of the quick fix
Here is how I initially wrote this. I defined a UserSettings interface and just told TypeScript that the result of the parse was definitely that type.
interface UserSettings {
theme: 'light' | 'dark';
notificationsEnabled: boolean;
}
const savedSettings = localStorage.getItem('user-settings');
if (savedSettings) {
// I'm just telling TS: "Trust me, I know what's in here."
const settings = JSON.parse(savedSettings) as UserSettings;
console.log(`Current theme is ${settings.theme}`);
}
At first glance, this is great. No red squiggles. The IDE gives me autocomplete for .theme and .notificationsEnabled. It feels like I've won. But the problem is that as UserSettings isn't actually performing any check; it's just silencing the compiler.
Where it all falls apart
Now, imagine we push an update to production. We decide that theme should be more flexible, so we change the data structure in our database and our new app version. But users still have the old version of the settings object sitting in their browser's localStorage.
I'll simulate that by putting a "broken" object into storage—maybe one where theme is missing entirely or spelled incorrectly:
localStorage.setItem('user-settings', JSON.stringify({ dark_mode: true }));
// Note: 'theme' is missing, 'dark_mode' is used instead.
const savedSettings = localStorage.getItem('user-settings');
if (savedSettings) {
const settings = JSON.parse(savedSettings) as UserSettings;
// TS thinks this is safe. It's not.
console.log(`Current theme is ${settings.theme}`);
}
When I run this, the app doesn't throw a TypeScript error (because we told it to ignore the risk), but it logs "Current theme is undefined". If I had tried to call a method on settings.theme, the whole app would have crashed with a TypeError. The assertion lied to me.
Turning assertions into evidence
The goal is to move from "trusting" to "verifying." Instead of using as, I want a way to prove to TypeScript that the data is what I think it is. This is where a Type Guard comes in. I'll write a small helper function that actually checks the keys at runtime.
function isUserSettings(obj: any): obj is UserSettings {
return (
typeof obj === 'object' &&
obj !== null &&
typeof obj.theme === 'string' &&
typeof obj.notificationsEnabled === 'boolean'
);
}
const savedSettings = localStorage.getItem('user-settings');
if (savedSettings) {
const parsed = JSON.parse(savedSettings);
if (isUserSettings(parsed)) {
// Inside this block, TS knows 'parsed' is UserSettings.
// No 'as' required!
console.log(`Current theme is ${parsed.theme}`);
} else {
console.error("Settings were corrupted or outdated. Resetting to defaults.");
// Handle the error gracefully instead of crashing
}
}
Do you see the difference? By removing the as and replacing it with a type guard, I've shifted the risk. Instead of the app crashing randomly in the wild, I now have a dedicated else block where I can handle bad data. I'm no longer lying to the compiler; I'm providing it with evidence.
My rule of thumb: if you find yourself typing as, stop for a second. Ask yourself: "Do I actually know this is true, or am I just hoping it is?" If it's the latter, you need a type guard.
📋 Practical Task
Refactoring the Payment Method Validator
You've inherited a piece of code that handles payment processing. The previous developer used type assertions to handle the different payment types, which is causing intermittent runtime crashes when the API returns unexpected data.
Your task: Refactor the processPayment logic. Remove the as CreditCard and as PayPal assertions and implement a proper User-Defined Type Guard to ensure the payment data is valid before accessing its properties.
interface CreditCard {
type: 'card';
cardNumber: string;
expiry: string;
}
interface PayPal {
type: 'paypal';
email: string;
}
type PaymentMethod = CreditCard | PayPal;
function processPayment(data: any) {
// BUGGY CODE: This is using assertions and is unsafe!
if (data.type === 'card') {
const card = data as CreditCard;
console.log(`Processing card ending in ${card.cardNumber.slice(-4)}`);
} else if (data.type === 'paypal') {
const paypal = data as PayPal;
console.log(`Processing PayPal account: ${paypal.email}`);
}
}
// Test cases
processPayment({ type: 'card', cardNumber: '1234567812345678', expiry: '12/25' }); // Should work
processPayment({ type: 'paypal', email: 'user@example.com' }); // Should work
processPayment({ type: 'card' }); // Currently crashes or logs undefined; should be handled safely
There are no comments for now.