Build Your Own Type-Safe Helpers
Generic Utilities
Once you're comfortable with generics, you can write small, reusable utilities that keep their input types.
What you'll learn
- Write small generic helpers
- Preserve input types through transformations
- Recognize common patterns
A handful of small generic helpers come up everywhere.
firstOrThrow
function firstOrThrow<T>(arr: T[], msg = "Empty array"): T {
if (arr.length === 0) throw new Error(msg);
return arr[0];
}
const u = firstOrThrow([{ id: "1" }, { id: "2" }]); // { id: string } groupBy
function groupBy<T, K extends string | number>(
arr: T[],
fn: (item: T) => K
): Record<K, T[]> {
return arr.reduce((acc, item) => {
const key = fn(item);
(acc[key] ||= []).push(item);
return acc;
}, {} as Record<K, T[]>);
}
const users = [{ role: "admin" }, { role: "user" }, { role: "admin" }];
const byRole = groupBy(users, u => u.role);
// Record<"admin" | "user", typeof users[number]> uniqueBy
function uniqueBy<T, K>(arr: T[], fn: (item: T) => K): T[] {
const seen = new Set<K>();
return arr.filter(item => {
const k = fn(item);
if (seen.has(k)) return false;
seen.add(k);
return true;
});
}
uniqueBy(users, u => u.id); keys That Preserves Literal Types
function keys<T extends object>(obj: T): (keyof T)[] {
return Object.keys(obj) as (keyof T)[];
}
const user = { name: "Ada", age: 36 };
const ks = keys(user); // ("name" | "age")[] Object.keys returns string[] natively. The generic version
preserves the actual key set.
pick (Like Lodash)
function pick<T, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {
const result = {} as Pick<T, K>;
for (const k of keys) result[k] = obj[k];
return result;
}
const u = { id: "1", name: "Ada", age: 36 };
const named = pick(u, ["name", "age"]); // { name: string; age: number } We cover Pick in the utility types lesson.
The Pattern
Most useful generic utilities take a T input and produce a
SomethingOf<T> output — preserving type info through the
transformation.
Up Next
Functions with multiple type parameters.
Multiple Type Parameters →