TypeScript Generics for Everyday Code

Frontend2026-10-05TryQuickToolBox

You're writing a function that returns the first element of an array. You want it to work for numbers, strings, and objects, but you also want the return type to match the input type. Without generics, you'd resort to any and lose all type safety. That's where TypeScript generics come in.

Generics let you write code that works with multiple types while preserving the specific type information. In this guide, we'll explore practical patterns for using generics in everyday TypeScript code.

What Are Generics?

Generics are a way to parameterize types. Just like functions take parameters, generics take type parameters. They allow you to create reusable components that work over a variety of types rather than a single one.

Consider this identity function:

function identity<T>(arg: T): T {
  return arg;
}

Here, T is a type variable. When you call identity(42), TypeScript infers T as number. When you call identity("hello"), T becomes string. The return type always matches the input type.

Generic Functions in Practice

Let's look at a common scenario: a function that wraps a value in an object.

function wrapInObject<T>(value: T): { value: T } {
  return { value };
}

Without generics, you'd have to use any or unknown, and callers would lose type information. With generics, the return type is precisely { value: T }.

Another everyday example is a function that returns the last element of an array:

function last<T>(arr: T[]): T | undefined {
  return arr[arr.length - 1];
}

TypeScript infers T from the array argument, so last([1, 2, 3]) returns number | undefined.

Generic Constraints

Sometimes you want to restrict the types that can be used with your generic. For example, you might need to access a property that only exists on certain types. You can use the extends keyword to add a constraint.

function getLength<T extends { length: number }>(item: T): number {
  return item.length;
}

Now T must have a length property of type number. This works for strings, arrays, and any object with a length property.

You can also use constraints with interfaces:

interface HasId {
  id: string;
}

function findById<T extends HasId>(items: T[], id: string): T | undefined {
  return items.find(item => item.id === id);
}

This ensures that the items passed to findById have an id property.

Generic Interfaces and Type Aliases

Generics aren't limited to functions. You can define generic interfaces and type aliases to create flexible data structures.

interface ApiResponse<T> {
  data: T;
  status: number;
  error?: string;
}

async function fetchData<T>(url: string): Promise<ApiResponse<T>> {
  const response = await fetch(url);
  const data = await response.json();
  return { data, status: response.status };
}

Now you can type API responses precisely:

interface User {
  id: number;
  name: string;
}

const userResponse = await fetchData<User>('/api/user');
// userResponse.data is User

Generic Classes

Classes can also be generic. A common example is a simple stack:

class Stack<T> {
  private items: T[] = [];

  push(item: T): void {
    this.items.push(item);
  }

  pop(): T | undefined {
    return this.items.pop();
  }

  peek(): T | undefined {
    return this.items[this.items.length - 1];
  }
}

const numberStack = new Stack<number>();
numberStack.push(1);
const num = numberStack.pop(); // number | undefined

Generics in React Components

If you're building React components, generics can make your components more reusable. For example, a list component that renders items of any type:

interface ListProps<T> {
  items: T[];
  renderItem: (item: T) => React.ReactNode;
}

function List<T>({ items, renderItem }: ListProps<T>) {
  return <ul>{items.map((item, index) => <li key={index}>{renderItem(item)}</li>)}</ul>;
}

Now you can use List with any type and get full type checking on the renderItem callback.

Common Pitfalls and Best Practices

Comparison: Generics vs. any vs. Union Types

Approach Type Safety Flexibility Use Case
any None High Migrating JS, quick prototypes
Union types Good Limited to known types When you know all possible types
Generics Strong High Reusable components with preserved types

FAQ

When should I use generics instead of any?

Use generics whenever you want to preserve type information across a function or component. any disables type checking, while generics keep it. If you find yourself reaching for any to make something work, consider if a generic could solve it more safely.

Can I use multiple type parameters?

Yes. You can define functions with multiple type parameters, like function pair<K, V>(key: K, value: V): [K, V]. Use descriptive names when there are more than one.

How do generics work with arrow functions in .tsx files?

In .tsx files, <T> can be ambiguous with JSX. Use a trailing comma: const identity = <T,>(arg: T): T => arg; or use extends unknown: <T extends unknown>.

Generics are a powerful feature that can make your TypeScript code more reusable and type-safe. Start with simple generic functions, then gradually apply them to interfaces and classes. If you need to format or validate JSON data with type safety, check out our JSON Formatter tool to quickly inspect and pretty-print your JSON.