# Generics — Part 2: Inference, Instantiation, and Internals

## 1. How Generic Type Inference Actually Works

When you call `first([1, 2, 3])`, TypeScript doesn't guess `T` — it solves for it by comparing the **shape of the argument** against the **shape of the parameter type**, position by position, and reading off whatever concrete type lines up with `T`.

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

first([1, 2, 3]);
// parameter shape:  T[]
// argument shape:    number[]
// aligning them:      T = number
```

This process is called **unification** — finding a substitution for the type variable(s) that makes the parameter type and argument type match. It's the same general idea as inference algorithms in other statically-typed languages with generics (Java, C#, Rust), adapted to TypeScript's structural type system.

## 2. Inference from Multiple Arguments — Combining Evidence

```ts
function combine<T>(a: T, b: T): T[] {
  return [a, b];
}

combine(1, 2);          // T = number
combine(1, "two");       // T = number | string — the checker computes the BEST COMMON TYPE across both
```

When multiple arguments all constrain the same type parameter, TypeScript doesn't just use the first one — it collects evidence from every position and computes the best common type across all of them (same mechanism as the Type Inference topic's array best-common-type rule, applied here to a type parameter instead of an array's element type).

## 3. When Inference Fails: Needing Explicit Type Arguments

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

const arr = createEmpty(); // T has NOTHING to infer from — no argument mentions T at all
// arr's type ends up as `unknown[]` in strict settings, not a useful specific type

const arr2 = createEmpty<string>(); // must supply T explicitly here
```

Inference can only work when `T` appears somewhere in the function's actual parameters — a type parameter that only appears in the return type has no argument to unify against, forcing an explicit type argument.

## 4. Generic Constraints (Preview)

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

`extends` here restricts which types are valid for `T` — full depth is the Generic Constraints topic; the short version is that unconstrained `T` can be *anything*, which means the function body can only do things valid for literally every possible type (nothing, essentially) until a constraint narrows what's guaranteed to exist.

## 5. Generics Are Erased Too — No Runtime Type Parameters

```ts
function identity<T>(value: T): T {
  return value;
}
```

compiles to:

```js
function identity(value) {
  return value;
}
```

`T` never exists at runtime in any form — there's no way to inspect "what was T instantiated as" inside the function body at runtime, unlike languages with runtime generic reflection (C#'s `typeof(T)`). If you need runtime knowledge of the type, it must be passed as an ordinary runtime value (e.g. a constructor function or a string tag), not derived from the erased type parameter.

## 6. Under the Hood: Instantiation, Not Monomorphization

### TypeScript checks each instantiation, but does NOT generate separate compiled code per type

Unlike C++ templates (which generate a separate compiled function per concrete type used — "monomorphization"), TypeScript generics are checked once, generically, against the constraint (or `unknown` if unconstrained), and then erased to a single, ordinary JavaScript function. There is exactly one `identity` function in the compiled output, regardless of how many different types call it — the type-checking happens per call site, but the *code* is never duplicated.

```
TypeScript:  identity<T>(value: T): T   → ONE generic definition, checked structurally
Compiled JS:  function identity(value) { return value; }   → ONE function, used for every T
```

### The unification algorithm walks structurally, not just top-level

For more deeply nested generic positions, unification recurses through the structure:

```ts
function unwrap<T>(box: { value: T }): T { return box.value; }

unwrap({ value: 42 });
// parameter shape:  { value: T }
// argument shape:    { value: 42 }  →  { value: number }
// walking into the matching property: T = number
```

The checker doesn't just compare top-level types — it walks into corresponding structural positions (object properties, array elements, function parameter/return positions) recursively, collecting a substitution for every type parameter it encounters along the way, however deeply nested.

### Type parameters used only in contravariant (input) positions infer differently than covariant (output) positions

When a type parameter appears in multiple argument positions with genuinely different types, and those positions are function parameters (contravariant), the checker tends toward the **most specific common type**; when it appears in return/output (covariant) positions across multiple candidates, it tends toward a **union** (section 2's example). This asymmetry reflects real soundness concerns — inferring too broad a type for something you're about to call as a function is more likely to cause problems than inferring a broad type for something you're only ever going to read.

## 7. Common Pitfalls

1. **Expecting a type parameter to give runtime type information** — `T` is fully erased; nothing about it exists once compiled, per section 5.
2. **Writing an unconstrained `T` and then trying to use `T`-specific operations** — with no constraint, `T` could be anything, so the function body can't assume it has any properties or methods at all (see the Generic Constraints topic).
3. **Assuming generics generate separate compiled code per type**, the way C++ templates do — TypeScript compiles to exactly one function; all the "specialization" is purely a compile-time checking activity.
4. **Forgetting inference needs `T` in the parameters** — a type parameter that only appears in the return type can't be inferred from arguments; it needs either an explicit type argument or a contextual type at the call site.

## 8. Quick Reference

```
function f<T>(x: T): T {}          → T inferred by unifying parameter shape against argument shape
f<string>(x)                          → explicit type argument, when inference has nothing to work from
generics erased entirely at compile     → no runtime reflection of T, ever
one compiled function, not one per T     → checking is per-call-site, code is not duplicated
```
