| |

TypeScript 64 🔷 Typing Callbacks and Higher-Order Functions

A callback is a function passed to another function to be called later. A higher-order function is a function that takes a function as an argument or returns one. The two are the same idea from different angles: the callback is the function that is passed, and the higher-order function is the function that accepts it. This pattern is everywhere — Array.prototype.map, setTimeout, the event listeners, the middleware, the reducer functions, the promise handlers. TypeScript’s type system is expressive enough to type the callback’s parameters, its return type, the this binding, the overloads, and the generic relationships between the callback and the function’s inputs and outputs. The result is that the callback’s parameters are inferred from the context, the return type is checked, and the mistakes are caught at compile time. This chapter covers the typing of callbacks and higher-order functions: the function type syntax, the parameter inference, the this typing, the overloads, the generic callbacks, the variance of the parameters, and the patterns that make the callbacks type-safe.

Key point: A function type is written with the parameter list and the return type: (a: number, b: number) => number. A higher-order function declares the callback as a parameter of a function type, and the callback’s parameters are inferred from the context when the function is called. The this parameter is typed with the special this parameter, which is erased at runtime. The overloads declare the multiple signatures for the different callback shapes. The generic callbacks declare the type parameters that the callback and the function share. The callback’s parameter types are contravariant, which means the callback with the wider parameter is assignable to the callback with the narrower one — the safety that prevents the callback from receiving the value it does not expect.


The function type syntax

A function type describes the shape of a function: its parameter list and its return type. It is written with the arrow syntax.

type Adder = (a: number, b: number) => number;

const add: Adder = (a, b) => a + b;

The Adder type is a function that takes two numbers and returns a number. The add function is assignable to it, and the parameter types are inferred from the Adder type.

Why the parameter names in the type are optional. The type’s parameter names are the documentation. The types are what matter, and the names can be omitted.

type Adder = (number, number) => number;  // ❌ the names are required if the types are present
type Adder = (a: number, b: number) => number;  // ✅

The names can be omitted when the types are omitted, but the two together are the form.

Why the return type is required. The => number is the return type, and it is required for the function type. The => void is the return type for the function that returns nothing.

Why the => is the arrow. The arrow is the function type’s syntax, and it is the same as the arrow function’s syntax. The two are the same, and the type is the shape.

Why the function type can be a type alias. The type Adder = ... is the type alias, and the interface can also declare a function type.

interface Adder {
  (a: number, b: number): number;
}

The two are the same, and the type alias is more common for the simple function types.

Why the function type can be a parameter. The higher-order function declares the callback as a parameter of the function type.

function apply(a: number, b: number, fn: (x: number, y: number) => number): number {
  return fn(a, b);
}

apply(1, 2, (a, b) => a + b);  // a, b inferred as number

The fn parameter is the function type, and the callback’s parameters are inferred from the type.

Why the inline function type is the common form. The inline type is written at the parameter, and the type alias is used when the type is reused. The two are the choice, and the inline is the convenience.


The parameter inference

When a callback is passed to a higher-order function, the callback’s parameters are inferred from the function’s type. The inference is what makes the callback’s parameters typed without the explicit annotations.

const numbers = [1, 2, 3];
numbers.map((n) => n * 2);  // n inferred as number

The map‘s callback parameter is typed as (value: number, index: number, array: number[]) => U, and the n is inferred as the number. The index and the array are also available.

Why the inference is the ergonomics. The callback’s parameters are inferred from the context, and the developer does not have to annotate them. The inference is the TypeScript’s strength, and the callbacks are the common case.

Why the inference can fail. The inference fails when the callback is passed to a function whose type is not declared or is declared as any. The any disables the inference, and the callback’s parameters are any.

declare function unsafe(fn: any): void;
unsafe((x) => x.toFixed());  // x is any, no checking

The any is the escape, and the inference is lost. The fix is to declare the function type.

Why the callback’s return type is checked. The callback’s return type is checked against the function type’s return type. A mismatch is a compile error.

numbers.map((n) => n.toString());  // string[]
numbers.map((n) => n.toFixed());   // string[]
// numbers.map((n) => { console.log(n); });  // void[], the return is void

The callback’s return type is the map‘s U, and the result is the U[]. The void callback produces the void[], which is the type.

Why the callback’s parameters can be annotated. The callback’s parameters can be annotated explicitly, which is the documentation and the safety.

numbers.map((n: number) => n * 2);

The explicit annotation is the same type, and the two are the choice. The explicit is the safety when the inference is uncertain.

Why the callback’s parameter count can be less. The callback can declare fewer parameters than the function type, and the extra parameters are ignored.

numbers.map((n) => n * 2);  // the index and the array are ignored
numbers.map((n, i) => n + i);  // the index is used

The callback with fewer parameters is assignable to the function type with more, and the extra parameters are the optional. The pattern is the common use.

Why the callback’s parameter count cannot be more. The callback cannot declare more parameters than the function type, because the function type does not provide the extra values.

// numbers.map((n, i, arr, extra) => ...);  // ❌ error: the extra is not provided

The extra parameter is the error, and the count is the compatibility.


The this typing

The this parameter is the special parameter that types the this binding of the function. It is the first parameter, and it is erased at runtime.

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

const obj = { name: 'Alice', greet };
obj.greet('Hello');  // ✅
// greet('Hello');  // ❌ error: the this context is wrong

The this: { name: string } declares the this type, and the greet can only be called with the this object that has the name. The this parameter is the type-level check.

Why the this parameter is the first. The this is the receiver, and the parameter is the way to type it. The runtime passes the this implicitly, and the type declares it explicitly.

Why the this parameter is erased. The this parameter is the TypeScript-only feature, and it is removed from the emitted JavaScript. The runtime’s this is the same, and the type is the compile-time check.

Why the this parameter is used in the callbacks. The callback can declare the this type, and the caller’s this is checked.

function bindTo(element: HTMLElement, handler: (this: HTMLElement, e: Event) => void): void {
  element.addEventListener('click', handler);
}

The handler‘s this is the HTMLElement, and the caller’s function is checked against it.

Why the this parameter is the safety. The this is the common source of the runtime errors — the this is undefined in the strict mode when the function is called without the receiver. The this parameter catches the mistake at the compile time.

Why the arrow function does not have the this parameter. The arrow function captures the this from the enclosing scope, and it does not have its own this. The this parameter is the regular function’s feature, and the arrow function’s this is the lexical.

const obj = {
  name: 'Alice',
  greet: () => this.name,  // the this is the enclosing scope, not obj
};

The arrow function’s this is the lexical, and the this parameter is the regular function’s. The two are the choice, and the arrow is the common for the callbacks.


The overloads

An overloaded function has multiple signatures, and the implementation signature is the union. The overloads are the API, and the implementation is the runtime.

function format(value: string): string;
function format(value: number): string;
function format(value: boolean): string;
function format(value: string | number | boolean): string {
  return String(value);
}

The three overloads declare the three accepted types, and the implementation handles the union. The caller sees the three signatures, and the implementation is the hidden.

Why the overloads are the API. The overloads are the public signatures, and the implementation is the detail. The caller’s call is checked against the overloads, and the implementation is not visible.

Why the implementation signature is not visible. The implementation is the union, and it is not part of the overloads. The caller cannot see it, and the implementation’s signature is the internal.

Why the overloads are used for the callbacks. The function that accepts the callback can have the overloads for the different callback shapes.

function on(event: 'click', handler: (e: MouseEvent) => void): void;
function on(event: 'keydown', handler: (e: KeyboardEvent) => void): void;
function on(event: string, handler: (e: Event) => void): void {
  // implementation
}

on('click', (e) => e.clientX);     // e is MouseEvent
on('keydown', (e) => e.key);       // e is KeyboardEvent

The click overload’s handler is the MouseEvent, and the keydown overload’s handler is the KeyboardEvent. The overloads are the API, and the callback’s parameter is inferred from the matching overload.

Why the overloads must be ordered. The overloads are checked in the order they appear, and the first match wins. The more specific overloads should come first.

function on(event: 'click', handler: (e: MouseEvent) => void): void;
function on(event: string, handler: (e: Event) => void): void;

The 'click' is more specific than the string, and the 'click' overload is the first. The reversed order would match the string first, and the 'click' would never be reached.

Why the overloads can be replaced by the conditional types. The modern TypeScript uses the conditional types to express the overloads in a single signature.

type Handler<E extends string> = E extends 'click'
  ? (e: MouseEvent) => void
  : E extends 'keydown'
    ? (e: KeyboardEvent) => void
    : (e: Event) => void;

function on<E extends string>(event: E, handler: Handler<E>): void {
  // implementation
}

The conditional type is the modern alternative, and it is the single signature. The two are the choice, and the overloads are the common.

Why the overloads are the classic and the conditional types are the modern. The overloads are the older feature, and they are the widely supported. The conditional types are the newer feature, and they are the more expressive. The code can use either, and the choice is the style.


The generic callbacks

A generic callback declares the type parameters that the callback and the function share. The callback’s parameter types are the type parameters, and the function’s return type can be the same or the derived.

function map<T, U>(array: T[], fn: (value: T, index: number) => U): U[] {
  const result: U[] = [];
  for (let i = 0; i < array.length; i++) {
    result.push(fn(array[i], i));
  }
  return result;
}

const lengths = map(['a', 'bb', 'ccc'], (s) => s.length);
// lengths is number[]

The map‘s T is the array’s element type, and the U is the callback’s return type. The s is inferred as the string, and the lengths is the number[].

Why the generic is the shared type. The T is shared between the array’s element and the callback’s parameter, and the U is the callback’s return. The two are the generic, and the inference is the flow.

Why the generic callback can be constrained. The generic can be constrained to the types that have the property.

function pluck<T, K extends keyof T>(array: T[], key: K): T[K][] {
  return array.map((item) => item[key]);
}

const names = pluck([{ name: 'Alice' }, { name: 'Bob' }], 'name');
// names is string[]

The K extends keyof T constrains the key to the object’s properties, and the T[K] is the property’s type. The callback’s return is the property’s type.

Why the constrained generic is the safety. The constraint prevents the key that does not exist, and the T[K] is the property’s type. The two are the safety, and the generic is the flow.

Why the generic callback can have the default. The generic can have the default, which is used when the type is not inferred.

function identity<T = unknown>(value: T): T {
  return value;
}

The T = unknown is the default, and the identity(1) infers the number. The default is the fallback.

Why the generic callback can be the higher-order. The function can return the callback, and the return type is the function type.

function multiplyBy(factor: number): (value: number) => number {
  return (value) => value * factor;
}

const double = multiplyBy(2);
const result = double(5);  // 10

The multiplyBy returns the callback, and the double is the function. The higher-order is the returning, and the callback is the returned.

Why the generic callback is the composition. The function can take the callback and return the callback, which is the composition.

function compose<A, B, C>(f: (a: A) => B, g: (b: B) => C): (a: A) => C {
  return (a) => g(f(a));
}

const addThenDouble = compose((x: number) => x + 1, (x: number) => x * 2);
const result = addThenDouble(5);  // 12

The compose is the composition, and the addThenDouble is the combined function. The generics are the flow, and the composition is the pattern.


The variance of the callbacks

The callback’s parameter types are contravariant. A callback with the wider parameter is assignable to the callback with the narrower one, which is the safety.

type Handler<T> = (value: T) => void;

const wide: Handler<Animal> = (a) => console.log(a.name);
const narrow: Handler<Dog> = (d) => console.log(d.breed);

const h: Handler<Dog> = wide;  // ✅ the wide accepts the Dog
// const w: Handler<Animal> = narrow;  // ❌ the narrow does not accept the Cat

The wide accepts the Animal, which includes the Dog, so it is assignable to the Handler<Dog>. The narrow accepts only the Dog, so it is not assignable to the Handler<Animal> — the Animal could be the Cat.

Why the contravariance is the safety. The callback is the consumer, and the wider parameter is the safer. The narrower parameter is the risk, and the compiler rejects it.

Why the return type is covariant. The callback’s return type is the producer, and the narrower return is the safer. A callback that returns the Dog is assignable to the one that returns the Animal.

type Factory<T> = () => T;
const dogFactory: Factory<Dog> = () => ({ name: 'Rex', breed: 'Lab' });
const animalFactory: Factory<Animal> = dogFactory;  // ✅

The dogFactory returns the Dog, which is the Animal, so it is assignable. The return type is covariant.

Why the parameters are contravariant and the returns are covariant. The consumer’s parameter is the input, and the producer’s return is the output. The input’s wider is the safer, and the output’s narrower is the safer. The two are the variance, and the function type is the consumer.

Why the strictFunctionTypes matters. The strictFunctionTypes: true option (implied by strict) enforces the contravariance for the function type positions. The false makes the parameters bivariant, which is the unsound.

Why the method parameters are the exception. The method parameters are bivariant even with the strictFunctionTypes. The class and the interface methods are the exception, and the function properties are the strict. The two are the difference, and the method’s exception is the backward compatibility.

Why the variance matters for the callbacks. The callback’s parameter type determines the assignability, and the wrong direction is the runtime error. The compiler enforces the direction, and the callback is the safe.


Complete Example Session

// ============================================
// PART 1: THE FUNCTION TYPE
// ============================================

type Adder = (a: number, b: number) => number;

const add: Adder = (a, b) => a + b;

// ============================================
// PART 2: THE HIGHER-ORDER FUNCTION
// ============================================

function apply(a: number, b: number, fn: (x: number, y: number) => number): number {
  return fn(a, b);
}

apply(1, 2, (a, b) => a + b);  // a, b inferred as number

// ============================================
// PART 3: THE PARAMETER INFERENCE
// ============================================

const numbers = [1, 2, 3];
numbers.map((n) => n * 2);  // n is number
numbers.map((n, i) => n + i);  // n is number, i is number

// ============================================
// PART 4: THE THIS PARAMETER
// ============================================

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

const obj = { name: 'Alice', greet };
obj.greet('Hello');  // ✅
// greet('Hello');  // ❌

// ============================================
// PART 5: THE OVERLOADS
// ============================================

function on(event: 'click', handler: (e: MouseEvent) => void): void;
function on(event: 'keydown', handler: (e: KeyboardEvent) => void): void;
function on(event: string, handler: (e: Event) => void): void {
  // implementation
}

on('click', (e) => e.clientX);   // e is MouseEvent
on('keydown', (e) => e.key);     // e is KeyboardEvent

// ============================================
// PART 6: THE GENERIC CALLBACK
// ============================================

function map<T, U>(array: T[], fn: (value: T, index: number) => U): U[] {
  return array.map(fn);
}

const lengths = map(['a', 'bb', 'ccc'], (s) => s.length);
// lengths is number[]

// ============================================
// PART 7: THE CONSTRAINED GENERIC
// ============================================

function pluck<T, K extends keyof T>(array: T[], key: K): T[K][] {
  return array.map((item) => item[key]);
}

const names = pluck([{ name: 'Alice' }, { name: 'Bob' }], 'name');
// names is string[]

// ============================================
// PART 8: THE HIGHER-ORDER RETURN
// ============================================

function multiplyBy(factor: number): (value: number) => number {
  return (value) => value * factor;
}

const double = multiplyBy(2);
double(5);  // 10

// ============================================
// PART 9: THE COMPOSITION
// ============================================

function compose<A, B, C>(f: (a: A) => B, g: (b: B) => C): (a: A) => C {
  return (a) => g(f(a));
}

const addThenDouble = compose((x: number) => x + 1, (x: number) => x * 2);
addThenDouble(5);  // 12

// ============================================
// PART 10: WHAT NOT TO DO
// ============================================

// Don't use `any` for the callback
declare function unsafe(fn: any): void;  // loses the inference

// Don't forget the this parameter
function wrong(this: void) { return this.name; }  // ❌

// Don't misorder the overloads
// function on(event: string, ...): void;
// function on(event: 'click', ...): void;  // the string matches first

// Don't use the narrower parameter where the wider is expected
// const w: Handler<Animal> = narrow;  // ❌ the narrow is too specific

// Don't forget the generic constraints
function bad<T>(array: T[], key: string): unknown[] {
  return array.map((item) => (item as any)[key]);  // no checking
}

The ten parts cover the function type, the higher-order function, the parameter inference, the this parameter, the overloads, the generic callback, the constrained generic, the higher-order return, the composition, and the anti-patterns.


Quick Reference

The Function Type

FormMeaning
(a: number) => numberA function
() => voidNo parameters, no return
(a: number, b?: number) => numberOptional parameter
(...args: number[]) => numberRest parameter
(this: T, a: number) => voidThe this parameter

The Callback Inference

ContextInference
array.map(fn)(value: T, index: number, array: T[])
array.filter(fn)(value: T, index: number, array: T[])
setTimeout(fn, ms)() => void
addEventListener(e: Event) => void

The this Parameter

FormMeaning
this: TThe this is T
this: voidThe this is not allowed
No thisThe this is any (implicit)

The Overloads

FormMeaning
function f(a: string): void;The overload
function f(a: number): void;The overload
function f(a: string | number): void {}The implementation

The Generic Callback

FormMeaning
<T, U>(arr: T[], fn: (v: T) => U): U[]The shared type
<T, K extends keyof T>(arr: T[], k: K): T[K][]The constrained key
<A, B, C>(f: (a: A) => B, g: (b: B) => C): (a: A) => CThe composition

The Variance

PositionVarianceDirection
ParameterContravariantWider is safer
ReturnCovariantNarrower is safer
Method parameterBivariantThe exception

Best Practices

✅ Do This:

// Use the function type for the callback
function apply(a: number, fn: (x: number) => number): number { ... } // ✅

// Let the callback's parameters be inferred
numbers.map((n) => n * 2);                                     // ✅

// Use the this parameter for the receiver
function greet(this: { name: string }): string { ... }         // ✅

// Use the overloads for the different callback shapes
function on(event: 'click', handler: (e: MouseEvent) => void): void; // ✅

// Use the generic for the shared type
function map<T, U>(arr: T[], fn: (v: T) => U): U[] { ... }     // ✅

// Use the constrained generic for the key
function pluck<T, K extends keyof T>(arr: T[], k: K): T[K][] { ... } // ✅

// Use the composition for the combining
const composed = compose(f, g);                                // ✅

// Use the narrower return and the wider parameter
type Handler<T> = (value: T) => void;                          // ✅

❌ Don’t Do This:

// Don't use `any` for the callback
function apply(fn: any): void { ... }  // loses the inference   // ⚠️

// Don't misorder the overloads
function on(event: string, ...): void;
function on(event: 'click', ...): void;  // the string matches first // ⚠️

// Don't use the narrower parameter where the wider is expected
const w: Handler<Animal> = (d: Dog) => {};  // ❌                 // ⚠️

// Don't forget the this parameter
function greet() { return this.name; }  // this is any           // ⚠️

// Don't use the arrow function for the method that needs this
const obj = { greet: () => this.name };  // this is the enclosing // ⚠️

// Don't forget the generic constraints
function bad<T>(arr: T[], k: string) { return arr.map((i) => i[k]); } // ⚠️

Common Pitfalls

PitfallProblemSolution
any callbackNo inferenceUse the function type
Misordered overloadsThe wrong overload matchesThe specific first
Narrow parameterThe unsound assignmentThe wider parameter
Missing thisThe this is anyDeclare the this
Arrow for the methodThe lexical thisUse the regular function
Missing constraintThe key is uncheckedUse K extends keyof T
The callback countThe extra parameterThe fewer parameters
The return typeThe mismatchThe function type’s return

Real-World Examples

1. The function type

type Adder = (a: number, b: number) => number;

2. The higher-order function

function apply(a: number, fn: (x: number) => number): number { ... }

3. The inference

numbers.map((n) => n * 2);

4. The this parameter

function greet(this: { name: string }): string { ... }

5. The overloads

function on(event: 'click', handler: (e: MouseEvent) => void): void;

6. The generic callback

function map<T, U>(arr: T[], fn: (v: T) => U): U[] { ... }

7. The constrained key

function pluck<T, K extends keyof T>(arr: T[], k: K): T[K][] { ... }

8. The higher-order return

function multiplyBy(factor: number): (value: number) => number { ... }

9. The composition

function compose<A, B, C>(f: (a: A) => B, g: (b: B) => C): (a: A) => C { ... }

10. The variance

type Handler<T> = (value: T) => void;
const wide: Handler<Animal> = (a) => { ... };
const h: Handler<Dog> = wide;  // ✅

Visual: The Function Type

┌──────────────────────────────────────────────────────────┐
│  type Adder = (a: number, b: number) => number;          │
│                 │       │         │                      │
│                 │       │         └── the return type    │
│                 │       └── the second parameter         │
│                 └── the first parameter                  │
│                                                          │
│  const add: Adder = (a, b) => a + b;                     │
│                       │  │                               │
│                       └──┴── inferred as number          │
│                                                          │
│  The type describes the shape.                           │
│  The implementation is inferred.                         │
│                                                          │
└──────────────────────────────────────────────────────────┘

Visual: The Higher-Order Function

┌──────────────────────────────────────────────────────────┐
│  function apply(                                         │
│    a: number,                                            │
│    b: number,                                            │
│    fn: (x: number, y: number) => number                  │
│  ): number {                                             │
│    return fn(a, b);                                      │
│  }                                                       │
│                                                          │
│  apply(1, 2, (a, b) => a + b);                           │
│               │  │                                       │
│               └──┴── inferred as number                  │
│                                                          │
│  The callback's parameters are inferred from the         │
│  function type.                                          │
│                                                          │
└──────────────────────────────────────────────────────────┘

Visual: The this Parameter

┌──────────────────────────────────────────────────────────┐
│  function greet(this: { name: string }, greeting: string): string {│
│    return `${greeting}, ${this.name}`;                   │
│  }                                                       │
│                                                          │
│  const obj = { name: 'Alice', greet };                   │
│  obj.greet('Hello');  ✅                                 │
│                                                          │
│  greet('Hello');  ❌                                     │
│    the this is undefined, and the type requires the obj  │
│                                                          │
│  The this parameter is the compile-time check.           │
│  It is erased at runtime.                                │
│                                                          │
└──────────────────────────────────────────────────────────┘

Visual: The Overloads

┌──────────────────────────────────────────────────────────┐
│  function on(event: 'click', handler: (e: MouseEvent) => void): void;│
│  function on(event: 'keydown', handler: (e: KeyboardEvent) => void): void;│
│  function on(event: string, handler: (e: Event) => void): void {│
│    // implementation                                     │
│  }                                                       │
│                                                          │
│  on('click', (e) => e.clientX);                          │
│                │                                         │
│                └── e is MouseEvent (the first overload)  │
│                                                          │
│  on('keydown', (e) => e.key);                            │
│                │                                         │
│                └── e is KeyboardEvent (the second overload)│
│                                                          │
│  The overloads are checked in the order they appear.     │
│  The specific must come before the general.              │
│                                                          │
└──────────────────────────────────────────────────────────┘

Visual: The Variance

┌──────────────────────────────────────────────────────────┐
│  type Handler<T> = (value: T) => void;                   │
│                                                          │
│  const wide: Handler<Animal> = (a) => console.log(a.name);│
│  const narrow: Handler<Dog> = (d) => console.log(d.breed);│
│                                                          │
│  const h: Handler<Dog> = wide;  ✅                       │
│    the wide accepts the Animal, which includes the Dog   │
│                                                          │
│  const w: Handler<Animal> = narrow;  ❌                  │
│    the narrow accepts only the Dog, not the Cat          │
│                                                          │
│  The parameter is contravariant.                         │
│  The wider parameter is the safer.                       │
│                                                          │
└──────────────────────────────────────────────────────────┘

Visual: The Generic Callback

┌──────────────────────────────────────────────────────────┐
│  function map<T, U>(array: T[], fn: (value: T, index: number) => U): U[] {│
│    return array.map(fn);                                 │
│  }                                                       │
│                                                          │
│  map(['a', 'bb', 'ccc'], (s) => s.length);               │
│      │                    │                              │
│      │                    └── s inferred as string       │
│      └── T inferred as string                            │
│                                                          │
│  The result: number[]                                    │
│  (the U is inferred as number from s.length)             │
│                                                          │
└──────────────────────────────────────────────────────────┘

Summary

ItemValue
Function type(a: number) => number
Callback inferenceFrom the function type
this parameterthis: T (erased)
OverloadsMultiple signatures
Generic callback<T, U>(arr: T[], fn: (v: T) => U)
Constrained keyK extends keyof T
Composition<A, B, C>(f, g) => (a: A) => C
Parameter varianceContravariant
Return varianceCovariant

Key takeaways:

  • A function type is the parameter list and the return type — the (a: number) => number is the shape, and the callback is the value
  • The callback’s parameters are inferred from the function type — the numbers.map((n) => n * 2) infers the n as the number
  • The this parameter types the receiver — it is the first parameter, it is erased at runtime, and it catches the wrong this at compile time
  • The overloads declare the multiple callback shapes — the specific overloads come first, and the callback’s parameter is inferred from the matching one
  • The generic callback shares the type parameters with the function — the <T, U> is the shared type, and the inference flows from the input to the output
  • The constrained generic is the safety — the K extends keyof T prevents the key that does not exist, and the T[K] is the property’s type
  • The higher-order function can return the callback — the multiplyBy returns the function, and the double is the result
  • The composition combines the callbacks — the compose takes the two functions and returns the combined one
  • The callback’s parameter is contravariant — the wider parameter is the safer, and the narrower is the unsound
  • The return type is covariant — the narrower return is the safer, and the wider is the unsound

Remember: The callbacks and the higher-order functions are the composition pattern, and TypeScript’s types make them precise. The function type describes the shape, the inference types the callback’s parameters, the this parameter types the receiver, the overloads handle the multiple shapes, and the generics share the types. The variance is the safety: the parameter is contravariant and the return is covariant. The callbacks are the vocabulary, and the types are the grammar.


Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!