Lesson 15 · Module 2.3 — Generics

Inference basics: how TypeScript solves T from your argument

You almost never write identity<string>("x"). You write identity("x") and TS fills T in for you. That filling-in is inference: TS reads the value you passed and solves for T. Reading generic code well means knowing which argument drove the solution.

The one idea

Inference is constraint-solving. Each place T appears in a signature is an equation; the argument you pass is the known value. TS reads the arguments, writes one equation per T, and solves for the single type that satisfies them.

The simple case: one argument, one equation

function identity<T>(arg: T): T { return arg; }
identity("abc"); // arg: T, you passed "abc"  ⇒  T = "abc"  ⇒  returns "abc"

One occurrence of T takes the value, so the equation is trivial: T is whatever you passed. The return type follows because it’s also T.

A subtle case: declaration order decides the winner

When T appears in several spots, TS still solves one T — and here’s a sharp example. An object implementing a Store<T> interface gives TS multiple methods that mention T; the first method in the interface acts as the primary constraint:

interface Store<T> {
  list(): T[];                  // ← FIRST method = primary constraint
  get(id: string): T;            // ← secondary
  save(id: string, item: T): void; // ← secondary
}

function addAndGetItems<T>(newItem: T, id: string, store: Store<T>): T[] {
  store.save(id, newItem);
  return store.list();
}

Now pass a store whose list() returns Homunculus[] while its other methods deal in Product:

const productStore = {
  list(): Homunculus[] { /* ... */ }, // ← this establishes T = Homunculus
  get(id: string): Product { /* ... */ },
  save(id: string, item: Product) { /* ... */ },
};

const newStore = addAndGetItems({ name: "Laptop", price: 999 }, "id", productStore);
// Because list() is first, TS solves T = Homunculus.
// Everything else (newItem, get, save) must now conform to Homunculus.

So does this compile? No — and that is the real point. The get/save lines are valid where they sit (productStore is a fine object on its own); the error appears at the addAndGetItems(…) call. Once T = Homunculus, the other pieces no longer fit: { name, price } is not a Homunculus, and get() returns a Product where a Homunculus is now required — so TS flags the arguments. One subtlety your eye should catch: a method’s return type must match, so get(): Product is the strict breaker, while parameter positions like save(item: Product) are checked more loosely. (Had Product been the same shape as Homunculus, it would all pass — the clash only bites because they differ.)

The takeaway: TS processes the interface’s methods in declaration order and treats the first match as primary. Reorder the interface so get() comes first, and a different method drives T. The list() return type won here simply because it was on top.

Reading trap

When a generic “infers the wrong type,” don’t assume the value is wrong. Check which occurrence of T the compiler read first — return-typed methods near the top of an interface quietly anchor the whole solve.

The mental model to carry forward

Inference = solve T from the arguments. Most of the time there’s one obvious source and the answer is boring. The moment T appears in many positions, “boring” ends — and the next lesson is about which position wins when they disagree.

Check yourself

Recall, don’t re-read. (Answers reveal on click.)

When you call identity("abc") without angle brackets, how is T chosen?
In the Store<T> example, what makes T = Homunculus rather than Product?
The best one-line model for generic inference is…

Primary source

The official page is TypeScript Handbook — Generics (working with type variables); inference is covered under Type Inference. Best way to feel it: paste the Store example into the TS Playground, then swap list() and get() in the interface and watch the inferred T change.

Try it yourself

Got a generic inferring a type you didn’t expect? Ask yourself “which occurrence of T won?” Next lesson (2.4) builds on this: inference priority — what happens when two positions disagree about T.