Functions move data around in JavaScript. TypeScript lets you type their inputs and outputs.

Parameter type annotations

Put the type after each parameter:

function greet(name: string) {
  console.log("Hello, " + name.toUpperCase() + "!!");
}

Bad arguments are caught:

greet(42);
// Error: Argument of type 'number' is not assignable to parameter of type 'string'.

Even without parameter annotations, TypeScript still checks that you passed the right number of arguments.

Return type annotations

Return types go after the parameter list:

function getFavoriteNumber(): number {
  return 26;
}

Often you can omit this : TypeScript infers the return type from return statements. Teams sometimes keep explicit returns for documentation or to lock the API.

Promises

For async functions, wrap the resolved type in Promise<>:

async function getFavoriteNumber(): Promise<number> {
  return 26;
}

Contextual typing (anonymous functions)

When TypeScript already knows how a function will be called, it can type the parameters for you:

const names = ["Alice", "Bob", "Eve"];

names.forEach(function (s) {
  console.log(s.toUpperCase());
});

names.forEach((s) => {
  console.log(s.toUpperCase());
});

s is inferred as string because names is string[] and forEach expects (value: string) => void. That is contextual typing : the surrounding context fills in the type.

Putting it together

function printCoord(x: number, y: number): void {
  console.log(`(${x}, ${y})`);
}

function add(a: number, b: number): number {
  return a + b;
}

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

void means the function does not return a useful value (like console.log).

Practice

  1. Write formatUser(name: string, age: number): string that returns "Name (age)".
  2. Call it with swapped argument types and read the errors.
  3. Pass an array of users into .map and rely on contextual typing for the callback parameter.