function firstElement<T>(arr: T[]): T | undefined {
return arr[0];
}
firstElement([1, 2, 3]); // T inferred as number — return type: number | undefined
firstElement(["a", "b"]); // T inferred as string — return type: string | undefined
Without a generic, you'd either lose type safety (any[] in, any out — no compiler help at all) or write a near-duplicate function per type. <T> lets the function stay generic while TypeScript still tracks the specific type used at each call site and enforces it — firstElement([1, 2, 3])?.toUpperCase() correctly fails to compile, since T was inferred as number and number has no .toUpperCase().
Generics extend to interfaces, type aliases, and classes, not just functions:
interface Box<T> {
value: T;
}
const numberBox: Box<number> = { value: 42 };
const stringBox: Box<string> = { value: "hi" };
class Stack<T> {
private items: T[] = [];
push(item: T) { this.items.push(item); }
pop(): T | undefined { return this.items.pop(); }
}
const numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push("two"); // Error: Argument of type 'string' is not assignable to parameter of type 'number'
A generic function or type can take more than one type parameter, and later parameters can reference earlier ones:
function merge<T, U>(a: T, b: U): T & U {
return { ...a, ...b };
}
const merged = merge({ name: "Ada" }, { age: 30 }); // { name: string } & { age: number }