Skip to content

Function Types and Callbacks

Functions are values in JavaScript — they are passed as arguments, returned from other functions, and stored in variables. TypeScript lets you type functions as values just as precisely as any other type.

A function type describes a callable value: its parameters and return type:

type Formatter = (score: number, total: number) => string;

Formatter is a type alias for any function that accepts a number and a number and returns a string. You can use it to type a variable holding a function:

const formatScore: Formatter = (score, total) => `${score} / ${total} correct`;

Notice that the parameter names in the arrow function do not need to be score and total — TypeScript checks types and order, not names. The function satisfies Formatter as long as it takes two numbers and returns a string.

When a function accepts a callback, you annotate the callback’s type inline or by reference:

function withResult(
score: number,
total: number,
onComplete: (score: number, total: number) => void,
): void {
onComplete(score, total);
}
withResult(7, 10, (s, t) => console.log(`${s} / ${t}`)); // ✓

TypeScript infers the types of s and t from the onComplete parameter’s type — you do not need to annotate them at the call site. This is contextual typing: the expected type at a given position provides the type for the value passed there.

You see contextual typing every time you use map, filter, or forEach:

const questions: TriviaQuestion[] = [];
questions.forEach(q => {
console.log(q.question); // TypeScript knows q is TriviaQuestion
});
const texts: string[] = questions.map(q => q.question); // inferred: string[]

The callback passed to forEach has its parameter typed as TriviaQuestion because TypeScript knows questions is TriviaQuestion[]. You get full autocomplete and type checking inside the callback without writing a single annotation.

A function typed to return void is not the same as one typed to return undefined:

type VoidFn = () => void;
type UndefFn = () => undefined;
const a: VoidFn = () => 42; // ✓ — void callbacks may return any value; it is ignored
const b: UndefFn = () => 42; // Error — must return undefined
const c: UndefFn = () => undefined; // ✓

This matters for callbacks. Array methods like forEach type their callbacks as returning void — meaning they do not care what the callback returns and will ignore it. If you accidentally write a map when you meant forEach, the return value would be discarded, but TypeScript would not complain because void accepts any return.

AceIt’s main.ts attaches event listeners with callbacks. TypeScript knows the types of browser events:

const startBtn = document.getElementById('start-btn') as HTMLButtonElement;
startBtn.addEventListener('click', (event: MouseEvent) => {
event.preventDefault(); // MouseEvent has preventDefault
// ...
});

addEventListener is typed in the DOM library — it knows that 'click' events produce MouseEvent values. You rarely need to annotate event explicitly; TypeScript infers it from the event name.

In main.ts, the answer-click handler uses a delegated pattern on a container element. Storing a handler reference with a typed variable makes the intent clear:

type AnswerHandler = (event: MouseEvent) => void;
const handleAnswerClick: AnswerHandler = event => {
const btn = (event.target as HTMLElement).closest<HTMLButtonElement>('.answer-btn');
if (!btn || awaitingNext) return;
// ...
};
answersEl.addEventListener('click', handleAnswerClick);

The type alias documents what the handler is, makes it reusable, and lets TypeScript verify the signature matches what addEventListener expects.

In a new file handlers.ts (you can delete it after the exercise):

  1. Declare type ScoreCallback = (score: number, total: number) => void.
  2. Write a function runQuiz(questions: TriviaQuestion[], onComplete: ScoreCallback): void — it should call onComplete with a score and total after “completing” the quiz (simulate with hardcoded values).
  3. Call runQuiz with an inline arrow function. Confirm TypeScript infers the callback parameter types.
  4. Write function applyToAll(questions: TriviaQuestion[], transform: (q: TriviaQuestion) => string): string[]. Call it with a callback that returns q.question. Confirm the return type is string[].
  • Function type expressions describe callable values: (param: Type) => ReturnType.
  • Callback parameters are typed with function type expressions, either inline or via a type alias.
  • Contextual typing lets TypeScript infer callback parameter types from the expected type — no annotation needed at the call site.
  • Array method callbacks (forEach, map, filter) get their parameter types from the array’s element type.
  • DOM event listener callbacks get their event parameter type from the event name — 'click' infers MouseEvent.