TypeScriptNonNullable

NonNullable

NonNullable<T> is a TypeScript utility type that removes null and undefined from a type. It is a small but powerful tool for writing null-safe code, especially when strict null checks are enabled.

What NonNullable Does

Given a type T, NonNullable<T> produces a new type with null and undefined removed from the union.

TS
type MaybeString = string | null | undefined;
type DefinitelyString = NonNullable<MaybeString>;
// Result: string

type MaybeNumber = number | null;
type DefinitelyNumber = NonNullable<MaybeNumber>;
// Result: number

type Complex = string | number | boolean | null | undefined;
type SafeComplex = NonNullable<Complex>;
// Result: string | number | boolean

// If T has no null/undefined, it is returned unchanged
type AlreadySafe = NonNullable<string>;
// Result: string
Note
The built-in definition is simply: type NonNullable<T> = T extends null | undefined ? never : T;
strictNullChecks Context

NonNullable<T> is most valuable when strictNullChecks is enabled in your tsconfig.json. Without it, null and undefined are assignable to every type and the utility has little effect.

TS
// tsconfig.json
// {
//   "compilerOptions": {
//     "strict": true  // enables strictNullChecks among other checks
//   }
// }

// With strictNullChecks ON:
let name: string = null; // Error: Type 'null' is not assignable to type 'string'

// You must explicitly opt into null/undefined:
let safeName: string | null = null; // OK
let optName: string | undefined = undefined; // OK

// NonNullable removes that optionality:
type GuaranteedName = NonNullable<string | null | undefined>;
// string
Tip
Always enable strict: true (which includes strictNullChecks) in new TypeScript projects. It prevents entire classes of runtime errors.
How NonNullable Is Defined

Under the hood, NonNullable<T> is a distributive conditional type:

type NonNullable<T> = T extends null | undefined ? never : T;

Because it is distributive, it applies the check to each member of a union independently.

TS
// Step-by-step distribution for NonNullable<string | null | undefined>:
//   string    extends null | undefined ? never : string    =>  string
//   null      extends null | undefined ? never : null      =>  never
//   undefined extends null | undefined ? never : undefined =>  never
// Combine: string | never | never  =>  string

// For NonNullable<number | string | null>:
//   number extends null | undefined ? never : number  =>  number
//   string extends null | undefined ? never : string  =>  string
//   null   extends null | undefined ? never : null    =>  never
// Combine: number | string | never  =>  number | string
Practical: Safe API Data Handling

API responses often include nullable fields. NonNullable helps you model the "after validation" shape of data clearly.

TS
interface ApiUser {
  id: number;
  name: string | null;
  email: string | null | undefined;
  avatarUrl: string | null;
}

// A validated user where required fields are guaranteed
type ValidatedUser = {
  [K in keyof ApiUser]: K extends 'name' | 'email'
    ? NonNullable<ApiUser[K]>
    : ApiUser[K];
};
// ValidatedUser['name'] is now string (not string | null)
// ValidatedUser['email'] is now string (not string | null | undefined)

function validateUser(user: ApiUser): ValidatedUser {
  if (!user.name || !user.email) {
    throw new Error('User is missing required fields');
  }
  return user as ValidatedUser;
}

const rawUser: ApiUser = { id: 1, name: 'Alice', email: 'alice@example.com', avatarUrl: null };
const validUser = validateUser(rawUser);
console.log(validUser.name.toUpperCase()); // ALICE — no null check needed
Combining with Mapped Types

You can build a Required + NonNullable combination using a mapped type to create a fully non-nullable version of any interface.

TS
// Remove null and undefined from every property
type NonNullableProperties<T> = {
  [K in keyof T]: NonNullable<T[K]>;
};

interface FormData {
  username: string | null;
  password: string | undefined;
  rememberMe: boolean | null | undefined;
}

type CleanFormData = NonNullableProperties<FormData>;
// {
//   username: string;
//   password: string;
//   rememberMe: boolean;
// }

// Combine with Required to also remove optional modifiers
type StrictFormData = Required<NonNullableProperties<FormData>>;
// All properties are required and non-nullable
Using NonNullable in Function Signatures

TS
// Filter out nulls from an array at the type level
function compact<T>(arr: (T | null | undefined)[]): NonNullable<T>[] {
  return arr.filter((x): x is NonNullable<T> => x != null);
}

const mixed = ['hello', null, 'world', undefined, 'foo'];
const clean = compact(mixed);
// Type: string[]
console.log(clean); // ['hello', 'world', 'foo']

const numbers = [1, null, 2, undefined, 3];
const safeNums = compact(numbers);
// Type: number[]
console.log(safeNums); // [1, 2, 3]

// Without the return type annotation, TypeScript would infer (string | null | undefined)[]
// NonNullable<T> makes the promise explicit
Note
The type predicate (x): x is NonNullable<T> tells TypeScript that after the filter, nulls are gone — bridging the runtime check with the type system.
NonNullable with keyof and Indexed Access

TS
interface Product {
  id: number;
  name: string;
  description: string | null;
  price: number;
  discount: number | null | undefined;
}

// Get the type of a specific field, then strip null
type ProductName = NonNullable<Product['name']>;     // string
type ProductDesc = NonNullable<Product['description']>; // string (null removed)
type ProductDisc = NonNullable<Product['discount']>; // number (null | undefined removed)

// Use it to write a safe getter
function getField<T extends object, K extends keyof T>(
  obj: T,
  key: K,
  fallback: NonNullable<T[K]>
): NonNullable<T[K]> {
  const value = obj[key];
  return (value ?? fallback) as NonNullable<T[K]>;
}

const product: Product = { id: 1, name: 'Widget', description: null, price: 9.99, discount: null };
const desc = getField(product, 'description', 'No description available');
// Type: string, Value: 'No description available'
const disc = getField(product, 'discount', 0);
// Type: number, Value: 0
NonNullable vs Optional Chaining

NonNullable is a compile-time tool. It does not change runtime behavior. Optional chaining (?.) and nullish coalescing (??) handle runtime null safety. The two approaches are complementary.

TS
interface Config {
  db: {
    host: string;
    port: number;
  } | null;
}

const config: Config = { db: null };

// Runtime guard with optional chaining
const host = config.db?.host ?? 'localhost';
console.log(host); // 'localhost'

// Type-level assertion after runtime validation
function getDb(cfg: Config): NonNullable<Config['db']> {
  if (!cfg.db) throw new Error('DB config is required');
  return cfg.db; // TypeScript now knows this is { host: string; port: number }
}

const db = getDb({ db: { host: '127.0.0.1', port: 5432 } });
console.log(db.host); // '127.0.0.1' — no null check needed
When to Use NonNullable
  • Filtering null/undefined from union types in function signatures

  • Creating "validated" versions of API response types after null checks

  • Building utility types that strip nullable properties across an object

  • Writing generic functions like compact() that remove falsy values

  • Deriving safe types from indexed access expressions

  • Combining with Required to enforce fully-populated object shapes

NonNullable with Conditional Types

TS
// Build a type that only includes keys whose value is never null/undefined
type NonNullableKeys<T> = {
  [K in keyof T]: null extends T[K] ? never
    : undefined extends T[K] ? never
    : K;
}[keyof T];

interface Order {
  id: number;
  customerId: number;
  note: string | null;
  couponCode: string | undefined;
  total: number;
}

type RequiredOrderKeys = NonNullableKeys<Order>;
// 'id' | 'customerId' | 'total'

type PickRequired<T> = Pick<T, NonNullableKeys<T>>;
type CoreOrder = PickRequired<Order>;
// { id: number; customerId: number; total: number }
Success
You now understand what NonNullable<T> does, how it works as a distributive conditional type, and how to use it in real-world patterns like compact arrays, validated API types, safe getters, and mapped types that enforce non-nullable properties.