SyntaxStudy
Sign Up
TypeScript Generic Functions and Constraints
TypeScript Beginner 1 min read

Generic Functions and Constraints

Generic functions accept type parameters that are determined at the call site rather than being fixed in the function definition. They allow you to write functions that work with many types while still preserving type information. The type parameter is written in angle brackets before the parameter list and can be used anywhere in the function signature and body. Without constraints, a generic type parameter T could be anything, which limits what you can do with it. The extends keyword adds a constraint that tells TypeScript T must satisfy a particular shape. This lets you access properties of T that are guaranteed by the constraint. Constraints can be interfaces, type aliases, or even other generic types. TypeScript can often infer generic type parameters from the arguments you pass, so you rarely need to supply them explicitly. However, explicit type parameters are sometimes needed when the compiler cannot infer the correct type, or when you want to lock in a more specific type than what inference would produce.
Example
// Simple generic function — identity
function identity<T>(value: T): T {
    return value;
}
const n = identity(42);          // T inferred as number
const s = identity("hello");     // T inferred as string

// Generic array utility
function first<T>(arr: T[]): T | undefined {
    return arr[0];
}
const head = first([1, 2, 3]); // number | undefined

// Constraint with extends
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
    return obj[key];
}

const user = { id: 1, name: "Alice", email: "alice@example.com" };
const userName = getProperty(user, "name"); // string
// getProperty(user, "missing"); // Error: not a key of user

// Constraint requiring a specific shape
interface HasLength { length: number }

function logLength<T extends HasLength>(value: T): T {
    console.log(value.length);
    return value;
}
logLength("hello");    // 5
logLength([1, 2, 3]);  // 3

// Multiple type parameters
function zip<A, B>(a: A[], b: B[]): [A, B][] {
    return a.map((item, i) => [item, b[i]]);
}
const zipped = zip([1, 2, 3], ["a", "b", "c"]);
// [[1,"a"], [2,"b"], [3,"c"]]

// Generic with default type
type Container<T = string> = { value: T };
const strBox: Container = { value: "hi" };  // T defaults to string