The previous lesson’s generic could work with any element type, but could not read domain-specific fields. A constraint limits accepted types to ones with a required capability. T extends { id: string } means T must include a string id.
This does not mean T is exactly that tiny shape. A Session with id, topic and minutes satisfies the constraint. The function can use id and still return the whole T, preserving the caller’s knowledge of topic and minutes. That is the advantage over returning only { id: string }.
Do not overconstrain. A lookup by id does not need to demand a topic or duration. Asking for only the fields the algorithm uses allows the same helper to work with books or sessions.
Constraints are checked at compile time. They do not inspect external values. A function accepting T with id:string is safe only when the calling code has established that contract. Day 6 will handle unknown inputs before they reach typed functions.
The extends keyword here describes a type constraint; it is not creating class inheritance. We are still using plain objects and ordinary array.find. Its callback returns true when a record matches, and find returns undefined when no record matches.
Predict, check, then run
Before running, write down the output and explain which information the checker knows. Click Check types first, then Check & run.
function byId<T extends { id: string }>(
items: readonly T[],
id: string
): T | undefined {
return items.find(item => item.id === id);
}
const sessions = [{ id: "s1", topic: "Generics", minutes: 20 }];
const found = byId(sessions, "s1");
console.log(found?.topic ?? "Missing");
console.log(byId(sessions, "no")?.topic ?? "Missing");Enable JavaScript to edit and type-check this example.
Check your prediction
Generics
Missing Read a real compiler error
The constraint describes the id type as well as its name.
function byId<T extends { id: string }>(
items: readonly T[],
id: string
): T | undefined {
return items.find(item => item.id === id);
}
console.log(byId([{ id: 1, title: "Types" }], "1"));Enable JavaScript to edit and type-check this example.
Check your diagnosis
Use a string id in the record if that is the domain contract. A numeric id is not automatically converted to string by the constraint.
A constraint is a minimum requirement
T extends {id:string} means the algorithm may rely on id. It does not discard the caller’s other fields. Returning T preserves those fields, which is the practical benefit over always returning the smaller id-only shape.
The constraint does not guarantee a matching id exists, that ids are unique, or that an id is nonblank. Those are separate domain and search rules. Test a missing match even when all records satisfy the structural requirement.
function byId<T extends { id: string }>(
values: readonly T[],
id: string
): T | undefined {
return values.find(value => value.id === id);
}
const found = byId([{ id: "a", title: "Types", pages: 120 }], "a");
console.log(found?.title ?? "Missing");
console.log(found?.pages ?? 0);Enable JavaScript to edit and type-check this example.
Check your prediction
Types
120 The search body uses only id, but the return retains the full inferred record. The optional access still acknowledges a possible missing result.
function byId<T extends { id: string }>(
values: readonly T[],
id: string
): T | undefined {
return values.find(value => value.id === id);
}
console.log(
byId(
[
{ id: "a", title: "First" },
{ id: "a", title: "Second" }
],
"a"
)?.title
);
console.log(byId([], "missing") === undefined);Enable JavaScript to edit and type-check this example.
Check your prediction
First
true Find returns the first matching record; the constraint did not enforce uniqueness. If duplicates are invalid, reject them at a validation boundary rather than silently assuming this helper proved that rule.
Try the rule on new inputs
Try richer records with a shared id field, a missing id field and a numeric id field. Explain which fail checking and which need runtime behavior tests despite satisfying the constraint.
Your turn
Generic Constraints: practise
Write titles<T extends { title: string }>(items: readonly T[]): string[]. Use map to extract titles. Test records with title and pages, printing Types,Loops with join(”,”).
// Require title without requiring the unrelated pages property.Enable JavaScript to edit and type-check this example.
Hint 1: the idea
The algorithm only reads title, so that is the constraint it needs.
Hint 2: a first step
Use extends with a record shape containing title:string.
Show one solution
function titles<T extends { title: string }>(items: readonly T[]): string[] {
return items.map(item => item.title);
}
const books = [
{ title: "Types", pages: 120 },
{ title: "Loops", pages: 80 }
];
console.log(titles(books).join(","));Enable JavaScript to edit and type-check this example.
Types,LoopsExplain the types at the function boundary and the runtime checks, if any. Compare your approach before copying this answer.
Change it and explain it
Explain why this particular titles function could also accept readonly { title: string }[] without a generic: its return does not preserve whole records. Contrast that with byId, which does preserve them. Try a missing title and read the diagnostic.
Completion is saved in this browser only. It does not measure mastery.