059 — You are a Junior Dev if You Don’t Know These 18 TypeScript Utility Types
The video works through all 18 utility types against one base `User` type (`id`, `name`, `age`, `address`), motivated by three functions that each want a different slice of it: `createUser` wants everything except `id`, `updateUser` wants any subset, and `renderUserDetails` wants only `name` and `age`. `Pick` and `Omit` carve out exact subsets; `Partial` and `Required` flip optionality; `Readonly`
Video: 059 · You are a Junior Dev if You Don’t Know These 18 TypeScript Utility Types · watch · ~22:09 · Web Dev Simplified
Source: this is a written companion to the video. Part of the Favorite TypeScript Videos curated path.
The video works through all 18 utility types against one base User type (id,
name, age, address), motivated by three functions that each want a different
slice of it: createUser wants everything except id, updateUser wants any
subset, and renderUserDetails wants only name and age. Pick and Omit
carve out exact subsets; Partial and Required flip optionality;
Readonly freezes one level; Record builds keyed objects; Extract/Exclude
narrow unions; ReturnType/Parameters/ConstructorParameters derive types from
functions and classes; NonNullable strips null/undefined; Awaited unwraps
promises; and four string types (Uppercase/Lowercase/Capitalize/
Uncapitalize) rewrite casing. The refrain throughout: derive every type from one
base so a single change propagates everywhere. A light/dark-mode cheat sheet with
all examples is linked in the description.
The setup — one base type, three mismatched consumers
type User = {
id: string;
name: string;
age: number;
address: { street: string; city: string };
};
Creating a user should not require an id — the database generates that. Updating a
user should not require every property. And rendering user details only needs name
and age, yet the signature demands a full User.
Pick and Omit — exact subsets
function renderUserDetails(user: Pick<User, "name" | "age">) {
/* ... */
}
function createUser(user: Omit<User, "id">) {
/* ... */
}
Pick takes the source object type plus a union of keys to keep — Pick<User, "name" | "age"> is the name-and-age subset. Omit is the exact opposite: Omit<User, "id"> is every user property except the ID. Hand-writing the subset also works,
but breaks the single-source rule — change name or age on the base later and a
derived Pick follows automatically.
That is the whole idea behind utility types: one base type, with every consumer deriving from it so a single change propagates everywhere.
Partial and Required — flipping optionality
function updateUser(user: Partial<User>) {
/* any subset of fields */
}
function createUserWithAddress(user: Required<User>) {
/* address mandatory */
}
Partial<User> takes one generic parameter and makes every key optional — pass as
many or as few fields as wanted. Required does the exact opposite, forcing every
normally-optional property to be passed — demonstrated by making an optional
address mandatory.
Readonly — shallow immutability
The JS parallel is Object.freeze, which locks an object against modification.
Readonly<User> prepends readonly to every property — but only one level deep,
so address.street stays writable while id, name, age, and address itself
cannot be reassigned.
Record — objects with fixed or open keys
type T = Record<string, User>; // any string key → User
type Roles = Record<"admin" | "user", { test: string }>; // forced keys
type Open = Record<PropertyKey, { test: string }>; // any valid key
Record takes keys first, value type second. With a union of keys the object always
has both — omitting admin or user errors; PropertyKey covers anything valid
as a JavaScript key.
Extract and Exclude — narrowing unions
type Role = "admin" | "user" | "moderator";
type T1 = Extract<Role, "admin" | "moderator">; // "admin" | "moderator"
type T2 = Exclude<Role, "user">; // "admin" | "moderator"
Extract keeps only members present in both unions — their intersection. Exclude
is the opposite, removing every member named in the second union; the everyday form
passes literal members to drop, handy for enum-adjacent unions.
ReturnType and Parameters — types from functions
function getUser(id: string) {
return { name: "Kyle", id };
}
type T = ReturnType<typeof getUser>; // { name: string; id: string }
type P = Parameters<typeof getUser>; // [id: string]
Since getUser is a value, not a type, typeof getUser recovers its type for
ReturnType. Adding a property (e.g. age) to the returned object propagates
automatically.
Parameters returns a tuple of all parameters — [string, number] for
(id: string, age: number). The killer use is wrappers: a
getUserWrapper re-passing id links the two signatures, so deriving the outer
param via Parameters<typeof getUser>[0] keeps them in sync. The headline
ReturnType case is libraries: when a library function returns a type it never
exports, ReturnType recovers it for use in calling code.
ConstructorParameters (and why InstanceType is pointless here)
Parameters rejects constructors, so classes get ConstructorParameters — the same
idea, but working on new. InstanceType on a class is noted only to dismiss: it
returns exactly the class’s own type, so there is no point writing it out.
NonNullable vs Required
type T = NonNullable<string | null | undefined>; // string
NonNullable takes one type and strips null and undefined from it. It differs
from Required: Required drops the ? but keeps an explicit | null — it forces
presence, not non-nullability. To remove nullability itself, use NonNullable.
Awaited — unwrapping promises
async function getUser() {
return Promise.resolve({ name: "Kyle", id: "1" });
}
type T = Awaited<ReturnType<typeof getUser>>; // { name: string; id: string }, not Promise<...>
Awaited takes a single type and strips every level of promise nesting, leaving
whatever the promise resolves to. The standard pattern is Awaited<ReturnType<...>>
on async functions — usually the resolved value is what matters, not the promise.
String transforms — Uppercase/Lowercase/Capitalize/Uncapitalize
Lowercase/Uppercase rewrite the whole literal; Uncapitalize/Capitalize
rewrite only the first letter. Seemingly useless until metaprogramming — converting
PascalCase type names into camelCase keys is the perfect use case.




Transcript corrections applied above: ID (not IED); pass a Partial (not parse a partial);
Readonly utility type (not read only property / read only type); boolean (not booin);
enums (not enumbs); prepends readonly (not prepins read only); strip out (not strap out);
PropertyKey (not property key (fancy type)).
Prev: 058 — Introduction To React #2 | JSX · Next: 060 — Learn Advanced TypeScript In 25 Minutes