Function Types, Overloads & Optional/Default Parameters

10 questions found

How do you write a type annotation for a function value, like a callback parameter?

Beginner
Use arrow-style syntax `(param: Type) => ReturnType` to describe a function's shape, specifying each parameter's type and the return type.
function process(callback: (value: number) => void) {
  callback(42);
}
process((n) => console.log(n));
Real-world example Typing a callback parameter passed to an array method wrapper or an event handler registration function.

Common follow-ups: How do you write a function type as a named 'type' alias instead of inline?

Generics

How do you mark a function parameter as optional, and what type does it implicitly get?

Beginner
Add a '?' after the parameter name; TypeScript automatically adds 'undefined' to its type, and optional parameters must come after all required parameters in the parameter list.
function greet(name: string, title?: string) {
  return title ? `${title} ${name}` : name;
}
greet('Sam');           // 'Sam'
greet('Sam', 'Dr.');    // 'Dr. Sam'
Real-world example Making a configuration parameter optional so callers can omit it and rely on default in-function behavior.

Common follow-ups: Can an optional parameter come before a required parameter in the list?

Nullability: strictNullChecks Optional Chaining & Nullish Coalescing

How do you give a function parameter a default value, and how does TypeScript infer its type?

Beginner
Assign a default value directly in the parameter list using '='; TypeScript infers the parameter's type from the default value's type unless you also add an explicit annotation, and the parameter becomes optional to callers.
function greet(name: string, greeting = 'Hello') {
  return `${greeting}, ${name}`;
}
greet('Sam');           // 'Hello, Sam'
greet('Sam', 'Hi');     // 'Hi, Sam'
Real-world example Providing a sensible default page size or sort order for a paginated query function.

Common follow-ups: Can a default parameter's value reference an earlier parameter in the same function?

Type Inference & Contextual Typing

What are function overloads, and how do they let a single function name handle multiple different call signatures?

Intermediate
You declare multiple overload signatures (no implementation) followed by one general implementation signature that's compatible with all of them; TypeScript uses the overload signatures for type-checking calls, picking the first matching one, while the implementation signature is only used internally and isn't visible to callers.
function makeDate(timestamp: number): Date;
function makeDate(year: number, month: number, day: number): Date;
function makeDate(yearOrTimestamp: number, month?: number, day?: number): Date {
  return month !== undefined
    ? new Date(yearOrTimestamp, month, day!)
    : new Date(yearOrTimestamp);
}
makeDate(2026);           // uses first overload
makeDate(2026, 7, 8);     // uses second overload
Real-world example Supporting both a single-timestamp and a year/month/day style call for a date-construction utility, each with precise argument-count checking.

Common follow-ups: What error occurs if you call the function with arguments that don't match ANY of the overload signatures?

Union & Intersection Types

How do rest parameters work with type annotations, and what type must the annotation be?

Intermediate
A rest parameter (prefixed with '...') collects any remaining arguments into an array, so its type annotation must be an array type (or tuple type for fixed-shape variadic arguments), matching how rest parameters behave in plain JavaScript.
function sum(...nums: number[]): number {
  return nums.reduce((a, b) => a + b, 0);
}
sum(1, 2, 3); // 6
Real-world example Typing a logging function that accepts a variable number of arguments of the same type.

Common follow-ups: Can you type a rest parameter as a tuple to enforce a fixed minimum number of arguments?

Primitive Types Arrays & Tuples

How does TypeScript check compatibility between two function types, particularly regarding parameter counts?

Intermediate
A function type A is assignable to function type B if A's parameters are compatible with B's (contravariantly, roughly) AND A doesn't require MORE parameters than B provides — meaning a function taking fewer parameters can safely stand in for one expecting more, since extra arguments are simply ignored, but not the reverse.
type Handler = (event: Event, index: number) => void;
const handler: Handler = (event) => console.log(event); // OK: fewer params allowed
// const bad: Handler = (event, index, extra) => {}; // Error: too many required params
Real-world example Passing a simple callback like `(item) => ...` where an array method expects `(item, index, array) => ...`.

Common follow-ups: Why does this asymmetry exist — why can a function with FEWER parameters satisfy a type expecting MORE?

Structural Typing & Duck Typing

How do you write a generic function type with overloads that behave differently based on a literal argument value?

Advanced
Combine overload signatures with specific literal parameter types for each variant, letting the return type or accepted shape change based on that literal — commonly used for APIs like document.createElement, where the tag name literal determines the specific returned element type.
function createElement(tag: 'a'): HTMLAnchorElement;
function createElement(tag: 'img'): HTMLImageElement;
function createElement(tag: string): HTMLElement {
  return document.createElement(tag);
}
const link = createElement('a'); // typed as HTMLAnchorElement
Real-world example Precisely typing a factory function whose return shape depends on a specific string literal argument, like DOM element creation.

Common follow-ups: Could this same effect be achieved with a single generic function and conditional types instead of overloads?

Conditional Types

Why must the overload signatures be listed from MOST specific to LEAST specific, and what bug occurs if this order is reversed?

Advanced
TypeScript resolves overloads by picking the FIRST signature (top to bottom) that matches the call — if a broader, less specific overload is listed before a more specific one, it will always match first, silently shadowing the more specific overload and giving callers a less precise type than intended.
// WRONG ORDER: general overload shadows the specific one
function process(value: unknown): string;
function process(value: number): number; // unreachable! unknown already matched
function process(value: any): any { /* ... */ }

process(42); // incorrectly resolves to the 'unknown' overload, returns string type
Real-world example Debugging why a specific overload's more precise return type never gets applied, tracing it to overload ordering.

Common follow-ups: Does the TypeScript compiler warn you when an overload signature is unreachable like this?

Never Unknown & Void Types

How do 'this' parameter annotations work in a function type, and what problem do they solve?

Advanced
An explicit `this: SomeType` as the FIRST parameter (removed from the actual call signature) tells TypeScript what type 'this' must be when the function is called, letting the compiler catch incorrect method extraction or binding — like passing an object method as a bare callback where 'this' would be lost.
interface Button {
  label: string;
  onClick(this: Button, event: Event): void;
}
const button: Button = {
  label: 'Save',
  onClick(this: Button, event) {
    console.log(this.label); // 'this' is correctly typed as Button here
  }
};
Real-world example Catching a bug where a class method is passed as a raw callback (losing its 'this' binding) before it causes a runtime error.

Common follow-ups: How does this relate to needing .bind(this) or an arrow function for event handlers in classes?

this & Binding

How would you type a higher-order function that accepts a function and returns a new function with an additional, injected parameter removed from its signature?

Advanced
Use generics with the built-in Parameters<T> and ReturnType<T> utility types (or simple generic inference) to derive the wrapped function's exact parameter and return types automatically, producing full type safety without manually re-declaring the shape.
function withLogging<Args extends unknown[], R>(
  fn: (...args: Args) => R
): (...args: Args) => R {
  return (...args: Args): R => {
    console.log('calling with', args);
    return fn(...args);
  };
}
const add = (a: number, b: number) => a + b;
const loggedAdd = withLogging(add); // inferred as (a: number, b: number) => number
Real-world example Writing a generic, fully type-safe decorator-like wrapper (logging, timing, memoization) that preserves the original function's exact signature.

Common follow-ups: How would Parameters<typeof fn> and ReturnType<typeof fn> be used as an alternative to the generic-inference approach shown here?

Generics