Constraining Generics: Improving Type Safety in TypeScript
Learn how to use the 'extends' keyword to constrain generics in TypeScript. Improve your type safety and catch errors early in your reusable components.

Previously in this course, we explored Introduction to Generics: Writing Reusable TypeScript Code and Generics with Interfaces: Building Flexible TypeScript Models. While generics allow us to write highly reusable code, they can sometimes be too permissive. By default, a generic type T can be literally anything, which might lead to errors if you expect that type to have specific properties.
In this lesson, we will learn how to apply Constraints to generics using the extends keyword. This allows us to limit the types that can be passed to our generic functions, ensuring that our reusable components only operate on the data we explicitly allow.
Understanding Generic Constraints
When you define a generic function, you are essentially telling TypeScript: "I don't know the type yet, but I'll figure it out when the function is called." Sometimes, however, you need to guarantee that the type passed in contains specific properties—for example, an id field or a length property.
If you don't constrain the generic, TypeScript will throw an error when you try to access those properties, because it cannot prove that T has them. The extends keyword acts as a filter, restricting the input type to a specific subset of types.
Worked Example: The ID Logger
Imagine we are building our Task API client. We want a utility function that logs the ID of any object that possesses one. Without constraints, this code fails:
TYPESCRIPT// This causes a compiler error! function logId<T>(item: T) { console.log(item.id); // Property CE9178">'id' does not exist on type CE9178">'T'. }
To fix this, we define an interface and constrain our generic T to extend it:
TYPESCRIPTinterface HasId { id: number | string; } // We constrain T to at least have an CE9178">'id' property function logId<T extends HasId>(item: T) { console.log(item.id); } // Works fine logId({ id: 1, task: "Finish lesson" }); // Compiler error: Argument is not assignable to parameter of type CE9178">'HasId' logId({ task: "Incomplete task" });
By adding extends HasId, we have enforced Type Safety. TypeScript now knows for a fact that any item passed to logId will have an id property.
Improving Reusability with Constraints

Constraints aren't just for preventing errors; they are for defining the "contract" of your component. When building complex systems, you often want to say: "I accept any data, as long as it looks like a Task."
| Feature | Unconstrained Generic (<T>) | Constrained Generic (<T extends U>) |
|---|---|---|
| Flexibility | Extremely high | Medium (restricted to U) |
| Safety | Low (accessing properties is unsafe) | High (guaranteed interface shape) |
| Best Use | Identity functions, simple wrappers | API clients, data processing utilities |
In our ongoing Task API project, this pattern is essential. When we eventually implement our fetch wrappers, we will constrain our generics to ensure that the data returned from the server matches the shapes we expect, similar to how we handled types in Any vs Unknown: Mastering TypeScript Type Safety.
Hands-on Exercise

Create a function called printTaskTitle that accepts a generic object. Constrain this object so that it must have a title property of type string.
- Define an interface
TaskLikewith atitleproperty. - Write the generic function
printTaskTitle<T extends TaskLike>(item: T). - Call the function with an object that has both a
titleand astatus. - Verify that calling the function with an object missing the
titlecauses a compilation error.
Common Pitfalls

- Over-constraining: Don't make your constraints too narrow. If you make a constraint that is almost impossible to satisfy, you lose the benefit of the generic. Keep your base interfaces lean.
- Assuming properties exist: Beginners often try to access properties on
Twithout a constraint. Remember: if the compiler can't see the property in the generic definition, it doesn't exist for the compiler. - Confusing
extendswith inheritance: In the context of generics,extendsmeans "is a subtype of" or "must have at least these properties," not necessarily that the type must inherit from a class.
By mastering constraints, you move from writing "any-like" generic code to building robust, predictable APIs. This is a critical step before we move into advanced manipulation with Fixing "Type X is not assignable to type Y" with TypeScript keyof.
Up next: We will explore how to use keyof for dynamic access, allowing us to safely look up properties in our task objects.
Work with me

Next.js Full-Stack Web App Development
A fast, SEO-ready full-stack web app built with Next.js 16 — from idea to deployed product, by an engineer who ships to production.

React & Next.js Dashboard / Admin UI Development
A clean, data-rich dashboard UI in React or Next.js — charts, tables, and real-time data that your users will actually enjoy using.

