void vs undefined.1. Function Annotations & Syntax#
TypeScript allows annotating function declarations, arrow functions, and method expressions:
// Named Function Declaration
function add(a: number, b: number): number {
return a + b;
}
// Arrow Function Expression
const multiply = (a: number, b: number): number => a * b;
// Function Type Alias
type MathFn = (a: number, b: number) => number;
const divide: MathFn = (a, b) => a / b;2. Optional, Default, and Rest Parameters#
Optional Parameters (?)#
Optional parameters must always appear after required parameters in the parameter list. Inside the function body, an optional parameter x?: number has the type number | undefined.
function formatLog(message: string, userId?: string): string {
if (userId) {
return `[User: ${userId}]: ${message}`;
}
return `[System]: ${message}`;
}Default Parameters#
When you supply a default value, TypeScript automatically infers the parameter’s type and treats it as optional for the caller:
// 'prefix' is inferred as 'string', default value "LOG"
function printLog(message: string, prefix = "LOG") {
console.log(`[${prefix.toUpperCase()}]: ${message}`);
}
printLog("Server started"); // Output: [LOG]: Server started
printLog("DB connected", "DEBUG"); // Output: [DEBUG]: DB connected
Rest Parameters (...args)#
Rest parameters capture an arbitrary number of arguments into an array. They must be typed as an Array type (T[] or Tuple):
function sumAll(initialValue: number, ...values: number[]): number {
return values.reduce((acc, curr) => acc + curr, initialValue);
}
console.log(sumAll(10, 1, 2, 3, 4)); // 20
3. Destructured Parameter Typing#
A common source of syntax confusion is typing destructured object parameters.
// ❌ WRONG SYNTAX: This attempts to rename 'name' to 'string' in JavaScript!
// function printUser({ name: string, age: number }) { ... }
// 🟢 CORRECT SYNTAX: Destructure on left, type object on right!
function printUser({ name, age }: { name: string; age: number }) {
console.log(`${name} is ${age} years old.`);
}
// 🟢 BETTER: Use a type alias for clean signatures
type UserPayload = {
name: string;
age: number;
};
function printUserClean({ name, age }: UserPayload) {
console.log(`${name} is ${age} years old.`);
}4. The Quirks of the void Return Type#
In traditional languages (C, C++, Java), void means a function returns nothing and cannot return a value.
In TypeScript, void behaves differently depending on whether it is used on a direct function implementation or a callback signature.
Case 1: Direct Function Implementation#
When a function implementation is annotated with : void, it cannot return a value (other than undefined or an empty return; statement).
function logToConsole(msg: string): void {
console.log(msg);
// return "Done"; // ❌ Compiler Error: Type 'string' is not assignable to type 'void'.
return; // Valid
}Case 2: Callback Signatures (The Ignored Return Value Rule!)#
When void is used as the return type of a callback parameter or function type alias, TypeScript permits the implementation to return ANY value, but guarantees that callers will ignore the return value!
type VoidCallback = () => void;
// 🟢 WORKED: Returning a boolean is allowed for a void callback!
const onClick: VoidCallback = () => {
return true; // No compiler error!
};
// Why does this exist?
// It allows passing array methods like Array.prototype.push directly to callbacks!
const numbers = [1, 2, 3];
const targetStorage: number[] = [];
// Array.prototype.forEach expects a callback: (val: number) => void
// targetStorage.push() RETURNS a number (the new array length).
// Because of the 'void' callback rule, TS permits this cleanly:
numbers.forEach((n) => targetStorage.push(n));void vs undefined#
| Return Type | Meaning | Permitted Implementations |
|---|---|---|
void (Callback) | “I will ignore whatever you return” | return true;, return 42;, return; |
void (Direct) | “This function produces no return value” | return; or no return statement |
undefined | “This function MUST return literal undefined” | return undefined; (MUST explicitly return!) |
// ❌ Error: A function whose declared type is 'undefined' MUST return a value.
// function getNothing(): undefined {
// console.log("nothing");
// }
function getNothingCorrect(): undefined {
console.log("nothing");
return undefined; // Must be explicit!
}5. Call Signatures & Callable Objects#
In JavaScript, functions are first-class objects. A function can have properties attached to it (like express() or axios).
To type a function that also has properties attached to it, use an Object Type with a Call Signature:
// A callable function that also has a 'version' string property attached!
interface ExecutableRunner {
(command: string): boolean; // The Call Signature
version: string; // Property attached to function
description: string; // Property attached to function
}
// Implementation
const runner: ExecutableRunner = Object.assign(
(command: string) => {
console.log(`Executing ${command}`);
return true;
},
{
version: "2.1.0",
description: "Production Runner CLI",
}
);
// Calling as a function:
runner("deploy"); // Output: "Executing deploy"
// Accessing properties:
console.log(runner.version); // Output: "2.1.0"
console.log(runner.description); // Output: "Production Runner CLI"
Summary & Next Steps#
In this episode:
- We annotated functions, arrow functions, and parameter signatures.
- We structured destructured parameter signatures correctly (
{ name }: User). - We mastered
voidsemantics: Direct functions cannot return values, butvoidcallbacks ignore returned values to support JavaScript patterns likeforEach. - We differentiated
voidfromundefined. - We built Callable Objects using interface Call Signatures.
In Episode 13: Function Overloads, we will learn how to declare multiple function signatures for functions that behave differently based on their argument types!

