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.
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
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.
// 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>;
// stringstrict: 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.
// 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.
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 neededCombining with Mapped Types
You can build a Required + NonNullable combination using a mapped type to create a fully non-nullable version of any interface.
// 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-nullableUsing NonNullable in Function Signatures
// 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(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
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: 0NonNullable 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.
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 neededWhen 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
// 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 }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.