Skip to Content
Course content
Click on the "Edit" button in the top corner of the screen to edit your slide content.

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>).

Rating
0 0

There are no comments for now.

to be the first to leave a comment.