AniUI Academy

Utility Types, Part 2: Record, Exclude, Extract, and the Function Ones

Record for typed dictionaries, Exclude and Extract for filtering unions, and pulling a function's types back out with ReturnType, Parameters, and Awaited.

10 min read

The previous lesson covered utility types that reshape an object type. This one covers building a dictionary type from a union of keys, filtering one union down using another, and pulling types back out of an existing function — useful whenever you don't own the original declaration and don't want to duplicate it by hand.

Record<Keys, Value>

Builds an object type with a specific, exact set of keys, all sharing one value type:

type Role = "admin" | "editor" | "viewer";
 
const permissionCounts: Record<Role, number> = {
  admin: 12,
  editor: 34,
  viewer: 108,
};

Every key in Role is required — omitting viewer is a compile error, and adding an extra key not in Role is one too. This is the important difference from an index signature ({ [key: string]: number }), which allows any string key and doesn't require any specific ones to be present:

const permissionCounts2: Record<Role, number> = {
  admin: 12,
  editor: 34,
  // Property 'viewer' is missing in type '{ admin: number; editor: number; }'
  // but required in type 'Record<Role, number>'.
};

Record<string, T> (with the general string type, not a specific union) behaves like an index signature instead, since there's no fixed set of keys to enforce.

Exclude<Union, Members> and Extract<Union, Members>

A pair of opposites, both operating on unions rather than object shapes:

type Status = "pending" | "active" | "done" | "failed" | "cancelled";
 
type Unfinished = Exclude<Status, "done" | "failed" | "cancelled">;
// "pending" | "active"
 
type Terminal = Extract<Status, "done" | "failed" | "cancelled">;
// "done" | "failed" | "cancelled"

Exclude removes the listed members, keeping what's left. Extract does the reverse — keeps only the listed members, discarding everything else. Both are especially useful for deriving a smaller, related union from a larger one that already exists, rather than redeclaring it and risking the two drifting apart.

NonNullable<T> is a specific, common case of Exclude — it removes null and undefined from a type:

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

Pulling types back out of a function

Sometimes you don't own a function's declaration — it's from a library, or defined elsewhere in the codebase — but you need the type it returns, or the types of its parameters, without duplicating them by hand.

ReturnType<T> extracts a function type's return type:

function getUser(id: string) {
  return { id, name: "Amara", age: 29 };
}
 
type User = ReturnType<typeof getUser>;
// { id: string; name: string; age: number }

Note the typeof here — getUser on its own is a value (a function you can call), and utility types operate on types. typeof getUser converts the value back into its type, which ReturnType can then operate on. This combination — typeof plus a utility type — is extremely common whenever you want to derive a type from existing runtime code instead of the other way around.

Parameters<T> extracts a function's parameter types as a tuple:

type GetUserParams = Parameters<typeof getUser>; // [id: string]

Awaited<T>: unwrapping a Promise

An async function's declared return type is always a Promise, wrapping the value you actually get after await:

async function fetchUser(id: string) {
  return { id, name: "Amara" };
}
 
type Direct = ReturnType<typeof fetchUser>;
// Promise<{ id: string; name: string; }> — not what you actually receive
 
type Resolved = Awaited<ReturnType<typeof fetchUser>>;
// { id: string; name: string; } — what "await fetchUser(...)" actually gives you

Awaited<T> recursively unwraps a Promise type (including a promise that itself resolves to another promise, which await also flattens at runtime), landing on the actual value type. Combining it with ReturnType like this is the standard way to derive "what does this async function actually resolve to" without writing that type out separately and risking it drifting from the real implementation.

Try it yourself

Try it yourself
Loading playground...

What to remember

  • Record<Keys, Value> builds a dictionary type requiring exactly the given keys — unlike an index signature, missing keys are a compile error.
  • Exclude removes listed union members; Extract keeps only the listed ones; NonNullable is Exclude specialised for null | undefined.
  • typeof someFunction turns a function value back into a type, which ReturnType and Parameters can then extract from.
  • Awaited<T> unwraps a Promise type down to the value you actually get after await — needed because an async function's declared return type is always wrapped in Promise.

Check yourself

4 questions · pass 3/4 to unlock Classes and Access Modifiers

up to 50
  1. 1.What does Record<"admin" | "editor" | "viewer", number> produce?

  2. 2.Given type Status = "pending" | "done" | "failed";, what does Exclude<Status, "failed"> produce?

  3. 3.For function getUser(id: string): { name: string } {}, what does ReturnType<typeof getUser> produce?

  4. 4.Why is Awaited<T> needed in addition to ReturnType<T> for an async function?

4 left to answer