Expert · TypeScript basics · topic 45 of 61 · 30 min
Generics
Explanation
The problem
Write a function that returns the first item of an array. With any, you lose the type: first([1, 2]) returns any, so first([1, 2]).toUpperCase() is not caught. Writing firstNumber, firstString, firstUser… is silly. Generics let you write it once, with the type as a parameter.
function first<T>(list: T[]): T | undefined {
return list[0];
}
const n = first([10, 20]); // n: number | undefined
const s = first(['tea', 'coffee']); // s: string | undefined
s?.toUpperCase(); // OK
n?.toUpperCase(); // Error: Property 'toUpperCase' does not exist on type 'number'.Generic types
interface ApiResponse<T> {
ok: boolean;
data: T;
error?: string;
}
type User = { id: number; name: string };
const res: ApiResponse<User[]> = { ok: true, data: [{ id: 1, name: 'Asha' }] };
class Box<T> {
constructor(private value: T) {}
get(): T { return this.value; }
}Constraints: T extends …
Sometimes T must have certain properties. A constraint says "T can be anything, as long as it has these". Inside the function you can then use those properties.
function byId<T extends { id: number }>(items: T[], id: number): T | undefined {
return items.find((item) => item.id === id);
}
const u = byId([{ id: 1, name: 'Asha' }], 1); // u keeps its full type
byId(['a', 'b'], 1); // Error: string does not have an idkeyof and indexed access
keyof T is the union of T's property names. T[K] is the type of property K. Together they let you write a fully typed "get a property" helper.
function pluck<T, K extends keyof T>(items: T[], key: K): T[K][] {
return items.map((item) => item[key]);
}
const products = [{ name: 'Fan', price: 2499 }, { name: 'Bulb', price: 99 }];
const prices = pluck(products, 'price'); // number[]
pluck(products, 'colour'); // Error: '"colour"' is not assignable to '"name" | "price"'Default type parameters
interface Page<T = string> { items: T[]; next?: string }
const tags: Page = { items: ['veg', 'spicy'] }; // T defaults to stringNeed help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
Generics · 1/6
In this lesson you will write generics, functions and types that work with many types safely.
Prefer a video by a teacher? Find free videos on “typescript generics explained constraints keyof” on YouTube ↗
In real life: A typed API client
A logistics company's front end calls 60 endpoints. One generic helper getJson<T>(url) returns Promise<T>, so each call site states what it expects: getJson<Shipment[]>('/shipments'). The editor then autocompletes shipment fields everywhere and flags typos.
async function getJson<T>(url: string, fetchFn: typeof fetch = fetch): Promise<T> {
const res = await fetchFn(url);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return (await res.json()) as T; // still validate important data at run time
}
interface Shipment { id: string; city: string }
// const list = await getJson<Shipment[]>('/api/shipments'); list[0].city is a stringPractice (0/3 done)
Write a generic last<T>(list: T[]): T | undefined that returns the last item.
Write byId<T extends { id: number }>(items: T[], id: number): T | undefined.
Write groupBy<T, K extends string>(items: T[], keyFn: (item: T) => K): Record<K, T[]> that groups items by the key returned from keyFn, keeping input order.
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- Utility types and real-world TypeScriptTypeScript basics · same module
- Basic types and inferenceTypeScript basics · same module
- Generics and utility typesFrontend Web Development · TypeScript basics
- Generics: type parameters and constraintsGo Programming (backend and cloud) · Packages, testing and generics
- Generics: reusable, type-safe codeC# and .NET · Collections, generics and LINQ
- GenericsJava Programming (Core to Spring Boot) · Exceptions and generics
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
function id<T>(x: T): T { return x; } What is the type of id("hi")?
Answer: string
T is inferred as string from the argument.
What does T extends { length: number } allow?
Answer: Any type that has a numeric length property
Strings, arrays and objects with length all qualify.
type P = { name: string; price: number }. What is keyof P?
Answer: 'name' | 'price'
keyof gives a union of the property names.
Which built-in type is generic?
Answer: Promise<T>
Promise<T> carries the type of its eventual value.
type Product = { id: string; price: number }. What is keyof Product?Related
Which constraint lets you use a type as a map key?Related
Answer: comparable
Map keys need ==, which comparable guarantees.
Which is a generic type in .NET?Related
Answer: Dictionary<TKey,TValue>
Dictionary has two type parameters.
What do generics mainly give you?Related
Answer: Type safety at compile time without casts
Errors move from run time to compile time.
From Generics
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- 1 more topic in “TypeScript basics” before its exam: Utility types and real-world TypeScript.
- Module 9 exam: TypeScript basics · 12 questions · 25 min, without help. 5 topics of this module still to finish first.
- Then the next module, Functional patterns and testing. 61 topics left in the course before the final exam.