learn
TypeScript

Functions

Parameters, returns, optional and default values, and callbacks.

Function signatures are contracts callers cannot wiggle out of: how many arguments, in what order, of which types, and what comes back. Interviewers test whether you read a signature the way the compiler does — positionally, not by name.

Real-life situation

A payment helper takes an amount and a currency. Someone calls it as chargeCard("USD", 25) — names swapped. JavaScript charges twenty-five "USD"s at runtime; TypeScript rejects the call before review. Add an optional tip with a default, and the signature documents itself better than any comment.

Visual

Function signature
BrowserAPIRealtimeCacheDatabaseShareddurableephemeral

Try it

Payment called in the wrong order

Checked with TypeScript 6.0.3, strict mode

typescriptTab moves to the next control
function chargeCard(amount: number, currency: string): string {
const label = amount + " " + currency;
return "charged " + label;
}
function withTip(amount: number, tip: number = 0): number {
return amount + tip;
}
function runTwice(task: () => void): void {
task();
task();
}
console.log(chargeCard(25, "USD"));
console.log(withTip(20));
console.log(withTip(20, 5));
runTwice(function () {
console.log("receipt printed");
});
console.log(chargeCard("USD", 25));

Compiler not loaded. Check or Run loads it here (about 9 MB) — nothing downloads until you do.

Runner helpers

sleep(ms) waits; fakeFetch(url) returns deterministic sample data. TypeScript is stripped before running; imports and exports are not available in the sandbox.

Try fixing the last line to chargeCard(25, "USD") — the Types tab goes clean. Then change runTwice's parameter to (code: number) => void and watch the anonymous function fail: callbacks are checked against the parameter they are passed to, which is why inline handlers usually need no annotations at all.

Optional parameters (tip?: number) may be skipped; default parameters (tip = 0) fill themselves in. Overloads — several signatures, one implementation — appear in library definitions; you will read them far more often than you write them. For function values, see also closures in the JavaScript topic.

Will it compile? — decide first, then reveal.

check.ts
function total(price: number, tax: number): number {
return price + tax;
}
const bill: number = total(10, "5");
console.log(bill);

Interview questions

How do you type a function's parameters and return value?basic

You annotate each parameter and the result, like function add(a: number, b: number): number. Parameters need explicit types because there is nothing to infer them from, while the return annotation documents the contract for callers. The compiler then rejects wrong-typed arguments and checks that every code path returns a compatible value. Keep the annotations at the boundary and let locals inside the body infer.

Interviewers also ask

  • Can you leave the return type off?
How do optional and default parameters work?basic

You mark a trailing parameter name?: string to let callers skip it, or give it a default like name = 'guest' which makes it optional automatically. Optional parameters read as possibly undefined inside the body, so handle that case. Keep required parameters first — a required parameter after an optional one confuses callers and needs an explicit undefined argument. Defaults also document the common case right in the signature.

Interviewers also ask

  • Where must optional parameters appear in the list?
How do you type an arrow function or callback?basic

You annotate it inline like (user: User) => user.name, and the return type is usually inferred. When passing a callback directly, you can often omit annotations entirely — the expected function type provides context, so (u) => u.name is checked against the parameter's declared signature. This contextual typing keeps event handlers and array callbacks quiet. Add explicit annotations only when the callback stands alone without context.

Interviewers also ask

  • What is contextual typing for inline callbacks?
How do function overloads work?intermediate

You write several call signatures above one implementation, each describing a supported way to call the function. Callers see only the overload list, while the implementation signature stays hidden and must accept them all. Use overloads when one function genuinely accepts different shapes, like pad(s: string) versus pad(n: number). If the variants can be expressed as one union or optional-parameter signature, prefer that — it is shorter and easier to maintain.

Interviewers also ask

  • When is a union signature better than overloads?
How do you type a function that takes no useful result, like a click handler?basic

You annotate the return as void, like (e: MouseEvent) => void, to show the result is ignored. Handlers, loggers, and forEach callbacks all use this shape. Callers can pass functions that happen to return something — TypeScript allows that — but nobody should read the value. Reserve never for functions that never return at all, such as ones that always throw.

Interviewers also ask

  • Can a void callback return a value?
How do rest parameters work with types?intermediate

You collect trailing arguments with ...rest: string[], which reads inside the body as a normal array. Callers can pass any number of extra arguments and each is checked against the element type. Tuples make rest precise: ... args: [string, number?] enforces the exact sequence. Use rest for wrappers like loggers and forwarders where the arity varies, and prefer fixed parameters when the count is known.

Interviewers also ask

  • How would you type a fixed two-argument rest pair?
How do you type an async function?intermediate

You annotate the unwrapped value inside Promise, like async function load(): Promise<User>. The async keyword wraps your return and throw into the promise for you, so you write the body as if it were synchronous. Callers must await or .then the result. For error paths, let the promise reject with an Error rather than resolving a union — it keeps try/catch at the call site natural.

Interviewers also ask

  • Do you write Promise in the return annotation of an async function?
How do you write a reusable function type?basic

You alias the signature, like type Handler = (id: string) => void, then reuse Handler for every matching function. It keeps callbacks consistent across components and makes refactors one-line changes. For methods inside objects, you can write the shorthand { onSave(id: string): void }. Name the alias after its role, not its shape, so readers learn intent from the name alone.

Interviewers also ask

  • How do you type a method inside an object type?

Key takeaways

  • Arguments match positionally: order and count are part of the contract.
  • Defaults document themselves; callbacks inherit types from context.
  • void means "ignore the result", not "returns nothing usable".

References

On this page