Kodokon kodokon.com

Overloads, typed this, and variance

Understand overload resolution, the this pseudo-parameter, and the variance rules that govern function assignability.

10 min · 3 questions

Open this lesson in Kodokon

An overloaded function exposes a list of public signatures followed by a single implementation signature. An essential specification point: the latter is invisible from the outside; only the declared overloads are callable. Resolution walks the signatures from top to bottom and keeps the first compatible one: always place the most specific signature first, otherwise it will never be selected.

TYPESCRIPT
function parse(input: string): number;
function parse(input: string[]): number[];
function parse(input: string | string[]) {
  return Array.isArray(input)
    ? input.map((s) => s.length)
    : input.length;
}

const single = parse("abc");      // number
const many = parse(["a", "bb"]);  // number[]
Each call resolves to a precise overload.

TypeScript lets you declare a this pseudo-parameter in first position. It is entirely erased from the emitted JavaScript: it is a purely static constraint on the call context, checked when noImplicitThis is enabled. The ThisParameterType and OmitThisParameter utilities let you extract or remove it, which is essential for typing functions meant to be used with call, apply, or bind.

TYPESCRIPT
function greet(this: { name: string }, msg: string) {
  return `${msg}, ${this.name}`;
}

type Ctx = ThisParameterType<typeof greet>;
// { name: string }
type Fn = OmitThisParameter<typeof greet>;
// (msg: string) => string

const user = { name: "Ada", greet };
user.greet("Hello"); // "Hello, Ada"

// greet("Hi"); // error: missing this context
The this parameter constrains the call site, not the emitted JS.

Variance describes how assignability propagates through a type constructor. For functions, return types are covariant and, with strictFunctionTypes, parameters are contravariant: a function that accepts a wider type can replace one that accepts a narrower type, never the other way around. One deliberate exception: the parameters of methods declared in shorthand syntax remain bivariant, a historical compromise without which Array<Dog> would not be assignable to Array<Animal>. Since 4.7, you can annotate the expected variance with in and out, for example interface Producer<out T>, and the compiler verifies it.

TYPESCRIPT
interface Animal { name: string }
interface Dog extends Animal { bark(): void }

declare let f: (a: Dog) => void;
declare let g: (a: Animal) => void;

f = g; // OK: g accepts a wider type, that is safe
// g = f; // error with strictFunctionTypes

interface Sink { accept(a: Animal): void }
interface DogSink { accept(a: Dog): void }
declare let s: Sink;
declare let d: DogSink;

d = s; s = d; // both compile: bivariance!
Strict function properties, bivariant methods.

Knowledge check

Make sure you remember the key points of this lesson.

  1. When calling an overloaded function, which signature does the compiler pick?
    • The most general signature
    • The first compatible signature in declaration order
    • The implementation signature
    • The union of all signatures
  2. What happens to a function's this parameter in the emitted JavaScript?
    • It becomes a real first argument
    • It disappears: it is a purely static annotation
    • It is replaced by a call to bind
    • It causes a runtime error if missing
  3. With strictFunctionTypes, which syntax benefits from strict contravariant parameter checking?
    • Methods in shorthand syntax, such as m(a: X): void
    • Function-typed properties, such as m: (a: X) => void
    • Both syntaxes
    • Neither of them