Function Types, Overloads & Optional/Default Parameters
10 questions foundUse 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
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
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
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
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
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
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
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
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
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