| |

TypeScript 8 ๐Ÿ”ท Functions โ€” Parameters, Return Types, and Overloads

Functions are where TypeScript’s type system does the most day-to-day work. Every function has a signature โ€” the types of its parameters and its return value โ€” and TypeScript checks every call against that signature. Getting functions right means typing parameters (required, optional, rest, defaulted), typing returns (explicit or inferred), understanding void and never, and knowing when to reach for overloads. Functions are the boundary between your type-safe code and the runtime world, so this is where being precise pays off the most.

Key point: Every function parameter is required unless marked optional (?) or given a default. Every function has a return type โ€” usually inferred, sometimes annotated. And when a function’s behavior depends on its argument types, overloads give you multiple signatures for one implementation. Get these three ideas and every function you write will be safer.


Typing parameters

Parameters are typed after their name with a colon.

function greet(name: string, age: number): string {
  return `Hello, ${name}. You are ${age}.`;
}

Every parameter needs a type โ€” or gets one inferred from a default. TypeScript refuses to compile a function with an untyped parameter under strict (via noImplicitAny).

Parameters are required by default:

greet('Alice', 30);               // โœ…
greet('Alice');                   // โŒ missing age
greet('Alice', 30, true);         // โŒ too many arguments

TypeScript enforces both count and type at every call site.

Parameter type inference: TypeScript doesn’t infer parameter types from usage. You must annotate (or let a default supply the type). It infers return types, but never parameter types.

function add(a: number, b: number) { return a + b; }
// a and b must be annotated โ€” return is inferred as number

That asymmetry is deliberate. A function’s parameters are its contract with callers, and TypeScript requires you to state the contract explicitly.

Why parameters need explicit types: The return type can be inferred from the body โ€” the function defines what it returns. Parameters come from the outside world. TypeScript has no way to know what callers will pass without a declaration. That’s why you write the contract yourself.


Optional parameters โ€” ?

An optional parameter may be omitted at the call site. Inside the function it’s T | undefined.

function greet(name: string, title?: string): string {
  return title ? `${title} ${name}` : name;
}

greet('Alice');                   // โœ…
greet('Alice', 'Dr.');            // โœ…

title is string | undefined inside the function. You can’t use it without checking.

Optional parameters must come last:

function f(a?: string, b: number) { }  // โŒ
// A required parameter cannot follow an optional one

If you need an “optional in the middle,” use undefined explicitly:

function f(a: string | undefined, b: number) { }
f(undefined, 42);                 // โœ…

This is rare. Usually you restructure the signature.

Optional is not the same as | undefined:

function a(x?: number) { }        // may be omitted OR pass undefined
function b(x: number | undefined) { }  // must be passed (possibly undefined)

a();                              // โœ…
a(undefined);                     // โœ…
b();                              // โŒ argument required
b(undefined);                     // โœ…

? allows omission. | undefined requires an argument but permits undefined as its value. Subtle but important.

Why the ordering rule: Because TypeScript needs to know which position each argument maps to. If an optional parameter came before a required one, a call with fewer arguments couldn’t decide which parameter to skip. The rule keeps positional argument mapping unambiguous.


Default parameters

A parameter with a default value is optional โ€” you can omit it, and the default fills in.

function greet(name: string, greeting = 'Hello'): string {
  return `${greeting}, ${name}!`;
}

greet('Alice');                   // 'Hello, Alice!'
greet('Alice', 'Hi');             // 'Hi, Alice!'

The default supplies the type. greeting is inferred as string from 'Hello'.

Defaults make parameters optional without ?:

function connect(url: string, port = 8080) { }
connect('http://x');              // โœ…
connect('http://x', 3000);        // โœ…

port is optional โ€” you can omit it. But you can’t pass undefined explicitly and get the default, unless you also accept undefined in the type. In practice, omission is what matters.

Defaults with undefined: Passing undefined triggers the default.

function f(x = 10) {
  return x;
}
f();                              // 10
f(undefined);                     // 10
f(null);                          // null (no default triggered)

This is JavaScript semantics โ€” undefined triggers the default, null doesn’t.

Typing a default that’s wider than the literal:

function create(tags: string[] = []) { }

Empty array default is typed from the annotation. Without annotation, [] would be never[] โ€” which is why you annotate defaults that start empty.

Why defaults are usually better than ?: A default parameter handles the missing case automatically. function f(x = 10) is cleaner than function f(x?: number) { x = x ?? 10; }. Use ? when there’s no sensible default and you need to distinguish “not passed” from “passed a value.”


Rest parameters โ€” ...

A rest parameter collects any number of trailing arguments into an array.

function sum(...nums: number[]): number {
  return nums.reduce((a, b) => a + b, 0);
}

sum();                            // 0
sum(1, 2);                        // 3
sum(1, 2, 3, 4, 5);               // 15

nums is number[] inside the function.

Rest with a specific tuple type:

function pair(...args: [string, number]): string {
  const [name, age] = args;
  return `${name} is ${age}`;
}

pair('Alice', 30);                // โœ…
pair('Alice');                    // โŒ needs two
pair('Alice', 30, true);          // โŒ too many

The rest parameter can have a tuple type โ€” this gives you exactly the fixed-length signature you want.

Rest after regular parameters:

function log(level: string, ...messages: string[]): void {
  for (const msg of messages) console.log(`[${level}] ${msg}`);
}

log('INFO', 'started', 'ready');

Rest must be last. You can have required and optional parameters before it.

Rest of a union type:

function all(...args: (string | number)[]): void { }

Any rest type works โ€” a union, an object, a generic.

Readonly rest: TypeScript treats rest parameters as mutable arrays by default. To prevent mutation, use readonly:

function sum(...nums: readonly number[]): number {
  // nums.push(1);  // โŒ
  return nums.reduce((a, b) => a + b, 0);
}

Why rest parameters matter: They model variadic functions โ€” Math.max, console.log, Promise.all. A rest parameter typed ...args: T[] accepts any number of Ts. A rest parameter typed ...args: [A, B] accepts exactly A then B. The tuple form is underused and powerful โ€” it lets you describe argument lists precisely.


Return types

Every function has a return type. TypeScript infers it from the body.

function add(a: number, b: number) {
  return a + b;
}
// inferred: (a: number, b: number) => number

Explicit return types are optional but often useful.

function add(a: number, b: number): number {
  return a + b;
}

When explicit is better:

  • Public APIs โ€” exported functions should have explicit returns so the contract is stable
  • Recursive functions โ€” inference struggles without the return type
  • Complex bodies โ€” clarity
  • Catching mistakes โ€” if you accidentally return a wider type, the annotation catches it

When inference is fine:

  • Local helper functions
  • Short arrow functions
  • Callbacks with obvious returns

void return type: a function that doesn’t return a useful value.

function log(msg: string): void {
  console.log(msg);
}

void doesn’t mean the function returns undefined โ€” it means “ignore whatever it returns.” This distinction matters for callbacks:

const nums = [1, 2, 3];
nums.forEach(n => { console.log(n); });
// forEach expects: (value: number) => void
// The arrow returns void โ€” fine.

Functions that do return a value are assignable to void callbacks โ€” the return value is just ignored.

nums.forEach(n => n * 2);         // โœ… allowed โ€” return value ignored

This is a special exception for void โ€” otherwise callbacks would be annoying to write.

never return type: a function that never returns.

function fail(msg: string): never {
  throw new Error(msg);
}

function infinite(): never {
  while (true) { }
}

never means the function doesn’t complete normally โ€” it throws or loops forever. It’s assignable to any type (bottom type). Useful for exhaustiveness checks.

function assertNever(x: never): never {
  throw new Error(`Unexpected value: ${x}`);
}

type Shape = { kind: 'circle' } | { kind: 'square' };

function area(s: Shape): number {
  switch (s.kind) {
    case 'circle': return 0;
    case 'square': return 0;
    default: return assertNever(s);   // s is never here
  }
}

If you add a new shape and forget a case, s won’t be never in the default branch, and TypeScript errors. That’s the exhaustive-check pattern.

Promise<T> return type: async functions.

async function fetchUser(id: number): Promise<User> {
  const res = await fetch(`/users/${id}`);
  return res.json();
}

The return type is Promise<User> โ€” the function returns a promise resolving to a User.

Promise<void> vs void: An async function returning nothing has type Promise<void>.

async function log(msg: string): Promise<void> {
  console.log(msg);
}

Why void is different from undefined: void is a signal โ€” “the caller shouldn’t use this return value.” undefined is a real value the caller can check. For callbacks, void accepts functions that return anything, because the return is ignored. If callbacks were typed undefined, every callback would need an explicit return undefined.


Function type expressions

A function type is written (params) => returnType.

type Adder = (a: number, b: number) => number;
type Logger = (msg: string) => void;
type Predicate<T> = (value: T) => boolean;
type Factory<T> = () => T;

You can use these anywhere a type goes โ€” parameters, variables, interface members.

function apply(fn: (x: number) => number, value: number): number {
  return fn(value);
}

const double = (x: number) => x * 2;
apply(double, 5);                 // 10

Typing callbacks inline vs with aliases:

// Inline
function filter(xs: number[], fn: (x: number) => boolean): number[] {
  return xs.filter(fn);
}

// With a type alias
type NumberPredicate = (x: number) => boolean;
function filter(xs: number[], fn: NumberPredicate): number[] {
  return xs.filter(fn);
}

Both work. Aliases read better when the type is reused or complex.

Optional and rest in function types:

type F1 = (a: string, b?: number) => void;
type F2 = (...xs: string[]) => void;
type F3 = (first: string, ...rest: number[]) => void;

Call signatures in interfaces:

interface Comparator<T> {
  (a: T, b: T): number;
}

An interface with only a call signature is a callable object type. Used for things like comparators and factory functions.

Constructor signatures:

interface Constructor<T> {
  new (...args: unknown[]): T;
}

A type with a new signature. This is how you type a class reference rather than an instance.

Why function types matter: Functions are first-class in JavaScript โ€” passed, returned, stored. Their types need to travel with them. A named function type makes callback signatures readable, reusable, and easy to check. When you’re tempted to write a long inline function type three times, extract an alias.


Function overloads

Sometimes a function’s return type depends on its argument types. Overloads let you write multiple signatures for one implementation.

function double(x: number): number;
function double(x: string): string;
function double(x: number | string): number | string {
  if (typeof x === 'number') return x * 2;
  return x + x;
}

Two overload signatures followed by one implementation signature. Callers see the overloads:

double(5);                        // number
double('hi');                     // string
double(true);                     // โŒ no matching overload

TypeScript picks the matching overload and returns its type. The implementation signature is not visible to callers โ€” it only needs to be compatible with all overloads.

The implementation signature must cover all overloads:

function f(x: number): number;
function f(x: string): string;
function f(x: number | string): number | string {
  // must accept the union of all overload parameters
}

The union covers both. The return type is the union of all overload returns.

Common overload patterns:

Return type narrows on input:

function parse(x: string): string[];
function parse(x: number): number[];
function parse(x: string | number): string[] | number[] {
  return typeof x === 'string' ? x.split('') : [x];
}

Number of arguments decides the return:

function create(value: string): { text: string };
function create(value: string, count: number): { text: string; count: number };
function create(value: string, count?: number) {
  return count === undefined ? { text: value } : { text: value, count };
}

Different argument shapes:

function log(message: string): void;
function log(error: Error): void;
function log(input: string | Error): void {
  console.log(typeof input === 'string' ? input : input.message);
}

Overloads vs union parameters โ€” when to use which:

Use a union when the function does the same thing to any of the types and returns the same shape:

function toStr(x: string | number): string {
  return String(x);
}

Use overloads when the return type depends on which argument was passed:

function wrap(x: string): { s: string };
function wrap(x: number): { n: number };

If overloads and a union are both viable, the union is simpler โ€” use it.

Order matters: TypeScript picks the first matching overload. Put more specific overloads first.

function f(x: string): 's';
function f(x: 'a'): 'a';          // never reached โ€” 'a' matches string first
function f(x: unknown): 's' | 'a' { return 's'; }

Reverse the order to catch the narrower case.

Overloads with generics:

function identity<T>(x: T): T;
function identity(x: unknown): unknown {
  return x;
}

Often a single generic signature is better than multiple overloads.

Why overloads exist: Some functions genuinely have different behavior for different inputs โ€” think document.createElement, querySelector, library builders. A union parameter would blur the relationship between input and output. Overloads preserve the precision: “if you pass a string, you get a string back.” Use them when the input/output relationship is tighter than a union can express.


this parameter

TypeScript lets you type this explicitly as the first parameter โ€” a special position that isn’t a real argument.

function greet(this: { name: string }): string {
  return `Hello, ${this.name}`;
}

const user = { name: 'Alice', greet };
user.greet();                     // โœ…
greet();                          // โŒ this context required

The this parameter is erased at runtime. It only exists for type-checking.

In classes: this is inferred from the class. Explicit typing is rare.

In callbacks: You sometimes need to type this to match a library’s expectations.

interface EventHandler {
  (this: HTMLElement, event: Event): void;
}

this type for polymorphism: this refers to the current instance’s type.

class Builder {
  add(x: number): this {
    return this;
  }
}

class Fluent extends Builder {
  mode(m: string): this {
    return this;
  }
}

new Fluent().add(1).mode('fast');

this as a return type preserves the subclass type through method chains.

Why this matters: JavaScript’s this is dynamic and easy to get wrong. TypeScript lets you declare what this must be, catching calls in the wrong context. For method chains, this as a return type preserves subclass types across .add().mode() calls โ€” something Builder return types would lose.


A full example

A small utility with typed parameters, defaults, rest, overloads, and a never assertion.

// ============================================
// BASIC PARAMS AND RETURN
// ============================================

function formatName(first: string, last: string, middle?: string): string {
  return middle ? `${first} ${middle} ${last}` : `${first} ${last}`;
}

// ============================================
// DEFAULTS AND REST
// ============================================

function log(level = 'INFO', ...messages: string[]): void {
  for (const msg of messages) {
    console.log(`[${level}] ${msg}`);
  }
}

// ============================================
// OVERLOADS
// ============================================

function wrap(value: string): { text: string };
function wrap(value: number): { value: number };
function wrap(value: string | number): { text: string } | { value: number } {
  return typeof value === 'string' ? { text: value } : { value };
}

// ============================================
// NEVER โ€” EXHAUSTIVE CHECK
// ============================================

type Shape =
  | { kind: 'circle'; radius: number }
  | { kind: 'square'; side: number };

function assertNever(x: never): never {
  throw new Error(`Unexpected: ${JSON.stringify(x)}`);
}

function area(s: Shape): number {
  switch (s.kind) {
    case 'circle': return Math.PI * s.radius ** 2;
    case 'square': return s.side ** 2;
    default: return assertNever(s);
  }
}

// ============================================
// USAGE
// ============================================

console.log(formatName('Alice', 'Johnson'));
console.log(formatName('Alice', 'Johnson', 'B.'));

log('INFO', 'started', 'ready');
log('DEBUG', 'value = 42');

const s = wrap('hello');
const n = wrap(42);
console.log(s.text, n.value);

console.log(area({ kind: 'circle', radius: 2 }));
console.log(area({ kind: 'square', side: 3 }));

Every pattern is exercised: optional param (middle?), default (level = 'INFO'), rest (...messages), overloads (wrap), and never for exhaustive checking (assertNever).

Why this shape: It covers the function toolkit in one file. formatName shows optional. log shows default + rest. wrap shows overloads preserving input/output relationships. area shows never catching missing branches. These are the patterns you’ll reuse across every codebase.


Complete Example Session

# ============================================
# PART 1: BASIC PARAMETERS
# ============================================

cat > params.ts << 'EOF'
function greet(name: string, age: number): string {
  return `Hello, ${name}. Age ${age}.`;
}

console.log(greet('Alice', 30));

// โŒ Missing argument
// greet('Alice');

// โŒ Wrong type
// greet('Alice', 'thirty');

// โŒ Too many
// greet('Alice', 30, true);
EOF

npx tsc --noEmit params.ts
# (no errors)

# ============================================
# PART 2: OPTIONAL, DEFAULT, REST
# ============================================

cat > flexible.ts << 'EOF'
function greet(name: string, title?: string): string {
  return title ? `${title} ${name}` : name;
}

function connect(url: string, port = 8080): string {
  return `${url}:${port}`;
}

function sum(...nums: number[]): number {
  return nums.reduce((a, b) => a + b, 0);
}

console.log(greet('Alice'));
console.log(greet('Alice', 'Dr.'));
console.log(connect('http://x'));
console.log(connect('http://x', 3000));
console.log(sum(1, 2, 3, 4, 5));
EOF

npx tsc --noEmit flexible.ts
# (no errors)

# ============================================
# PART 3: RETURN TYPES
# ============================================

cat > returns.ts << 'EOF'
function add(a: number, b: number): number {
  return a + b;
}

function log(msg: string): void {
  console.log(msg);
}

function fail(msg: string): never {
  throw new Error(msg);
}

type Shape = { kind: 'circle' } | { kind: 'square' };

function assertNever(x: never): never {
  throw new Error(`Unexpected: ${JSON.stringify(x)}`);
}

function area(s: Shape): number {
  switch (s.kind) {
    case 'circle': return 1;
    case 'square': return 1;
    default: return assertNever(s);
  }
}

console.log(add(1, 2));
log('done');
console.log(area({ kind: 'circle' }));
EOF

npx tsc --noEmit returns.ts
# (no errors)

# ============================================
# PART 4: FUNCTION TYPES
# ============================================

cat > types.ts << 'EOF'
type Adder = (a: number, b: number) => number;
type Predicate<T> = (value: T) => boolean;

const add: Adder = (a, b) => a + b;
const isEven: Predicate<number> = n => n % 2 === 0;

function filter<T>(xs: T[], pred: Predicate<T>): T[] {
  return xs.filter(pred);
}

console.log(add(1, 2));
console.log(filter([1, 2, 3, 4], isEven));
EOF

npx tsc --noEmit types.ts
# (no errors)

# ============================================
# PART 5: OVERLOADS
# ============================================

cat > overloads.ts << 'EOF'
function double(x: number): number;
function double(x: string): string;
function double(x: number | string): number | string {
  return typeof x === 'number' ? x * 2 : x + x;
}

console.log(double(5));
console.log(double('hi'));

// Overload order matters
function f(x: 'a'): 'a';
function f(x: string): 's';
function f(x: string): 'a' | 's' {
  return x === 'a' ? 'a' : 's';
}

console.log(f('a'));  // 'a' โ€” matches the narrow first
console.log(f('b'));  // 's'
EOF

npx tsc --noEmit overloads.ts
# (no errors)

# ============================================
# PART 6: COMPILE AND RUN
# ============================================

npx tsc params.ts flexible.ts returns.ts types.ts overloads.ts
node params.js
# [ Hello, Alice. Age 30. ]

node flexible.js
# [ Alice ]
# [ Dr. Alice ]
# [ http://x:8080 ]
# [ http://x:3000 ]
# [ 15 ]

node returns.js
# [ 3 ]
# [ done ]
# [ 1 ]

node types.js
# [ 3 ]
# [ [ 2, 4 ] ]

node overloads.js
# [ 10 ]
# [ hihi ]
# [ a ]
# [ s ]

Quick Reference

Parameter Syntax

SyntaxMeaning
x: TRequired parameter
x?: TOptional โ€” may be omitted
x = defaultDefault value โ€” optional
...xs: T[]Rest parameter
...xs: [A, B]Rest with tuple
this: TType the this context

Return Types

SyntaxMeaning
: TExplicit return type
: voidNo useful return
: neverNever returns
: Promise<T>Async โ€” resolves to T
(omitted)Inferred from body

Optional vs | undefined

FormOmittableAccepts undefined
x?: Tโœ…โœ…
x: T | undefinedโŒโœ…

Function Type Syntax

FormMeaning
(x: number) => stringFunction type
type F = (x: number) => stringAlias
interface C { (a: T, b: T): number }Call signature
new (...args: T[]) => UConstructor signature
<T>(x: T) => TGeneric function

Overload Rules

RuleValue
Overload signaturesVisible to callers
Implementation signatureNot visible
OrderFirst match wins โ€” specific first
Implementation must coverUnion of all overloads
Generic vs overloadsPrefer a generic if possible

void vs never vs undefined

TypeMeaning
voidNo useful return โ€” value ignored
neverNever returns โ€” throws or loops
undefinedReturns the value undefined

void Callback Exception

FunctionAssignable to () => void?
() => voidโœ…
() => numberโœ… (return ignored)
() => undefinedโœ…
() => neverโœ…

Common Patterns

PatternSignature
Comparator(a: T, b: T) => number
Predicate(x: T) => boolean
Mapper(x: T) => U
Factory() => T
Async factory() => Promise<T>
Reducer(acc: U, x: T) => U
Handler(event: Event) => void

Overload vs Union

Use overloads whenUse a union when
Return type varies with inputSame return for all
Argument count variesSame shape
Distinct behavior per typeCommon behavior
Precision mattersSimplicity wins

Best Practices

โœ… Do This:

// Annotate parameters โ€” always
function greet(name: string): string { }                // โœ…

// Use defaults for optional
function connect(url: string, port = 8080) { }           // โœ…

// Use rest for variadic
function sum(...nums: number[]): number { }              // โœ…

// Type callback parameters
xs.filter((x: number) => x > 0);                         // โœ…

// Extract reusable function types
type Handler<T> = (value: T) => void;                    // โœ…

// Use `never` for exhaustive checks
function assertNever(x: never): never { throw x; }       // โœ…

// Put specific overloads first
function f(x: 'a'): 'a';
function f(x: string): 'string';                         // โœ…

// Explicit return type on public APIs
export function fetchUser(id: number): Promise<User> { } // โœ…

// Type `this` in standalone functions
function greet(this: Ctx) { }                            // โœ…

โŒ Don’t Do This:

// Don't leave parameters untyped
function greet(name) { }                                 // โŒ implicit any

// Don't use `| undefined` when `?` is cleaner
function f(x: string | undefined) { f(); }               // โš ๏ธ  use x?: string

// Don't put optional before required
function f(a?: string, b: number) { }                    // โŒ

// Don't use union when overloads are needed
function double(x: number | string): number | string { } // โš ๏ธ  loses precision

// Don't mix overload and union return carelessly
function f(x: string | number): string;                  // โš ๏ธ  may not fit all branches

// Don't rely on implementation signature at call sites
// (It's not visible)                                    // โŒ

// Don't overuse overloads
function f(x: unknown): unknown { }                      // โš ๏ธ  single generic better

// Don't forget to type `this` in callbacks
[1].forEach(function () { this.x; });                    // โš ๏ธ  this may be undefined

Common Pitfalls

PitfallProblemSolution
Optional before requiredCompile errorReorder or use | undefined
Confusing ? with | undefinedDifferent call rulesKnow which you need
Forgetting a return typeInference may widenAdd explicit return
Overload implementation mismatchCompile errorCover the union of overloads
Wrong overload orderWrong matchSpecific overloads first
Overusing overloadsNoisePrefer generics/unions
void return usedValue inaccessibleUse the actual type
never never checkedMissing branchAdd assertNever default
Missing default typenever[] for empty arrayAnnotate
Untyped restany[]Annotate as T[]

Real-World Examples

1. Typed function

function add(a: number, b: number): number { return a + b; }

2. Optional parameter

function greet(name: string, title?: string): string { }

3. Default parameter

function connect(url: string, port = 8080): string { }

4. Rest parameter

function sum(...nums: number[]): number { }

5. Rest with tuple

function pair(...args: [string, number]): void { }

6. Void return

function log(msg: string): void { console.log(msg); }

7. Never return

function fail(msg: string): never { throw new Error(msg); }

8. Exhaustive check

function assertNever(x: never): never { throw x; }

9. Async return

async function fetchUser(id: number): Promise<User> { }

10. Function type alias

type Handler = (event: Event) => void;

11. Generic function type

type Mapper<T, U> = (x: T) => U;

12. Call signature interface

interface Comparator<T> { (a: T, b: T): number; }

13. Constructor type

type Ctor<T> = new (...args: unknown[]) => T;

14. Function overload

function double(x: number): number;
function double(x: string): string;
function double(x: number | string): number | string { }

15. Overload for optional arg

function create(v: string): { v: string };
function create(v: string, n: number): { v: string; n: number };
function create(v: string, n?: number) { return n === undefined ? { v } : { v, n }; }

16. Type this

function greet(this: { name: string }): string {
  return `Hello, ${this.name}`;
}

17. Polymorphic this

class Builder {
  add(x: number): this { return this; }
}

18. Callback param type

function filter<T>(xs: T[], pred: (x: T) => boolean): T[] {
  return xs.filter(pred);
}

19. Function returning union

function parse(s: string): number | null { }

20. Readonly rest

function sum(...nums: readonly number[]): number {
  return nums.reduce((a, b) => a + b, 0);
}

Visual: Parameter Shapes

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Required                                    โ”‚
โ”‚  function f(a: string, b: number) { }        โ”‚
โ”‚                                              โ”‚
โ”‚  f('x', 1)      โœ…                           โ”‚
โ”‚  f('x')         โŒ missing                   โ”‚
โ”‚  f('x', 1, 2)   โŒ too many                  โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Optional โ€” ?                                โ”‚
โ”‚  function f(a: string, b?: number) { }       โ”‚
โ”‚                                              โ”‚
โ”‚  f('x')         โœ…                           โ”‚
โ”‚  f('x', 1)      โœ…                           โ”‚
โ”‚  f('x', 'a')    โŒ wrong type                โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Default                                     โ”‚
โ”‚  function f(a: string, b = 1) { }            โ”‚
โ”‚                                              โ”‚
โ”‚  f('x')         โœ… b = 1                     โ”‚
โ”‚  f('x', 2)      โœ… b = 2                     โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Rest                                        โ”‚
โ”‚  function f(...xs: number[]) { }             โ”‚
โ”‚                                              โ”‚
โ”‚  f()            โœ…                           โ”‚
โ”‚  f(1, 2, 3)     โœ…                           โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Visual: Return Type Decisions

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Return type meanings                        โ”‚
โ”‚                                              โ”‚
โ”‚  T            โ€” returns T                    โ”‚
โ”‚  void         โ€” return value ignored         โ”‚
โ”‚  never        โ€” never completes              โ”‚
โ”‚  Promise<T>   โ€” async, resolves to T         โ”‚
โ”‚  undefined    โ€” returns undefined            โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Visual: Overload Flow

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Call site: double('hi')                     โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                  โ”‚
                  โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Overload signatures (visible to caller):    โ”‚
โ”‚                                              โ”‚
โ”‚  1. (x: number) => number                    โ”‚
โ”‚  2. (x: string) => string                    โ”‚
โ”‚                                              โ”‚
โ”‚  Match โ†’ #2                                  โ”‚
โ”‚  Return type โ†’ string                        โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                  โ”‚
                  โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Implementation:                             โ”‚
โ”‚                                              โ”‚
โ”‚  (x: number | string) => number | string     โ”‚
โ”‚                                              โ”‚
โ”‚  Runs at runtime                             โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Visual: void Exception

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  type Callback = () => void;                 โ”‚
โ”‚                                              โ”‚
โ”‚  const a: Callback = () => undefined;   โœ…   โ”‚
โ”‚  const b: Callback = () => 42;          โœ…   โ”‚
โ”‚  const c: Callback = () => { throw 1; } โœ…   โ”‚
โ”‚  const d: Callback = () => 'hello';     โœ…   โ”‚
โ”‚                                              โ”‚
โ”‚  Return value is simply ignored.             โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Visual: never for Exhaustive Checks

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  type Shape =                                โ”‚
โ”‚    | { kind: 'circle' }                      โ”‚
โ”‚    | { kind: 'square' };                     โ”‚
โ”‚                                              โ”‚
โ”‚  function area(s: Shape) {                   โ”‚
โ”‚    switch (s.kind) {                         โ”‚
โ”‚      case 'circle': return 1;                โ”‚
โ”‚      case 'square': return 2;                โ”‚
โ”‚      default:                                โ”‚
โ”‚        return assertNever(s);                โ”‚
โ”‚        // s is never here โ€” all cases done   โ”‚
โ”‚    }                                         โ”‚
โ”‚  }                                           โ”‚
โ”‚                                              โ”‚
โ”‚  Add a new shape โ†’ default fires โ†’ error     โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Visual: this Parameter

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  function greet(this: { name: string }) {    โ”‚
โ”‚    return `Hello, ${this.name}`;             โ”‚
โ”‚  }                                           โ”‚
โ”‚                                              โ”‚
โ”‚  const user = { name: 'Alice', greet };      โ”‚
โ”‚  user.greet();    โœ… this = user             โ”‚
โ”‚                                              โ”‚
โ”‚  greet();         โŒ this missing            โ”‚
โ”‚                                              โ”‚
โ”‚  The this parameter is erased at runtime.    โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Visual: Function Type Alias

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  type Handler<T> = (value: T) => void;       โ”‚
โ”‚                                              โ”‚
โ”‚  function on<T>(event: string, fn: Handler<T>) {โ”‚
โ”‚    // ...                                    โ”‚
โ”‚  }                                           โ”‚
โ”‚                                              โ”‚
โ”‚  on<number>('click', n => console.log(n));   โ”‚
โ”‚  on<string>('input', s => console.log(s));   โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Summary

ConceptMeaning
Parameter typesRequired, annotated
?Optional parameter
= defaultDefault value
...rest: T[]Variadic parameter
...rest: [A, B]Tuple-typed rest
Return typeInferred or explicit
voidReturn ignored
neverNever completes
Promise<T>Async return
Function type(args) => return
OverloadMultiple signatures, one implementation
this parameterTypes the this context
assertNeverExhaustive check helper

Key takeaways:

  • Every parameter is required unless marked optional (?) or given a default
  • Optional parameters must come last; ? differs from | undefined
  • Default parameters supply the type and trigger on undefined
  • Rest parameters collect trailing arguments; can be typed as T[] or a tuple
  • Return types are inferred, but explicit returns are better for public APIs
  • void means “no useful return” โ€” functions returning values are still assignable to void callbacks
  • never means “never returns” โ€” used for throws and exhaustive checks
  • Function type expressions โ€” (x: T) => U โ€” travel with callbacks and get extracted into aliases
  • Overloads provide multiple signatures for one implementation โ€” use when the return type depends on the input type
  • Overload order matters โ€” specific signatures first
  • Union parameters are simpler than overloads when the return shape is uniform
  • The this parameter types the context and is erased at runtime
  • Use assertNever in a default branch to catch missing union cases

Remember: Functions are where types meet the runtime. Annotate parameters, decide on return types, and use defaults, optional, and rest for flexibility. Reach for overloads when a single function’s return depends on its input โ€” otherwise a union is simpler. Use never to catch missing branches, void for side-effect functions, and function type aliases for callbacks that travel. Get these right and every call site becomes a checked contract.


Stop using slow, ad-bloated tool sites! ๐Ÿคฎ

๐Ÿ”Ž Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
โœ… Finance (Mortgage, Interest, Inflation)
โœ… Tech (Base64, JSON, Dev Suite, IP)
โœ… Health (BMI, BMR, TDEE)
โœ… Productivity (Timer, Workspace, QR)

โšก๏ธ Fast & Private
๐Ÿ”’ No data leaves your device
๐Ÿ’Ž 100% Free

๐Ÿ”— Use it now: https://tools.kandz.me
๐Ÿ”– Bookmark itโ€”youโ€™ll need it later!