Generic without extends
1. What "generic without extends" means
When you declare a generic like this:
function identity<T>(value: T): T {
return value;
}you're telling TypeScript:
"
Tcan be absolutely any type."
And this is really true:
T can be a number, a string, an object, an array, a function, null, undefined, void, never, anything.
2. What's the problem?
The problem isn't the generic itself, it's the assumptions
the developer makes about type T.
Let's look at an example:
function getLength<T>(value: T): number {
return value.length; // Error: T might not have length!
}TypeScript complains because T can be anything, even number or boolean,
and then length doesn't exist.
Why this is unsafe
If TypeScript didn't check this, you could write code like this:
function unsafeLength<T>(value: T): number {
return (value as any).length;
}
console.log(unsafeLength(123)); // undefined
console.log(unsafeLength(true)); // undefinedThe code compiles, but the behavior is unpredictable, runtime errors are guaranteed. TypeScript no longer protects you.
3. How to fix it: add extends
You can constrain the generic so it only fits certain types:
function getLength<T extends { length: number }>(value: T): number {
return value.length; // safe
}Now T must be an object (or an array, a string, and so on)
that is guaranteed to have a length property.
It works:
getLength("hello"); // 5
getLength([1, 2, 3]); // 3It doesn't work:
getLength(42); // Error - number has no lengthThanks to
extends, TypeScript knows which properties can be used.
4. An example of an unsafe generic without extends
function merge<T, U>(a: T, b: U): T & U {
return { ...a, ...b };
}
merge(1, "test"); // Logically meaningless, but TS doesn't forbid itTypeScript doesn't know that
TandUare supposed to be objects, and it allows you to pass numbers, strings, and evennull.
A safe version:
function merge<T extends object, U extends object>(a: T, b: U): T & U {
return { ...a, ...b };
}
merge({ name: "Tim" }, { age: 25 }); // OK
merge(1, "test"); // Error - not an objectNow TypeScript strictly tracks the data structure.
5. Another example: with keys
Without extends, it's easy to "break" the type logic:
function getValue<T, K>(obj: T, key: K) {
return obj[key]; // Error: T might not have property K
}The correct way: constrain K:
function getValue<T, K extends keyof T>(obj: T, key: K) {
return obj[key]; // safe
}
getValue({ name: "Tim", age: 25 }, "name"); // OK
getValue({ name: "Tim" }, "invalid"); // Error - no such keyThanks to
extends keyof T, TypeScript knows thatkeymust be an existing property of the object.
6. Why this matters
If a generic is unconstrained (T without extends):
- you cannot safely use properties or methods of that type;
- TypeScript doesn't know which operations are allowed;
- there's a temptation to "plug" types with
as any, which makes the code unsafe.
7. Rule of thumb
| Situation | What to do |
|---|---|
| You use a generic but don't access properties | function identity<T>(x: T): T - fine |
You use properties of the type (.length, .id, etc.) | Add extends { length: number } |
| You use object keys | Add K extends keyof T |
| You expect the generic to be an object | Add extends object |
| You want to restrict to interface implementers | Add extends MyInterface |
Summary
Without extends | With extends |
|---|---|
T can be any type | T is constrained to a specific structure |
| The compiler doesn't know which properties exist | The compiler checks allowed properties |
| Runtime errors are possible | Errors are caught at compile time |
Often requires as any | Rarely requires as |
| Less predictable code | Safe and self-documenting code |
Put simply:
A generic without
extendsis a "universal container with no restrictions". Without restrictions, TypeScript cannot protect you.Add
extendswhen a generic must satisfy a specific contract (a structure, an interface, or keys).
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.