-
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
178: Type Assertions and Non-Null Assertions
You’ve probably hit a point in your project where TypeScript is being incredibly stubborn. You know exactly what a variable is—maybe it's a specific DOM element or a response from an API you've tested a dozen times—but TypeScript is insisting that it might be null or that it's just a generic object. In those moments, it's tempting to just force the compiler to agree with you. That's where type assertions and non-null assertions come in.
Telling the Compiler to Shut Up
The most common "quick fix" I see is the as keyword. Imagine you're grabbing a password field from a login form. document.getElementById returns a generic HTMLElement | null, but you need the value property, which only exists on HTMLInputElement. The naive way to handle this is to just assert the type and tell TypeScript to stop complaining:
const passwordInput = document.getElementById('password-field') as HTMLInputElement;
const password = passwordInput.value;
Then, you might notice that if getElementById returns null, the code still looks "correct" to the compiler, but it'll crash at runtime. To fix that, you might throw in a non-null assertion (the bang operator):
const passwordInput = document.getElementById('password-field')! as HTMLInputElement;
const password = passwordInput.value;
By adding that !, you're essentially saying, "I promise this isn't null, and if I'm wrong, feel free to crash the app." It's fast, it's concise, and in a small prototype, it feels fine.
When Your Assumptions Lie to You
Here is the problem: assertions are an escape hatch, not a solution. When you use as or !, you aren't actually changing the data; you're just lying to the type checker. I've seen countless production bugs where a developer asserted a type, then a month later, someone renamed an ID in the HTML or the API changed a field from a string to null. Suddenly, the type system—which is supposed to be your safety net—is useless because you told it to ignore the danger.
If password-field is missing from the DOM, that non-null assertion will let the code compile perfectly, but your users will get a "Cannot read property 'value' of null" error in their browser. You've effectively turned off the very feature you installed TypeScript for.
Letting the Code Prove Itself
The professional way to handle this isn't to insist you're right, but to prove you're right. Instead of assertions, use type guards. This moves the check from "compile-time guesswork" to "runtime reality."
const passwordInput = document.getElementById('password-field');
if (passwordInput instanceof HTMLInputElement) {
// Inside this block, TypeScript automatically knows
// passwordInput is an HTMLInputElement and NOT null.
const password = passwordInput.value;
console.log(password);
} else {
console.error("Password field was not found or is the wrong element type.");
}
I'll be honest: this is more typing. You have to write an if block and handle the "else" case. But this is the trade-off. By writing a few extra lines, you've transformed a potential crash into a handled error. You're no longer guessing; you're verifying. I always tell my juniors: if you find yourself using as or ! frequently, you're likely fighting the type system rather than using it. Use assertions only when you are dealing with external libraries that have truly broken types, or when you are absolutely certain the value is guaranteed by the environment (like a hardcoded HTML template that is checked into the same repo).
📋 Practical Task
Refactoring the User Settings Form
You have been handed a piece of legacy code for a User Settings page. The original developer used assertions to move quickly, but the app is crashing intermittently when users have certain browser extensions that modify the DOM.
Your Goal: Refactor the following code to remove all type assertions (as) and non-null assertions (!). Replace them with a type guard that ensures the element exists and is the correct type before accessing its properties.
// LEGACY CODE - DO NOT USE
function saveUserSettings() {
const usernameInput = document.getElementById('username')! as HTMLInputElement;
const emailInput = document.getElementById('email')! as HTMLInputElement;
const settings = {
username: usernameInput.value,
email: emailInput.value
};
console.log("Saving settings:", settings);
}
Make sure your solution handles the case where the elements might be missing or are not input elements, preventing the app from throwing a runtime error.
There are no comments for now.