TypeScript generics keep a type relationship intact
You write a function that returns the first item in an array. It works for names, then someone needs it for prices. Copying the function feels wasteful, but replacing every type with any throws away useful checks. Generics give you a third option: reusable code that remembers the types passing through it.
Think of a generic as a labeled placeholder. Instead of deciding that every input must be a string, you let each call supply its own type. The important part is the relationship: a string array produces a string result, while a number array produces a number result.
Your first generic function needs one placeholder
Start with an identity function, which returns exactly what it receives: function identity<T>(value: T): T { return value; }
The placeholder T appears after the function name, on the parameter, and on the return type. Those appearances connect the input and output. T is a conventional name, not a special keyword; a descriptive name such as Item also works.
Call identity(42) and TypeScript infers the type from the argument. You usually do not need to write the type argument yourself. An explicit call such as identity<string>("hello") is available when you want to specify it.
Using any would lose this connection. A return type of any lets callers perform operations without meaningful checking. A generic preserves information so the compiler can keep helping after the function returns.
An array example reveals the empty-input trap
Now try function first<T>(items: T[]): T | undefined { return items[0]; }
The input is an array of T, but the output includes undefined because the array might be empty. Generics do not magically prove that a value exists. They describe type relationships, while your function still needs an honest contract.
For first([10, 20]), the result is number | undefined. With strict null checking enabled, check for undefined before using number methods. This is a useful lesson for newcomers: a more accurate return type can be less convenient, yet prevent a real bug.
Generic constraints state what your function needs
A placeholder does not mean you can do anything with its value. If you try to access value.length on an unconstrained T, TypeScript objects because numbers, for example, have no length property.
Add a minimum requirement: function getLength<T extends { length: number }>(value: T): number { return value.length; }
Here, extends means the supplied type must satisfy that shape. Strings and arrays qualify. A plain number does not. However, this particular function could simply accept { length: number } without a generic, because its output does not depend on T. Use a generic when preserving a relationship actually helps.
Choose a model that explains and checks its edits
Which model copes best? There is no universal winner to establish from these examples alone. For this learning workflow, Claude Sonnet 5.5 is a reasonable model to try. Judge its usefulness by whether it explains the type relationship, catches missing cases, and avoids hiding errors with any or unsupported assertions.
Give it a narrow task: “Explain this generic function for a beginner. Show the inferred result types, test an empty array, and keep strict null checking enabled. Do not solve errors with any or type assertions.” Then run the result through your TypeScript compiler. A convincing explanation is not proof that code type-checks.
Run a small generics checklist through SwiftRelay
SwiftRelay is a practical way to try this model-assisted workflow with pay-as-you-go, per-token billing. It works with Claude Code and OpenAI-compatible SDKs. Claude Code uses the bare host https://api.fastapirelay.com; OpenAI-compatible SDKs use https://api.fastapirelay.com/v1 as their base URL.
Sign up for SwiftRelay to try your first generics exercise with the small gift credit available to new accounts.
- Identify which input and output types must stay connected.
- Let inference work before adding explicit type arguments.
- Represent empty or missing results honestly.
- Add constraints only for operations the implementation needs.
- Compile the model's suggestion and inspect unexpected errors.
Your first successful lap is not the cleverest generic. It is a small function whose types explain its behavior without concealing its edge cases.