Expert · TypeScript basics · topic 54 of 64 · 35 min
Generics and utility types
Explanation
The problem generics solve
function firstAny(list: any[]): any {
return list[0];
}
const a = firstAny(["Pune", "Goa"]); // any: no help, no checks
function first<T>(list: T[]): T | undefined {
return list[0];
}
const city = first(["Pune", "Goa"]); // string | undefined
const score = first([45, 78]); // number | undefinedT is a type parameter: a placeholder that is filled in when the function is called. TypeScript usually infers it from the arguments, but you can also write it yourself: first<string>(names).
Generic types
type ApiResponse<T> =
| { ok: true; data: T }
| { ok: false; error: string };
type Product = { id: string; name: string; price: number };
const res: ApiResponse<Product[]> = {
ok: true,
data: [{ id: "p1", name: "Atta 5kg", price: 260 }],
};
if (res.ok) {
console.log(res.data[0].price); // number
}You have already used generic types: Array<string>, Promise<Response>, Record<string, number> and Map<string, number> all take a type inside the angle brackets.
Constraints: T extends something
function findById<T extends { id: string }>(items: T[], id: string): T | undefined {
return items.find((item) => item.id === id);
}
findById(products, "p1"); // works for any objects that have an id
findById([1, 2, 3], "1"); // Error: numbers have no id propertykeyof and typeof in types
type ProductKey = keyof Product; // "id" | "name" | "price"
const settings = { theme: "dark", fontSize: 16 };
type Settings = typeof settings; // { theme: string; fontSize: number }
function getField<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
getField(settings, "fontSize"); // number
getField(settings, "colour"); // Error: not a key of settingsBuilt-in utility types
| Utility | What it makes | Typical use |
|---|---|---|
| Partial<T> | Every property optional | An "edit profile" update |
| Required<T> | Every property required | Settings after defaults are filled in |
| Readonly<T> | Every property read-only | Data that must not be changed |
| Pick<T, K> | Only the properties named in K | Pick<Product, "id" | "name"> for a list view |
| Omit<T, K> | All properties except K | Omit<Product, "id"> for a "new product" form |
| Record<K, V> | An object with keys K and values V | Record<PaymentStatus, number> for counts |
| ReturnType<F> | The return type of a function type | ReturnType<typeof makeOrder> |
type NewProduct = Omit<Product, "id">; // { name: string; price: number }
type ProductChanges = Partial<NewProduct>; // { name?: string; price?: number }
function updateProduct(p: Product, changes: ProductChanges): Product {
return { ...p, ...changes }; // a new object; p is not changed
}
type PaymentStatus = "pending" | "paid" | "failed";
const counts: Record<PaymentStatus, number> = { pending: 2, paid: 10, failed: 1 };Watch the lesson
Generics and utility types · 1/7
Generics let one function work for many types and still remember which type it got.
Prefer a video by a teacher? Find free videos on “typescript generics utility types partial pick omit tutorial” on YouTube ↗
In real life: One fetch helper for a whole app
A train-booking side project calls many endpoints: stations, trains, seat availability. Instead of writing a typed fetch function for each, the developer writes one helper, getJson<T>(url: string): Promise<ApiResponse<T>>, and calls getJson<Station[]>("/api/stations") or getJson<Train[]>("/api/trains"). Every screen gets the right type, and the edit-booking form uses Partial<Booking> for the fields a user may change.
Practice (0/3 done)
Write a generic last<T>(list: T[]): T | undefined that returns the last item of the list, or undefined for an empty list. last([1, 2, 3]) returns 3, last(["Pune", "Goa"]) returns "Goa", last([]) returns undefined.
Write a generic groupBy<T>(items: T[], getKey: (item: T) => string): Record<string, T[]>. It puts the items into lists by the key that getKey returns, keeping the original order. groupBy(["apple", "avocado", "banana"], (w) => w[0]) returns { a: ["apple", "avocado"], b: ["banana"] }.
type Product = { id: string; name: string; price: number }. Write updateProduct(product: Product, changes: Partial<Omit<Product, "id">>): Product that returns a NEW product with the changes applied. The original object must not change. updateProduct({ id: "p1", name: "Tea", price: 40 }, { price: 45 }) returns { id: "p1", name: "Tea", price: 45 }.
Quick quiz
Read more (free)
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.