Skip to content

Expert · TypeScript basics · topic 54 of 64 · 35 min

Generics and utility types

Explanation

A generic is a type with a blank to fill in, written <T>. It lets one function or type work with many types and still remember which one it got: first(["Pune", "Goa"]) gives a string, first([45, 78]) gives a number. Utility types like Partial, Pick and Omit build new types from the ones you already have.

The problem generics solve

typescript
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 | undefined
any forgets the type; a generic keeps it
Think of it like this: A generic is like a steel tiffin box with a label slot. The same box carries idli one day and rajma chawal the next. You write on the label what is inside today, so whoever opens it knows exactly what they get. T is that label.

T 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

typescript
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
}
One response shape for every endpoint; T says what data holds

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

typescript
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 property
extends limits T to types that have what the function needs

keyof and typeof in types

typescript
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 settings
keyof gives the property names; typeof (in a type) copies the type of a value

Built-in utility types

UtilityWhat it makesTypical use
Partial<T>Every property optionalAn "edit profile" update
Required<T>Every property requiredSettings after defaults are filled in
Readonly<T>Every property read-onlyData that must not be changed
Pick<T, K>Only the properties named in KPick<Product, "id" | "name"> for a list view
Omit<T, K>All properties except KOmit<Product, "id"> for a "new product" form
Record<K, V>An object with keys K and values VRecord<PaymentStatus, number> for counts
ReturnType<F>The return type of a function typeReturnType<typeof makeOrder>
typescript
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 };
Build new types from one source of truth
Tip: Derive types instead of copying them. When Product gets a new field, every Omit, Pick and Partial built from it updates by itself.
Careful: Do not make everything generic. If a function only ever handles products, Product[] is clearer than T[]. Reach for a generic when the same logic really works for many types and you want to keep the exact type.

Watch the lesson

Generics and utility types · 1/7

function first<T>(list: T[]): T | undefined

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)

Task 1 · Code · Typescript

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.

Task 2 · Code · Typescript

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"] }.

Task 3 · Code · Typescript

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

Question 1

1.function first<T>(list: T[]): T | undefined. What is the type of first([10, 20])?

Question 2

2.type Product = { id: string; name: string; price: number }. What is Omit<Product, "id">?

Question 3

3.Which utility type makes every property optional, for an "edit profile" form where any field may change?

Question 4

4.In function findById<T extends { id: string }>(items: T[], id: string), what does T extends { id: string } mean?

Question 5

5.type Product = { id: string; price: number }. What is keyof Product?

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.

← Previous: Unions and narrowing