level || 50 looks like "use 50 when no level was passed", and TypeScript accepts it happily — the types are fine. But || falls back on any falsy value, and 0 is falsy. A caller asking for volume 0 (mute) gets 50 instead, and no type check can see it, because 0 is a perfectly valid number.
function setVolume(level?: number): number {
const volume = level || 50; // default when no level is given
return volume;
}
console.log(setVolume()); // 50
console.log(setVolume(80)); // 80
console.log(setVolume(0)); // 50 <- the caller asked for 0A function's parameters and return value can each carry a type annotation, checked at every call site at compile time. A parameter marked with ? is optional; giving a parameter a default value makes it optional automatically, with TypeScript inferring its type from the default. Function overloads let a single function name accept several distinct signatures, each with its own parameter and return types, useful when a function's behavior genuinely depends on which shape of arguments it receives.
Typed parameters and return values
The return type is usually inferred automatically, but annotating it explicitly documents intent and catches a mistaken return path immediately.
function add(a: number, b: number): number {
return a + b;
}
add(2, "3"); // COMPILE ERROR: argument of type string not assignable to numberOptional and default parameters
An optional parameter (?) may be undefined inside the function body and must be checked before use; a default value makes a parameter optional at the call site while guaranteeing it's never undefined inside the function.
function greet(name: string, greeting?: string): string {
return `${greeting ?? "Hello"}, ${name}!`; // greeting might be undefined
}
function greetWithDefault(name: string, greeting: string = "Hello"): string {
return `${greeting}, ${name}!`; // greeting is NEVER undefined here
}
greet("Alice"); // fine, greeting is undefined
greetWithDefault("Bob"); // fine, greeting defaults to "Hello"That's why greet above uses ?? rather than ||: nullish coalescing only falls back when the value is null or undefined — exactly the "argument was left out" case an optional parameter introduces — so 0, "" and false pass through untouched. A default parameter (level: number = 50) is the other correct option:
function setVolume(level?: number): number {
const volume = level ?? 50; // only null/undefined fall back
return volume;
}
console.log(setVolume()); // 50
console.log(setVolume(80)); // 80
console.log(setVolume(0)); // 0Function overloads: one name, multiple signatures
The overload signatures (no body) describe every valid call pattern; the final implementation signature must be compatible with all of them, and is the only one with an actual function body.
function makeDate(timestamp: number): Date;
function makeDate(year: number, month: number, day: number): Date;
function makeDate(yearOrTimestamp: number, month?: number, day?: number): Date {
if (month !== undefined && day !== undefined) {
return new Date(yearOrTimestamp, month, day);
}
return new Date(yearOrTimestamp);
}
makeDate(1746576000000); // matches the first overload
makeDate(2026, 4, 6); // matches the second overload
// makeDate(2026, 4); // COMPILE ERROR: no overload matches this call