Skip to content

Generics

Generics allow creating reusable components that work with multiple types while maintaining type safety. They use type parameters (like <T>) to represent types that will be specified when the component is used.

Without generics, youโ€™d duplicate code or use any:

// Duplication
function numberIdentity(arg: number): number {
return arg;
}
function stringIdentity(arg: string): string {
return arg;
}
// Or lose type safety
function identity(arg: any): any {
return arg;
}

Generics provide reusability with type safety:

// Generic function
function identity<T>(arg: T): T {
return arg;
}
const num = identity<number>(42); // type: number
const str = identity<string>("hello"); // type: string
const auto = identity(true); // type inferred: boolean
// Generic interface
interface Box<T> {
value: T;
}
const numberBox: Box<number> = { value: 123 };
const stringBox: Box<string> = { value: "text" };
// Generic class
class DataStore<T> {
private data: T[] = [];
add(item: T): void {
this.data.push(item);
}
get(index: number): T | undefined {
return this.data[index];
}
}
const numberStore = new DataStore<number>();
numberStore.add(1);
numberStore.add(2);
// Multiple type parameters
function pair<T, U>(first: T, second: U): [T, U] {
return [first, second];
}
const result = pair<string, number>("age", 25);
// Generic constraints
interface Lengthwise {
length: number;
}
function logLength<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
logLength("hello"); // OK
logLength([1, 2, 3]); // OK
logLength({ length: 10 }); // OK
logLength(42); // Error: number has no length
  • Type safety: Maintain type information throughout
  • Reusability: One implementation works for many types
  • Autocomplete: IDE provides accurate suggestions
  • No duplication: Avoid repeating similar code
  • Flexibility: Constraints limit to specific type shapes
  • Type parameters are conventionally named T, U, V, etc.
  • Can use extends to constrain generic types
  • Can have default type parameters: <T = string>
  • TypeScript can often infer generic types
  • Can use keyof with generics for type-safe property access
// Default type parameter
interface Config<T = string> {
value: T;
}
const config1: Config = { value: "default" }; // T defaults to string
const config2: Config<number> = { value: 42 };
// keyof with generics
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { name: "Alice", age: 25 };
const name = getProperty(user, "name"); // type: string
const age = getProperty(user, "age"); // type: number
const invalid = getProperty(user, "invalid"); // Error!
// Generic array utilities
function first<T>(arr: T[]): T | undefined {
return arr[0];
}
function last<T>(arr: T[]): T | undefined {
return arr[arr.length - 1];
}
  1. Create a generic function wrapInArray that takes a value and returns it in an array

    Answer
    function wrapInArray<T>(value: T): T[] {
    return [value];
    }
  2. Create a generic interface Pair with two values of potentially different types

    Answer
    interface Pair<T, U> {
    first: T;
    second: U;
    }
  3. How would you constrain a generic type to objects with a name property?

    Answer
    function greet<T extends { name: string }>(obj: T): string {
    return `Hello, ${obj.name}`;
    }