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 thanfunction 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 ofTs. A rest parameter typed...args: [A, B]accepts exactlyAthenB. 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
voidis different fromundefined:voidis a signal โ “the caller shouldn’t use this return value.”undefinedis a real value the caller can check. For callbacks,voidaccepts functions that return anything, because the return is ignored. If callbacks were typedundefined, every callback would need an explicitreturn 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
thismatters: JavaScript’sthisis dynamic and easy to get wrong. TypeScript lets you declare whatthismust be, catching calls in the wrong context. For method chains,thisas a return type preserves subclass types across.add().mode()calls โ somethingBuilderreturn 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.
formatNameshows optional.logshows default + rest.wrapshows overloads preserving input/output relationships.areashowsnevercatching 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
| Syntax | Meaning |
|---|---|
x: T | Required parameter |
x?: T | Optional โ may be omitted |
x = default | Default value โ optional |
...xs: T[] | Rest parameter |
...xs: [A, B] | Rest with tuple |
this: T | Type the this context |
Return Types
| Syntax | Meaning |
|---|---|
: T | Explicit return type |
: void | No useful return |
: never | Never returns |
: Promise<T> | Async โ resolves to T |
| (omitted) | Inferred from body |
Optional vs | undefined
| Form | Omittable | Accepts undefined |
|---|---|---|
x?: T | โ | โ |
x: T | undefined | โ | โ |
Function Type Syntax
| Form | Meaning |
|---|---|
(x: number) => string | Function type |
type F = (x: number) => string | Alias |
interface C { (a: T, b: T): number } | Call signature |
new (...args: T[]) => U | Constructor signature |
<T>(x: T) => T | Generic function |
Overload Rules
| Rule | Value |
|---|---|
| Overload signatures | Visible to callers |
| Implementation signature | Not visible |
| Order | First match wins โ specific first |
| Implementation must cover | Union of all overloads |
| Generic vs overloads | Prefer a generic if possible |
void vs never vs undefined
| Type | Meaning |
|---|---|
void | No useful return โ value ignored |
never | Never returns โ throws or loops |
undefined | Returns the value undefined |
void Callback Exception
| Function | Assignable to () => void? |
|---|---|
() => void | โ |
() => number | โ (return ignored) |
() => undefined | โ |
() => never | โ |
Common Patterns
| Pattern | Signature |
|---|---|
| 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 when | Use a union when |
|---|---|
| Return type varies with input | Same return for all |
| Argument count varies | Same shape |
| Distinct behavior per type | Common behavior |
| Precision matters | Simplicity 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
| Pitfall | Problem | Solution |
|---|---|---|
| Optional before required | Compile error | Reorder or use | undefined |
Confusing ? with | undefined | Different call rules | Know which you need |
| Forgetting a return type | Inference may widen | Add explicit return |
| Overload implementation mismatch | Compile error | Cover the union of overloads |
| Wrong overload order | Wrong match | Specific overloads first |
| Overusing overloads | Noise | Prefer generics/unions |
void return used | Value inaccessible | Use the actual type |
never never checked | Missing branch | Add assertNever default |
| Missing default type | never[] for empty array | Annotate |
| Untyped rest | any[] | 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
| Concept | Meaning |
|---|---|
| Parameter types | Required, annotated |
? | Optional parameter |
= default | Default value |
...rest: T[] | Variadic parameter |
...rest: [A, B] | Tuple-typed rest |
| Return type | Inferred or explicit |
void | Return ignored |
never | Never completes |
Promise<T> | Async return |
| Function type | (args) => return |
| Overload | Multiple signatures, one implementation |
this parameter | Types the this context |
assertNever | Exhaustive 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
voidmeans “no useful return” โ functions returning values are still assignable tovoidcallbacksnevermeans “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
thisparameter types the context and is erased at runtime - Use
assertNeverin adefaultbranch 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!