# Generics Explained With Real Examples


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:

```typescript
// 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.

```typescript
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**

```typescript
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.

```typescript
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**

```typescript
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:

```typescript
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.
