Functions and generics

Type callbacks

Describe the parameters and result of a function passed as a value.

In JavaScript, functions are values. You pass them to array methods, event handlers and utilities all the time. TypeScript needs a way to describe those values, and that is what a function type does.

A function type describes how another function may be called. Here transform() accepts a string and a callback that turns a string into another string:

function transform(value: string, fn: (value: string) => string) {
  return fn(value)
}

Read (value: string) => string as “a function that takes one string and returns a string”. The arrow separates the parameters from the result. Now transform() only accepts callbacks that match that contract.

Pass something else and the call site fails, not the body:

transform('hello', (n: number) => n * 2)
error TS2345: Argument of type '(n: number) => number' is not assignable to parameter of type '(value: string) => string'.
  Types of parameters 'n' and 'value' are incompatible.
    Type 'string' is not assignable to type 'number'.

Notice how the message drills down. First the whole function type, then the parameter that clashes, then the two primitive types. Read it bottom up when you are in a hurry.

Parameter names inside the type are documentation. Compatibility depends on the types and positions, not the names. (text: string) => string and (value: string) => string are the same type.

Inference inside inline callbacks

When you write the callback inline, the surrounding context gives its parameter a type:

const result = transform('hello', value => value.toUpperCase())

You do not annotate value. TypeScript looks at the type of fn and knows value must be a string. This is why callbacks in .map() and .filter() almost never need annotations: the array’s element type flows in.

void callbacks

Use void as the return type when the caller ignores whatever the callback returns:

function visit(fn: (title: string) => void) {
  fn('TypeScript')
}

void means “whatever this returns, I will not use it”. You can still pass a callback that returns something. visit(title => title.length) is fine, because ignoring a value is always safe. This flexibility is deliberate, and it makes void the right default for handlers and listeners.

One trap with optional parameters

Be careful with optional parameters in callback types. Writing (index?: number) => void means your code may call the callback without an index. It does not mean callers may ignore an argument you always provide.

If you always pass the index, declare it required. Callers who do not need it can still pass a callback with fewer parameters, and TypeScript accepts that. A function that takes zero arguments is a valid (index: number) => void, because it ignores the extra argument safely.

Try this on your own: change transform() so the callback converts a string into a number. Hover over the result of a call and confirm it became number.

Lesson completed