Lesson 14 of 25
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.
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>; // stringPulling 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 youAwaited<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
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.Excluderemoves listed union members;Extractkeeps only the listed ones;NonNullableis Exclude specialised fornull | undefined.typeof someFunctionturns a function value back into a type, whichReturnTypeandParameterscan then extract from.Awaited<T>unwraps aPromisetype down to the value you actually get afterawait— needed because an async function's declared return type is always wrapped inPromise.
Check yourself
4 questions · pass 3/4 to unlock Classes and Access Modifiers
1.What does Record<"admin" | "editor" | "viewer", number> produce?
2.Given
type Status = "pending" | "done" | "failed";, what doesExclude<Status, "failed">produce?3.For
function getUser(id: string): { name: string } {}, what does ReturnType<typeof getUser> produce?4.Why is Awaited<T> needed in addition to ReturnType<T> for an async function?
4 left to answer