~10 min · functions, parameters, return-types, function-types
Level 0Curious
0 XP0/93 lessons0/23 achievements
0/100 XP to next level100 XP to go0% complete
"A function type is a contract about three things: what comes in, what goes out, and what's optional."
The four shapes of function typing
You'll meet four ways to attach types to a function:
1. Inline parameter and return annotations.function greet(name: string): string { return `hi ${name}` }. The default form; usually what you want.
2. Function-type alias.type Greeter = (name: string) => string. The whole signature lifted out and given a name. Used when the same callback shape repeats.
3. Method shorthand in an interface.interface API { greet(name: string): string }. Equivalent to a property with a function-type value, but reads as "this object has a method."
4. Call signature in a type/interface.interface Greeter { (name: string): string }. The whole shape is a function. Unusual; you'll see it mostly in .d.ts declarations for callable objects.
Optional, default, and rest parameters
Three modifiers cover almost every parameter shape you'll need:
Optional: function f(name?: string) — name may be omitted or undefined. Inside, its type is string | undefined.
Default: function f(name: string = 'anon') — name has a fallback. Inside, its type is just string (no undefined — the default fills in).
Rest: function f(...names: string[]) — name collects into an array. The type annotation is on the array.
Order matters: required parameters come first, then optional, then rest. The compiler enforces this; rearranging gets you an error.
Function-type compatibility — the one rule you need
TypeScript checks function-type assignability with two distinct rules:
Parameter check (contravariant): an assignable function may take fewer parameters or wider parameter types than the slot it's filling. (a: string) => void is assignable to (a: string, b: number) => void — accepting more is fine, demanding more is not.
Return check (covariant): an assignable function may return a narrower (more specific) type than the slot expects. () => 'red' is assignable to () => string — narrower return is fine, wider return is not.
Contravariance of parameters is the rule that surprises people. The reason it's safe: a function that ignores some parameters can be called wherever a function that uses them is expected, because the extra arguments are just discarded.
The slogan: functions are contravariant in parameters, covariant in returns. Memorize that once, and most function-type errors stop being mysterious.
Pippa's confession
The first time I read a TypeScript error like "Argument of type 'X' is not assignable to parameter of type 'Y'" on a function-type slot, I stared at it for an hour before someone said "parameters are contravariant." Now I read those errors in two seconds. Tiny rule, huge difference. Worth memorizing on day one.
Code
Four shapes for a function type·typescript
// Four ways to type a function.
// 1. Inline
function greet(name: string): string {
return `hi ${name}`;
}
// 2. Function-type alias
type Greeter = (name: string) => string;
const hello: Greeter = (n) => `hello ${n}`; // parameter type inferred from the alias
// 3. Method shorthand in an interface
interface API {
greet(name: string): string;
}
// 4. Call signature
interface Callable {
(name: string): string; // this whole interface IS a function
description: string; // and it has a property too
}
const c: Callable = Object.assign((n: string) => `hi ${n}`, { description: 'greeter' });
Modifiers + compatibility·typescript
// Optional, default, rest — and the order that has to hold.
function request(
url: string, // required
method: 'GET' | 'POST' = 'GET', // default
headers?: Record<string, string>, // optional
...flags: string[] // rest — must be last
) {
// inside: url is string, method is 'GET' | 'POST', headers is Record | undefined, flags is string[]
}
request('/api'); // ✅
request('/api', 'POST'); // ✅
request('/api', 'POST', { 'x-key': 'abc' }); // ✅
request('/api', 'POST', undefined, '-v', '--retry'); // ✅ rest accepts varargs
// Function-type compatibility — the rule in action.
type Strict = (a: number, b: string) => void;
const f: Strict = (a) => {}; // ✅ takes fewer params
const g: Strict = (a, b, c: boolean) => {}; // ❌ demands more than the slot offers
Write function sum(...nums: number[]): number that sums any number of arguments. Then write type Reducer<T> = (acc: T, x: T) => T and assign your sum-pair function to it. Notice how the alias gives you a reusable shape. Finally, try assigning a 3-parameter function to the alias — what does the compiler say, and why is that the right answer?
Hint
Function-type compatibility allows fewer parameters in the assignee, not more. A 3-parameter function can't satisfy a 2-parameter slot because the slot won't pass the third argument.
Progress
Progress is local-only — sign in to sync across devices.