Transform types with the built-ins — Partial, Readonly, Pick, Omit, Record, Exclude, Extract, NonNullable, Parameters, ReturnType, Awaited, and InstanceType.
Why: these build new object types from existing ones instead of duplicating shapes. Partial is the type of every "update" payload; Pick/Omit slice a type down; Record builds maps with known keys.
type User = { id: number; name: string; email: string; age: number };
// Partial<T> — every property optional. Perfect for updates.
function updateUser(id: number, changes: Partial<User>) {
console.log('patching', id, changes);
}
updateUser(1, { name: 'Alicia' }); // any subset is fine
// Readonly<T> — nothing can be reassigned
const frozen: Readonly<User> = { id: 1, name: 'Alice', email: 'a@x.dev', age: 30 };
// frozen.age = 31; // Error: read-only
// Pick<T, K> keeps listed keys; Omit<T, K> drops them
type UserPreview = Pick<User, 'id' | 'name'>; // { id; name }
type UserSafe = Omit<User, 'email'>; // everything except email
const preview: UserPreview = { id: 1, name: 'Alice' };
// Record<K, V> — known keys, uniform value type
type FeatureFlags = Record<'darkMode' | 'beta' | 'newNav', boolean>;
const flags: FeatureFlags = { darkMode: true, beta: false, newNav: true };
console.log(preview, flags);Why: these subtract from or select out of a union. Exclude removes members, Extract keeps matching ones, and NonNullable strips null and undefined — the most common cleanup of all.
type Status = 'active' | 'banned' | 'pending' | null | undefined;
// Exclude<T, U> — remove members assignable to U
type KnownStatus = Exclude<Status, null | undefined>;
// 'active' | 'banned' | 'pending'
// NonNullable<T> — shorthand for exactly that
type AlsoKnown = NonNullable<Status>;
// Extract<T, U> — keep only members assignable to U
type AppEvent =
| { type: 'click'; x: number; y: number }
| { type: 'keypress'; key: string }
| { type: 'scroll'; offset: number };
type PointerEvents = Extract<AppEvent, { type: 'click' | 'scroll' }>;
function handlePointer(e: PointerEvents) {
console.log(e.type === 'click' ? e.x : e.offset);
}
handlePointer({ type: 'click', x: 10, y: 20 });
const status: KnownStatus = 'active';
console.log(status);Why: these pull types out of functions and classes you don't control, so you never copy a shape by hand. Awaited unwraps promises — combine it with ReturnType to type any async function's result.
async function fetchUser(id: number, withPosts: boolean) {
return { id, name: 'Alice', posts: withPosts ? ['hello'] : [] };
}
// Parameters<F> — tuple of parameter types
type FetchArgs = Parameters<typeof fetchUser>;
// [id: number, withPosts: boolean]
// ReturnType<F> — what the function returns
type FetchReturn = ReturnType<typeof fetchUser>;
// Promise<{ id: number; name: string; posts: string[] }>
// Awaited<T> — unwrap the promise
type User = Awaited<FetchReturn>;
// { id: number; name: string; posts: string[] }
function render(user: User) {
console.log(user.name, user.posts.length);
}
// InstanceType<C> — what 'new C()' produces
class Db {
query(sql: string) { return [sql]; }
}
type DbInstance = InstanceType<typeof Db>;
const db: DbInstance = new Db();
fetchUser(1, true).then(render);
console.log(db.query('SELECT 1'));