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
187: NonNullable<Type>
You might be tempted to think that NonNullable<T> is just a more "official" way of doing what the non-null assertion operator (the ! guy) does. It feels like it should be a tool for telling TypeScript, "I know this is null right now, but just pretend it isn't."
Thinking NonNullable is a Runtime Assertion
Let's look at why that logic falls apart. If you try to use NonNullable to "force" a value to be non-null in your logic, you'll quickly realize it doesn't actually move the needle on the value itself. Look at this snippet:
type UserBio = string | null | undefined;
function printBio(bio: UserBio) {
// Misconception: Thinking I can "cast" the value to be non-nullable here
const cleanBio: NonNullable<UserBio> = bio;
// Error: Type 'UserBio' is not assignable to type 'string'.
// Type 'null' and 'undefined' are not assignable to type 'string'.
console.log(cleanBio.toUpperCase());
}
In the example above, the compiler yells at you. Why? Because NonNullable is a type utility, not a type assertion. It doesn't change the fact that bio might be null at runtime; it simply creates a new type definition that excludes null and undefined. I've seen developers spend an hour fighting this, thinking they're using the syntax wrong, when in reality, they're trying to use a blueprint tool to fix a construction error.
Using NonNullable to Define Strict Requirements
The real power of NonNullable comes into play when you are working with generic types or extracting types from complex objects, and you want to ensure a subsequent operation only happens with the "clean" version of that type.
Imagine you have a configuration object where most values are optional or nullable, but you have a specific helper function that must have a concrete value to function. Instead of manually writing out string | number | boolean every time, you use NonNullable to strip the noise.
interface AppConfig {
apiKey: string | null;
timeout: number | undefined;
theme: "light" | "dark" | null;
}
// I want a helper that logs the value, but ONLY if it's actually there.
// I don't want to manually write (val: string | number | "light" | "dark")
function logConfigValue<T>(val: NonNullable<T>) {
console.log(`Config value is: ${val}`);
}
const config: AppConfig = {
apiKey: "secret_123",
timeout: undefined,
theme: null,
};
logConfigValue(config.apiKey); // Works! It's a string.
// logConfigValue(config.timeout); // Error: Argument of type 'undefined' is not assignable...
// logConfigValue(config.theme); // Error: Argument of type 'null' is not assignable...
In this case, NonNullable<T> acts as a filter. It says: "Whatever T is, give me the version of it that isn't null or undefined." It's incredibly useful when you're building libraries or utility functions where you don't know the exact type beforehand, but you know you can't proceed if the value is empty.
Just remember: NonNullable defines what a type should be; it doesn't magically transform what a value actually is.
📋 Practical Task
Refactoring a Metadata Processor for Strict Types
You are working on a system that processes metadata for a blog. Some metadata fields are optional (can be null or undefined), but the saveToDatabase function requires a concrete value to avoid database corruption.
Currently, the code is using any to bypass the null checks, which is dangerous. Your task is to replace any with NonNullable to ensure that only actual values are passed to the database function.
interface BlogMetadata {
title: string | null;
tags: string[] | null;
authorId: number | undefined;
}
function saveToDatabase<T>(key: string, value: any) {
console.log(`Saving ${key}: ${value}`);
}
function processMetadata<T>(key: string, value: T) {
// FIX THIS: Use NonNullable to ensure 'value' is not null/undefined
// before calling saveToDatabase.
// You should create a local variable or a type check that
// utilizes NonNullable<T> to satisfy the compiler.
if (value !== null && value !== undefined) {
const validatedValue: NonNullable<T> = value;
saveToDatabase(key, validatedValue);
}
}
const meta: BlogMetadata = {
title: "TypeScript Tips",
tags: null,
authorId: 42
};
processMetadata("title", meta.title); // Should work
processMetadata("tags", meta.tags); // Should not call saveToDatabase
processMetadata("authorId", meta.authorId); // Should work
Goal: Modify the processMetadata function so that it uses NonNullable<T> to strictly type the value being passed to saveToDatabase, removing the need for any in the saveToDatabase signature (change value: any to value: NonNullable<T>).
There are no comments for now.