Skip to main content

Command Palette

Search for a command to run...

Generics Explained With Real Examples

Most explanations of generics use <T> with no context, which is exactly why they never click. Here's what generics actually solve, with real code.

Updated
4 min readView as Markdown
Generics Explained With Real Examples
J
Jayesh Sojitra | AI & Frontend

Generics get introduced through abstract examples (function identity<T>(arg: T): T) that are technically correct but don't show why you'd reach for them. Here's the practical version — what problem they solve, and when you actually need them.

The problem generics solve: type safety without losing reusability

Without generics, you have two bad options for a function that should work with multiple types:

// Option 1: use `any` — you lose type safety entirely
function firstItem(arr: any[]): any {
  return arr[0];
}
const num = firstItem([1, 2, 3]); // TypeScript thinks this is `any`, not `number`

// Option 2: write a separate function per type — repetitive
function firstNumber(arr: number[]): number { return arr[0]; }
function firstString(arr: string[]): string { return arr[0]; }

Generics give you a third option: one function, full type safety, works with any type.

function firstItem<T>(arr: T[]): T {
  return arr[0];
}
const num = firstItem([1, 2, 3]); // TypeScript correctly infers: number
const str = firstItem(['a', 'b']); // TypeScript correctly infers: string

T is a placeholder for "whatever type gets passed in" — TypeScript figures out the actual type at each call site and enforces it consistently throughout the function.

A real example: a typed API response wrapper

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

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

// Usage — T gets specified at the call site
interface User { id: number; name: string; }
const response = await fetchData<User>('/api/user/1');
response.data.name; // TypeScript knows this is a string, autocomplete works

Without generics, ApiResponse would need a separate interface for every possible response shape, or you'd lose type safety on data entirely.

Generic constraints: limiting what T can be

Sometimes you want a generic, but not for literally any type — you need to guarantee it has certain properties.

interface HasId { id: number; }

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

T extends HasId means: T can be any type, as long as it has an id: number property. This lets the function safely access .id on any item, while still working with any object shape that includes that property.

A real example: a reusable custom hook with generics

function useLocalStorage<T>(key: string, initialValue: T): [T, (value: T) => void] {
  const [value, setValue] = useState<T>(() => {
    const stored = localStorage.getItem(key);
    return stored ? JSON.parse(stored) : initialValue;
  });
  const setStoredValue = (newValue: T) => {
    setValue(newValue);
    localStorage.setItem(key, JSON.stringify(newValue));
  };
  return [value, setStoredValue];
}

// Fully typed at every call site:
const [theme, setTheme] = useLocalStorage<'light' | 'dark'>('theme', 'light');
const [user, setUser] = useLocalStorage<User | null>('user', null);

Remember Day 2's useLocalStorage hook — this is the same hook, made properly type-safe. Without generics, you'd either lose typing on the returned value or need a separate hook per type stored.

Multiple type parameters

Generics aren't limited to one type parameter:

function mapObject<T, U>(obj: T, fn: (value: T) => U): U {
  return fn(obj);
}

Useful when a function transforms one type into a genuinely different one, and you want both the input and output types tracked correctly.

When you actually need generics (and when you don't)

  • Need them: reusable utility functions, custom hooks, API wrappers, any code meant to work with multiple types while staying type-safe.
  • Don't need them: a function that only ever works with one specific type — adding <T> there is unnecessary complexity for no benefit.

Try this yourself: find a utility function in your codebase that currently uses any, or that you've duplicated for different types. Rewrite it with a generic type parameter, and notice what autocomplete and type-checking you get back that you didn't have before.

Takeaway: Generics aren't an advanced feature to avoid — they're what lets you write one reusable function or hook instead of duplicating it per type, without falling back to any and losing type safety. The moment you're tempted to copy-paste a function just to change its type signature, that's the generics signal.

30 Days of AI

Part 2 of 46

A 30-day series breaking down AI concepts, tools, and prompts in plain, jargon-free language — for beginners and professionals who want to actually understand and use AI, not just talk about it.

Up next

Week 2 Recap: Architecture + Tooling

Week 2 is done. Seven posts, all built around the same theme — most technical debates aren't about right vs. wrong, they're about matching the tool to the actual problem.