# Generics

> Source: https://learn-typescript.org/generics/
> Part of Learn TypeScript, free to read.

You have already used generics without noticing:

```typescript
const names: Array<string> = [];        // Array with a hole filled by string
const scores: Map<string, number> = new Map();
const later: Promise<User> = fetchUser();
```

`Array` on its own is incomplete — an array *of what?* The type parameter fills the hole. Writing your own works the same way.

## A generic function

The problem generics solve: without them, a reusable function loses type information.

```typescript
function first(items: any[]): any {
  return items[0];
}

const n = first([1, 2, 3]);      // n is any. we lost the number.
```

With a type parameter, the type flows through:

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

const n = first([1, 2, 3]);           // number
const s = first(["a", "b"]);          // string
const u = first<User>([]);            // User, stated explicitly
```

`<T>` declares the parameter; `T[]` and `: T` use it. You almost never pass it explicitly — TypeScript infers it from the argument.

`T` is only a convention. `<Item>` is just as valid and often clearer.

## Constraints

An unconstrained `T` could be anything, so you can barely touch it:

```typescript
function longest<T>(a: T, b: T): T {
  return a.length > b.length ? a : b;    // error: T has no 'length'
}
```

`extends` narrows what `T` may be:

```typescript
function longest<T extends { length: number }>(a: T, b: T): T {
  return a.length > b.length ? a : b;
}

longest("hello", "hi");           // string
longest([1, 2], [1, 2, 3]);       // number[]
longest(10, 20);                  // error — numbers have no length
```

Read `T extends X` as "T is at least an X". The return type is still the *specific* type passed in, which is the whole point — `longest("a", "b")` gives you a `string`, not a `{length: number}`.

## Constraining by key

`keyof` plus a constraint gives you type-safe property access:

```typescript
function pluck<T, K extends keyof T>(item: T, key: K): T[K] {
  return item[key];
}

const user = { id: "1", email: "a@b.com", age: 36 };

pluck(user, "email");     // string
pluck(user, "age");       // number
pluck(user, "nope");      // error: not a key of the object
```

`keyof T` is the union of `T`'s property names — here `"id" | "email" | "age"`. `T[K]` is the type of that property. This pattern turns up constantly in real code.

## Generic interfaces and classes

```typescript
interface Repository<T> {
  findById(id: string): Promise<T | null>;
  save(item: T): Promise<void>;
}

class InMemoryRepo<T extends { id: string }> implements Repository<T> {
  private items = new Map<string, T>();

  async findById(id: string): Promise<T | null> {
    return this.items.get(id) ?? null;
  }

  async save(item: T): Promise<void> {
    this.items.set(item.id, item);
  }
}

const users = new InMemoryRepo<User>();
const found = await users.findById("1");     // User | null
```

One implementation, fully typed for every entity you use it with.

## Defaults

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

const a: ApiResponse = { status: 200, data: "anything" };       // T is unknown
const b: ApiResponse<User> = { status: 200, data: ada };
```

Note the default is `unknown`, not `any` — keep the safe default even here.

## A practical example

```typescript
async function fetchJson<T>(url: string): Promise<T> {
  const res = await fetch(url);
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  return res.json() as T;
}

const user = await fetchJson<User>("/api/users/1");   // typed as User
```

:::danger This example contains a real bug worth understanding
`as T` is an **assertion**, not a check. Nothing verifies the response actually is a `User` — you have told the compiler to believe you about data you did not write.

The generic here is honest only if something validates. In production, parse with a schema:

```typescript
async function fetchJson<T>(url: string, schema: { parse(v: unknown): T }): Promise<T> {
  const res = await fetch(url);
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  return schema.parse(await res.json());     // actually checked
}
```
Generics move types around; they never validate. That distinction is where most unsafe TypeScript comes from.
:::

## When not to use a generic

The most common mistake is reaching for one where a plain type would do:

```typescript
function log<T>(message: T): void {           // pointless — T is never used
  console.log(message);
}
function log(message: string): void {}        // just say what you mean
```

**A type parameter earns its place only when it appears at least twice** — usually once in a parameter and once in the return type, so it can carry information between them. If it appears once, delete it.

Generated code over-reaches here regularly, producing elaborate generic signatures for functions that take one concrete type. Push back towards the simple version.

## Exercise

```typescript
// Write a generic function `groupBy` that:
//   - takes an array of T and a key K (a key of T whose value is a string)
//   - returns Record<string, T[]>
// Type it so that groupBy(users, "role") compiles and groupBy(users, "nope") does not.

// write your code here
```

## Common questions

### When should I add a type parameter?

When the same type needs to appear in more than one place in the signature — a parameter and the return, or two parameters that must match. If it appears only once, it is doing nothing and a concrete type is clearer.

### What does `extends` mean here — is it inheritance?

No. In a generic constraint it means "assignable to", so `T extends { length: number }` reads as "T must have at least a numeric length". It is a bound on what may be substituted, not a class relationship.

### Why is my inferred type wider than I wanted?

Usually because the argument was inferred loosely — a `string` rather than a literal. `as const` on the argument, or a `T extends string` constraint, keeps the narrow literal type instead of widening it.
