TypeScript Utility Types in Depth
TypeScript provides a variety of built-in utility types that you can use to transform and manipulate types.
With them, you can make your code more reusable and write code that is more concise and easier to read.
| Utility | Description (main type) | Type variables |
|---|---|---|
Partial |
Returns a new type with every property of TYPE made optional (interface) |
<TYPE> |
Required |
Returns a new type with every property of TYPE made required (interface) |
<TYPE> |
Readonly |
Returns a new type with every property of TYPE made readonly (interface) |
<TYPE> |
Record |
Returns a new type that uses KEY as its properties and TYPE as the type of their values (interface) |
<KEY, TYPE> |
Pick |
Returns a new type with the properties picked from TYPE by KEY (interface) |
<TYPE, KEY> |
Omit |
Returns a new type that omits the properties in KEY from TYPE and keeps the rest (interface) |
<TYPE, KEY> |
Exclude |
Returns a new type with TYPE2 excluded from TYPE1 (union) |
<TYPE1, TYPE2> |
Extract |
Returns a new type with TYPE2 extracted from TYPE1 (union) |
<TYPE1, TYPE2> |
NonNullable |
Returns a new type with null and undefined excluded from TYPE (union) |
<TYPE> |
Parameters |
Returns the parameter types of TYPE as a new tuple type (function, tuple) |
<TYPE> |
ConstructorParameters |
Returns the parameter types of TYPE as a new tuple type (class, tuple) |
<TYPE> |
ReturnType |
Returns the return type of TYPE as a new type (function) |
<TYPE> |
InstanceType |
Returns the instance type of TYPE (class) |
<TYPE> |
ThisParameterType |
Returns the type of the explicit this parameter of TYPE as a new type (function) |
<TYPE> |
OmitThisParameter |
Returns a new type with the explicit this parameter of TYPE removed (function) |
<TYPE> |
ThisType |
Specifies the this context of TYPE and returns nothing! (interface) |
<TYPE> |
Awaited |
Returns the resolve type of a Promise |
<TYPE> |
NoInfer |
Blocks type inference for the generic type T |
<TYPE> |
Uppercase |
Converts every character of the string literal TYPE to uppercase |
<TYPE> |
Lowercase |
Converts every character of the string literal TYPE to lowercase |
<TYPE> |
Capitalize |
Converts the first character of the string literal TYPE to uppercase |
<TYPE> |
Uncapitalize |
Converts the first character of the string literal TYPE to lowercase |
<TYPE> |
# Partial
Returns a new type with every property of TYPE made optional (?).
1Partial<TYPE>
123456789101112interface User { name: string age: number } const userA: User = { // TS2741: Property 'age' is missing in type '{ name: string }' but required in type 'User'. name: 'A' } const userB: Partial<User> = { name: 'B' }
You can think of Partial<User> in the example above like this.
1234interface NewType { name?: string age?: number }
# Required
Returns a new type with every property of TYPE made required.
1Required<TYPE>
123456789101112interface User { name?: string age?: number } const userA: User = { name: 'A' } const userB: Required<User> = { // TS2741: Property 'age' is missing in type '{ name: string }' but required in type 'Required<User>'. name: 'B' }
You can think of Required<User> in the example above like this.
1234interface User { name: string age: number }
# Readonly
Returns a new type with every property of TYPE made readonly (readonly).
See the 'Interfaces > Readonly properties' section.
1Readonly<TYPE>
12345678910111213141516interface User { name: string age: number } const userA: User = { name: 'A', age: 12 } userA.name = 'AA' // OK! const userB: Readonly<User> = { name: 'B', age: 13 } userB.name = 'BB' // TS2540: Cannot assign to 'name' because it is a read-only property.
You can think of Readonly<User> in the example above like this.
1234interface NewType { readonly name: string readonly age: number }
# Record
Returns a new object type that uses KEY as its properties (keys) and TYPE as the type of their values.
1Record<KEY, TYPE>
123456type Name = 'neo' | 'lewis' const developers: Record<Name, number> = { neo: 12, lewis: 13 }
You can think of Record<Name, number> in the example above like this.
1234interface NewType { neo: number lewis: number }
Record is useful when you need a loose object type.
1234567891011function getValues(obj: Record<string, unknown>, keys: string[]) { return keys.map(key => obj[key]) } const user = { name: 'Neo', age: 22, isValid: true, email: 'neo@gmail.com' } console.log(getValues(user, ['name', 'email'])) // ['Neo', 'neo@gmail.com']
# Pick
Returns a new object type with the properties picked from TYPE by KEY.
You can pick several properties with a union type.
1Pick<TYPE, KEY>
12345678910111213interface User { name: string age: number email: string isValid: boolean } type Key = 'name' | 'email' const user: Pick<User, Key> = { name: 'Neo', email: 'thesecon@gmail.com', age: 22 // TS2322: Type '{ name: string; email: string; age: number; }' is not assignable to type 'Pick<User, Key>'. }
You can think of Pick<User, Key> in the example above like this.
1234interface NewType { name: string email: string }
# Omit
The opposite of Pick above: it returns a new object type that omits the properties in KEY from TYPE and keeps the rest.
You can omit several properties with a union type.
1Omit<TYPE, KEY>
12345678910111213interface User { name: string age: number email: string isValid: boolean } type Key = 'name' | 'email' const user: Omit<User, Key> = { age: 22, isValid: true, name: 'Neo' // TS2322: Type '{ age: number; isValid: true; name: string; }' is not assignable to type 'Pick<User, "age" | "isValid">'. }
You can think of Omit<User, Key> in the example above like this.
123456interface NewType { // name: string age: number // email: string isValid: boolean }
# Exclude
Returns a new type with the union TYPE2 excluded from the union TYPE1.
1Exclude<TYPE1, TYPE2>
1234567type T = string | number | boolean | string[] const a: Exclude<T, number | string[]> = 'Only string' const b: Exclude<T, number> = 1234 // Type Error! ts(2322) const c: T = 'Hello world!' const d: T = 1234 console.log(a, b, c, d)
# Extract
Returns a new type with the union TYPE2 extracted from the union TYPE1.
1Extract<TYPE1, TYPE2>
1234567type T = string | number | string[] type U = number | boolean // Extract<T, U> is number const a: Extract<T, U> = 123 const b: Extract<T, U> = 'Only number' // Type Error! ts(2322) console.log(a, b)
# NonNullable
Returns a new type with null and undefined excluded from the union TYPE.
1NonNullable<TYPE>
1234type T = string | number | undefined const a: T = undefined const b: NonNullable<T> = null // TS2322: Type 'null' is not assignable to type 'string | number'.
# Parameters
Returns the parameter types of the function TYPE as a new tuple type.
1Parameters<TYPE>
12345678910function fn(a: string | number, b: boolean) { console.log(a, b) } // Parameters<typeof fn> is [string | number, boolean] const a: Parameters<typeof fn> = ['Hello', true] const b: Parameters<typeof fn> = ['Hello', true] const c: Parameters<typeof fn> = [123, false] const d: Parameters<typeof fn> = [123, 456] // Type Error! ts(2322) console.log(a, b, c, d)
# ConstructorParameters
Returns the parameter types of the class TYPE as a new tuple type.
1ConstructorParameters<TYPE>
1234567891011121314class User { constructor( public name: string, private age: number ) {} } // ConstructorParameters<typeof User> is [string, number] const a: ConstructorParameters<typeof User> = ['Neo', 12] const b: ConstructorParameters<typeof User> = ['Lewis'] // Type Error! ts(2741) console.log(a, b) const neo = new User('Neo', 12) const lewis = new User('Lewis', 34) console.log(neo, lewis)
# ReturnType
Returns the return type of the function TYPE as a new type.
1ReturnType<TYPE>
123456function fn(str: string) { return str } const a: ReturnType<typeof fn> = 'Only string' const b: ReturnType<typeof fn> = 1234 // TS2322: Type '123' is not assignable to type 'string'.
# InstanceType
Returns the instance type of the class TYPE.
1InstanceType<TYPE>
12345class User { constructor(public name: string) {} } const neo: InstanceType<typeof User> = new User('Neo')
# ThisParameterType
Returns the type of the explicit this parameter of the function TYPE as a new type.
If the function TYPE has no explicit this parameter, it returns the unknown type.
See the 'Functions > this > Explicit this' section.
1ThisParameterType<TYPE>
In the following example, the explicit this type of the function toHex is Number, and we use that type to declare the type of the parameter n of the function numberToString.
This prevents a this of a different type from being bound to toHex.
123456789// https://www.typescriptlang.org/docs/handbook/utility-types.html#thisparametertype function toHex(this: Number) { return this.toString(16) } function numberToString(n: ThisParameterType<typeof toHex>) { return toHex.apply(n) }
# OmitThisParameter
Returns a new type with the explicit this parameter of the function TYPE omitted (removed).
1OmitThisParameter<TYPE>
In the following example, the function getAge is designed around the data cat, so it can't use the new data dog, which has a different type, as this.
However, OmitThisParameter lets you create a new function type without the explicit this, so you can use the data dog without changing getAge itself.
123456789101112131415161718// Original object const cat = { age: 12 // Number } function getAge(this: typeof cat) { return this.age } getAge.call(cat) // 12 // New object const dog = { age: '13' // String } getAge.call(dog) // Type Error! ts(2345) // New function type without this const getAgeForDog: OmitThisParameter<typeof getAge> = getAge getAgeForDog.call(dog) // 13
# ThisType
Specifies the this context of TYPE and doesn't return a separate type.
1ThisType<TYPE>
The method getName, which is passed as an argument to the function makeNeo, uses this.name inside, so we set the this context explicitly with ThisType.
However, since ThisType doesn't return a separate type, the type of the return value of makeNeo ({ name: 'Neo', ...methods }) isn't inferred correctly.
So you need a separate type assertion such as as User before you can call neo.getName correctly.
123456789101112131415interface User { name: string getName: () => string } function makeNeo(methods: ThisType<User>) { return { name: 'Neo', ...methods } as User } const neo = makeNeo({ getName() { return this.name } }) neo.getName() // Neo
# Awaited
The Awaited utility type returns the resolved type of a Promise.
If Promises are nested, it finds the resolved type recursively and returns it.
1Awaited<TYPE>
1234567891011// A is string type A = Awaited<Promise<string>> // B is number type B = Awaited<Promise<Promise<number>>> // C is string | number type C = Awaited<Promise<string> | Promise<number>> // D is string | number type D = Awaited<Promise<string> | number>
A typical use is getting the type of API response data.
For example, say you have an asynchronous function that returns user information from an API (Promise<User>), like this.
123456789interface User { id: number name: string email: string } export async function fetchUser(id: number): Promise<User> { const res = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`) return res.json() }
You can get the type of the user data returned by the fetchUser function with the Awaited utility type.
Get the type of the function with typeof fetchUser, get its return type with ReturnType, and then get the resolved type with Awaited.
This way, even if the return value of the API function (fetchUser) changes, the User type updates automatically, which makes maintenance easier.
1234567891011import { fetchUser } from '@/api/user' // The return type of the `fetchUser` function is `Promise<User>`. type User = Awaited<ReturnType<typeof fetchUser>> function getUserName(user: User) { return user.name } const user = await fetchUser(1) getUserName(user)
# NoInfer
The NoInfer utility type stops the inference of a generic type variable from being widened unintentionally.
This lets you block type inference at a specific position and make it rely on the type given at another position.
1NoInfer<TYPE>
The getItem function in the following example takes a readonly array and a default item.
If the array includes the default item (defaultItem), it returns that default item; if not, it returns the first item of the array instead.
This function works fine at runtime, but no type error occurs even when the second argument isn't one of the array items.
That's because, in a call like getItem(colors, 'blue'), TypeScript uses the type variable T of defaultItem to widen the inferred type to "red" | "yellow" | "green" | "blue", including the value blue that isn't in colors.
12345678function getItem<T>(items: readonly T[], defaultItem?: T) { if (!defaultItem) return items[0] return items.includes(defaultItem) ? defaultItem : items[0] } const colors = ['red', 'yellow', 'green'] as const console.log(getItem(colors, 'yellow')) // 'yellow' console.log(getItem(colors, 'blue')) // 'red'
Now let's use the NoInfer utility type so that a type error occurs when the second argument isn't one of the array items.
Change the type of defaultItem to NoInfer<T> so that its type variable T isn't used to widen the inferred type.
Then a type error occurs when the second argument isn't one of the array items.
1234567891011function getItem<T>(items: readonly T[], defaultItem?: NoInfer<T>) { // ... } const colors = ['red', 'yellow', 'green'] as const console.log(getItem(colors, 'yellow')) // 'yellow' console.log(getItem(colors, 'blue')) // Type Error! const numbers = [7, 8, 9] as const console.log(getItem(numbers, 8)) // 8 console.log(getItem(numbers, 100)) // Type Error!
# Uppercase
Returns a new type with every character of TYPE converted to uppercase.
1Uppercase<TYPE>
1234567type Str = `hello ${string}!` type UpperStr = Uppercase<Str> const str: Str = 'hello world!' const upperStr1: UpperStr = 'HELLO WORLD!' const upperStr2: UpperStr = 'HELLO world!' // Type Error! console.log(str, upperStr1, upperStr2)
# Lowercase
Returns a new type with every character of TYPE converted to lowercase.
1Lowercase<TYPE>
1234567type Str = `HELLO ${string}!` type LowerStr = Lowercase<Str> const str: Str = 'HELLO WORLD!' const lowerStr1: LowerStr = 'hello world!' const lowerStr2: LowerStr = 'HELLO world!' // Type Error! console.log(str, lowerStr1, lowerStr2)
# Capitalize
Returns a new type with the first character of the string literal TYPE converted to uppercase.
1Capitalize<TYPE>
1234567type Str = `hello ${string}!` type CapStr = Capitalize<Str> const str: Str = 'hello world!' const capStr1: CapStr = 'Hello world!' const capStr2: CapStr = 'hello world!' // Type Error! console.log(str, capStr1, capStr2)
# Uncapitalize
Returns a new type with the first character of the string literal TYPE converted to lowercase.
1234567type Str = `HELLO ${string}!` type UncapStr = Uncapitalize<Str> const str: Str = 'HELLO WORLD!' const uncapStr1: UncapStr = 'hELLO WORLD!' const uncapStr2: UncapStr = 'HELLO WORLD!' // Type Error! console.log(str, uncapStr1, uncapStr2)
Thanks for reading to the end.
Likes and kind comments help keep this blog going!