In Part 1, we saw how generics make your code more flexible and type-safe. We looked at how to define them, why they're better than using any, and how they fit into real world functions and components.
Now it's time to go deeper. In this part, we'll explore how to constrain generics using extends, provide default types, apply conditional logic with infer, and see these patterns show up in React components and API helpers.
By the end, we'll have a clearer grasp of how generics power real code not just as syntax, but as a tool for writing smarter, safer abstractions.
Can I restrict what T can be?
Sometimes T is too flexible, and you want to make sure it has certain properties, let's say length.
Use extends to constrain T.
This works with anything that has a length property:
But fails with types that don't:
You can constrain generics to:
- Specific shapes (e.g.
{ length: number }) - Interfaces or types
- Unions of types (e.g.
T extends string | string[])
This is especially useful in utility functions where you need flexibility without giving up control over the types.
Can types change based on conditions?
Yes. Conditional types let you choose one type or another depending on what T extends.
You'll often see this pattern used to filter parts of a union:
Here's what happens:
aextendsstring, so it's kept1does not, so it becomesneverbextendsstring, so it's kept
How do generics help with arrays?
Let's say you want a function that returns the first item of an array.
This works, but it throws away type info:
It's better with generics:
Now the return type stays in sync with the array contents:
TypeScript infers T from the array's type, no manual annotations needed.
This pattern is behind many array utility functions in libraries. It gives you type safety without extra effort.
Can I set default generic types?
You can provide a default type so callers don't always need to specify one.
It can be used like this:
The second call works without passing a type, T defaults to string.
Use default generics when:
- A specific type is common enough to assume
- You still want flexibility for other cases
What does infer do in generics?
Sometimes you want to extract a part of a type without knowing its name ahead of time. That's where infer comes in, it lets you "grab" a type from inside a structure.
Let's take an example:
Here, infer R tells TypeScript: "If T is a function, extract the return type as R."
You don't need to declare R elsewhere, infer introduces it right in place.
How do recursive generics work?
You can use generics inside themselves to model recursive structures, like arrays containing more arrays.
This tells TypeScript:
A NestedArray<number> is either a number, or an array of more NestedArray<number> values.
You can also model recursive objects like this:
Recursive generics are useful for:
- Deeply nested lists
- JSON structures with unknown depth
- Recursive shapes in APIs or schemas
Can I use generics in React components?
Yes and you should, especially when your component needs to be reusable but still type-safe.
Let's say you want a List component that works with any kind of item. You don't want to hardcode the type, you want the caller to decide.
Now use it like this:
No need to annotate the props manually, TypeScript infers T from the items array.
This keeps your component flexible across different data types while maintaining full type safety.
How do real projects use generics?
One common use of generics is in HTTP clients. Instead of hardcoding response types or casting manually, you can define a generic wrapper once and reuse it across your codebase.
Here's a simple API utility:
Now when you fetch something:
The response is strongly typed, no need for casting or any. You get autocomplete and type safety for free.
Wrap-up
Generics aren't just for writing flexible code, they help you write precise code. In this post, you saw how to constrain types, add defaults, use conditional logic, and extract deeply nested types with infer.
These tools show up everywhere: in components, utility functions, and API wrappers. Knowing how to use them well makes your code safer, clearer, and easier to scale.