REUSABLE TYPES · LESSON 9 OF 24
Generics and constraints
Preserve relationships between input and output types.
- type parameters
- constraints
- inference
- readonly
Your runnable checkpoint
Extract into a new practice folder. Each download includes complete source, package.json, tsconfig.json and an offline lesson. Use Node.js 22 or 24. Each checkpoint is independent.
Install with npm install. Run npm run check, then npm run build. Terminal lessons use npm start; browser lessons use npm run serve and http://127.0.0.1:4173.
1. Understand the concepts
A generic function describes a relationship that works for many types. first<T> returns the element type of the array it receives. Returning unknown would lose that relationship; returning any would disable checking.
T is chosen for each call, usually through inference. It is not a runtime class and cannot be used to construct a value or test instanceof T. Types disappear, so runtime behavior still needs actual values or callbacks.
A constraint limits what T may be. T extends { id: number } permits reading id while preserving additional fields. It does not say T is exactly that minimal shape.
Keep generic parameters useful. A parameter appearing only once may not express a relationship and can often be replaced by a simpler type. Return undefined explicitly when an empty input is possible.
2. Build and run
These are complete files. Preserve the paths shown below, including the src directory. The package and compiler configuration are included at the end.
src/main.ts
function first<T>(values: readonly T[]): T | undefined {
return values[0];
}
function findById<T extends { id: number }>(values: readonly T[], id: number): T | undefined {
return values.find(value => value.id === id);
}
const students = [{ id: 1, name: "Asha" }, { id: 2, name: "Ravi" }];
console.log(first([10, 20]));
console.log(findById(students, 2)?.name);
console.log(first<string>([]) ?? "Empty");
package.json
{
"name": "edupoly-typescript-generics-and-constraints",
"private": true,
"version": "1.0.0",
"type": "module",
"scripts": {
"check": "tsc --noEmit",
"build": "tsc",
"start": "node dist/main.js"
},
"devDependencies": {
"typescript": "5.9.3"
}
}
tsconfig.json
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"lib": [
"ES2022",
"DOM"
],
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noEmitOnError": true,
"verbatimModuleSyntax": true,
"rootDir": "src",
"outDir": "dist",
"sourceMap": true,
"types": [],
"moduleDetection": "force"
},
"include": [
"src/**/*"
]
}
3. Trace the code and check the result
- first infers number from the first call.
- findById keeps the name property in its inferred return type.
- The explicit string argument is useful when the input array is empty.
Expected result
10
Ravi
Empty4. Practice independently
Write last<T> with the same empty-array behavior.
Show the explanation
Return values[values.length - 1] from function last<T>(values: readonly T[]): T | undefined. Do not assert that an item exists.
When something goes wrong
If tsc is missing, run npm install in the folder containing package.json. If the output looks old, rebuild before running. Read the first compiler diagnostic and check the indicated field or argument. For module errors, preserve the .js extensions in imports. Browser examples must run through the included local server, not directly as TypeScript files.
