# Generics — Part 1: The Fundamentals

## 1. The Problem Generics Solve

Without generics, writing a reusable function forces an unpleasant choice: either lock it to one specific type (not reusable), or use `any` (reusable, but loses all type safety).

```ts
function firstAny(arr: any[]): any {
  return arr[0];
}

const num = firstAny([1, 2, 3]);      // typed `any` — no safety, could be anything
const str = firstAny(["a", "b"]);      // also `any` — TypeScript forgot it was a string array
```

## 2. Generic Functions — Type Parameters

A **type parameter** (conventionally `T`) lets a function stay fully reusable while preserving the exact type relationship between its inputs and output.

```ts
function first<T>(arr: T[]): T {
  return arr[0];
}

const num = first([1, 2, 3]);     // T inferred as number → num: number
const str = first(["a", "b"]);     // T inferred as string → str: string
```

![first<T> called with a number array substitutes T=number; called with a string array substitutes T=string](diagrams/01-generic-instantiation.svg)

`T` is a placeholder decided **per call site** — `first([1,2,3])` and `first(["a","b"])` each independently substitute their own concrete type for `T`, without you writing it explicitly (thanks to inference, previewed in the Type Inference topic and covered in depth in Part 2 of this topic).

## 3. Multiple Type Parameters

```ts
function pair<A, B>(first: A, second: B): [A, B] {
  return [first, second];
}

const p = pair("age", 30); // [string, number], both inferred independently
```

## 4. Generic Interfaces and Type Aliases

```ts
interface Box<T> {
  value: T;
}

const numberBox: Box<number> = { value: 42 };

type Pair<A, B> = { first: A; second: B };
const p: Pair<string, number> = { first: "age", second: 30 };
```

## 5. Generic Classes

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

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

const numberStack = new Stack<number>();
numberStack.push(1);
// numberStack.push("a"); // ❌ error — this Stack instance is locked to T = number
```

Once instantiated with a concrete type (`Stack<number>`), every method on that instance is checked against that specific substitution — `push` only accepts `number` for that particular stack.

## 6. Explicit Type Arguments

```ts
function wrapInArray<T>(value: T): T[] {
  return [value];
}

const explicit = wrapInArray<string>("hello"); // explicitly specify T, instead of relying on inference
const inferred = wrapInArray("hello");           // same result — T inferred as string automatically
```

Explicit type arguments are needed when there's nothing for the checker to infer from (an empty array literal with no other context, for example) or when you deliberately want a wider type than inference alone would choose.

## 7. Default Type Parameters

```ts
interface ApiResponse<T = unknown> {
  data: T;
  status: number;
}

const response: ApiResponse = { data: "anything", status: 200 }; // T defaults to unknown
const typedResponse: ApiResponse<string> = { data: "hello", status: 200 }; // T explicitly string
```

A default type parameter behaves like a default function parameter — used only when no type argument is supplied and nothing can be inferred.

Continue to `ts_generics_theory_2.md` for how generic inference actually works and how instantiation is compiled.
