Advanced TypeScript Utility Types Explained

extends

The extends keyword in TypeScript serves two primary purposes: interface inheritance and conditional type evaluation.

Interface Inheritance

In TypeScript, extends enables interfcae inheritance similar to class inheritance in ES6. Here's an example:

interface IName {
  name: string;
}

interface IAge {
  age: number;
}

interface IPerson extends IAge, IName {
  sex: string;
}

const obj: IPerson = {
  age: 18,
  name: 'Jenny',
  sex: 'female'
};

In this case, IPerson inherits properties from both IAge and IName, combining them with its own property sex.

Conditional Type Evaluation

Conditional types use extends for type checking and selecting between two types based on the relationship between them:

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

let typeString: WhatType<string> = 'abc'; // string
let typeNull: WhatType<null> = undefined; // never

If T is either null or undefined, it resolves to never; otherwise, it returns T.

infer

The infer keyword allows declaring type variables within conditional types for inference.

type ParamType<T> = T extends (...args: infer P) => any ? P : T;

interface User {
  name: string;
  age: number;
}

type Func = (user: User) => void;
type Param = ParamType<Func>; // Param = User
type AA = ParamType<string>; // string

Here, infer P captures the paramter types of a function.

keyof

The keyof operator extracts all keys of a type, returning a union type.

interface IPerson {
  name: string;
  age: number;
}

type allKeys1 = keyof IPerson; // 'name' | 'age'
type allKeys2 = keyof IPerson[]; // 'length' | 'toString' | ...
type allKeys3 = keyof { [x: string]: IPerson }; // string | number

typeof

The typeof operator retrieves the type of a variable or property.

interface IPerson {
  name: string;
  age: number;
}

const user: IPerson = {
  name: 'jenny',
  age: 18
};

type student = typeof user; // IPerson

in

The in operator iterates over union types.

type Keys = 'a' | 'b' | 'c';
type Obj = {
  [p in Keys]: any;
}; // { a: any, b: any, c: any }

Partial

Converts all properties of a type into optional ones.

type Partial<T> = {
  [P in keyof T]?: T[P];
};

interface IPerson {
  name: string;
  age: number;
}

type Person = Partial<IPerson>;
// Equivalent to:
// type Person = {
//   name?: string | undefined;
//   age?: number | undefined;
// };

Required

Makes all properties required.

type Required<T> = {
  [P in keyof T]-?: T[P];
};

interface IPerson {
  name?: string;
  age?: number;
}

type Person = Required<IPerson>;
// Equivalent to:
// type Person = {
//   name: string;
//   age: number;
// };

Readonly

Sets all properties to read-only.

type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};

interface IPerson {
  name: string;
  age: number;
}

type Person = Readonly<IPerson>;
// Equivalent to:
// type Person = {
//   readonly name: string;
//   readonly age: number;
// };

const obj: Person = {
  name: 'lucy',
  age: 18
};
// obj.name = 'jenny'; // Error: Cannot assign to read-only property

Mutable

Removes the read-only modifier from all properties.

type Mutable<T> = {
  -readonly [P in keyof T]: T[P];
};

interface IPerson {
  readonly name: string;
  readonly age: number;
}

type Person = Mutable<IPerson>;
// Equivalent to:
// type Person = {
//   name: string;
//   age: number;
// };

Exclude

Removes types from one union that exist in another.

type Exclude<T, U> = T extends U ? never : T;

type Excluded0 = Exclude<string | number | boolean, number>; // string | boolean

type Excluded1 = Exclude<'a' | 'b' | 'c', 'a' | 'c'>; // 'b'

Extract

Retrieves types from one union that also exist in another.

type Extract<T, U> = T extends U ? T : never;

type Extracted0 = Extract<string | number | boolean, number>; // number

type Extracted1 = Extract<'a' | 'b' | 'c', 'a' | 'c'>; // 'a' | 'c'

Pick

Selects specific properties from a type.

type Pick<T, K extends keyof T> = {
  [P in K]: T[P];
};

interface IPerson {
  name: string;
  age: number;
  sex: string;
}

type Person = Pick<IPerson, 'name'>;

const user: Person = {
  name: 'jenny',
  // age: 18 // Error: Property 'age' does not exist on type 'Person'
};

Record

Creates an object type with keys from a union and values of a specified type.

type Record<K extends keyof any, T> = {
  [P in K]: T;
};

type Record0 = Record<'a' | 'b', number>;
// Equivalent to:
// type Record0 = {
//   a: number;
//   b: number;
// };

Omit

Excludes specific properties from a type.

type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

interface IPerson {
  name: string;
  age: number;
  sex: string;
}

type Person = Omit<IPerson, 'name'>;
// Equivalent to:
// type Person = {
//   age: number;
//   sex: string;
// };

ReturnType

Obtains the return type of a function.

type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;

type T1 = ReturnType<() => string>; // string
type T2 = ReturnType<(s: string) => void>; // void

NonNullable

Removes null and undefined from a union type.

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

type T = NonNullable<string | string[] | null | undefined>; // string | string[]

Tags: TypeScript utility-types generics conditional-types type-inference

Posted on Sun, 26 Jul 2026 16:52:11 +0000 by nakins