skip to content

Optional, Default & Rest Parameters

Marking a parameter optional, giving it a default, or collecting the tail into a rest parameter — and how each changes the inferred type. Interviewers follow up with why a function taking fewer parameters is assignable where more are expected, which surprises most candidates.

part ofTypeScriptoverview, primer and where to startread it →
on this pageshow

questions

5

In TypeScript, what does the question mark do in `function log(msg: string, level?: string)`, and what rules govern where such a parameter may appear?

level: juniorimportance: must knowfreq 70%

answer

  1. caller may leave it out
  2. type gains undefined inside
  3. position matters in the list
  4. cannot pair with an initializer
  5. erased — no runtime arity check

basics

~20 s

A question mark makes the parameter optional: callers may omit it, and inside the body its type includes undefined. Optional parameters must follow all required ones, and a parameter cannot have both a question mark and a default value.

solid answer

~40 s

`level?: string` marks the parameter optional. At the call site the compiler's arity check accepts `log('hi')` as well as `log('hi', 'warn')`, and it also accepts `log('hi', undefined)`. Inside the function body, with `strictNullChecks` on, `level` has type `string | undefined`, so you must narrow it — a truthiness check, an `=== undefined` check, or a `??` fallback — before using it as a string. Two syntax rules apply: every optional parameter has to come after all required ones, so `function f(a?: string, b: number)` is rejected, and you cannot combine `?` with an initializer — `function f(a?: string = 'x')` is an error, because a default already implies optionality. The `?` is pure type-layer syntax: it is erased, and the emitted JavaScript is just `function log(msg, level) { ... }`.

go deeper

for a junior

Know that ? lets the caller omit the argument, that the value is then undefined, and that optional parameters come last. Be ready to add a ?? fallback when the compiler complains the value may be undefined.

for a middle

Explain the mechanics: the parameter's declared type becomes T | undefined under strictNullChecks, the arity check is compile-time only, and ? plus an initializer is a syntax error because a default already implies optionality.

for a senior

Show judgment about when optionality belongs in the signature at all. Discuss what happens when values arrive from untyped or external code, and why an optional parameter is a caller-side convenience rather than any kind of validation.

for a principal

Own the API-shape tradeoff: a growing tail of optional parameters is a signal to move to an options object, which keeps call sites readable and lets fields evolve independently. Weigh that against the churn of changing a widely-used signature.

## What the question mark actually declares In a TypeScript parameter list, `?` after the parameter name declares that **callers may omit the argument**. It changes two things: the *minimum arity* the compiler will accept at call sites, and the *type* the parameter has inside the body. ```ts function log(msg: string, level?: string) { // level: string | undefined } log('started'); // ok — one argument log('started', 'info'); // ok — two arguments log('started', undefined); // ok — undefined is an acceptable value ``` With `strictNullChecks` enabled (which `strict` turns on), the declared type of `level` is `string | undefined`. That is the whole point: the compiler refuses to let you treat a possibly-missing argument as if it were always there. ```ts function log(msg: string, level?: string) { console.log(level.toUpperCase(), msg); // error: 'level' is possibly 'undefined' } ``` You fix it by narrowing or by supplying a fallback: ```ts function log(msg: string, level?: string) { const l = level ?? 'info'; // l: string if (level !== undefined) { level.toUpperCase(); // narrowed to string here } } ``` ## Ordering: optional parameters go last A required parameter cannot follow an optional one. The reason is positional: arguments are matched by position, so if an earlier slot could be skipped there would be no way to tell which value landed where. ```ts function bad(a?: string, b: number) {} // error: a required parameter cannot follow an optional parameter function good(a: string, b?: number) {} // fine ``` Several optional parameters may follow each other, and callers then omit them from the right: `function f(a: string, b?: number, c?: boolean)` accepts one, two, or three arguments, but there is no syntax for "skip `b`, pass `c`" other than passing `undefined` explicitly for `b`. ## `?` and a default are mutually exclusive ```ts function f(a?: number = 1) {} // error: parameter cannot have question mark and initializer function f2(a = 1) {} // fine — the initializer already makes it optional ``` An initializer makes the parameter omittable *and* removes `undefined` from its type inside the body, so writing `?` on top of it would be contradictory. ## The arity check is compile-time only This is the erasure rule that organises all of TypeScript: none of this survives to runtime. The emitted JavaScript is ```js function log(msg, level) { /* ... */ } ``` There is no check that a caller supplied the right number of arguments — plain JavaScript has always let you call any function with any number of arguments and binds the missing ones to `undefined`. The `?` is a *promise the compiler enforces on the callers it can see*. Code that reaches the function from untyped JavaScript, a `any`-typed value, or a JSON payload can pass anything at all, so an optional parameter is not a validation mechanism. ## In declaration output and in function types The same `?` syntax appears in function *type* positions and in emitted `.d.ts` files: ```ts type Logger = (msg: string, level?: string) => void; const l: Logger = (m) => console.log(m); // fine — see arity assignability ``` So when you read a signature with a `?`, you are reading the same contract whether it came from your source or from a library's declaration file. ## Optional parameter vs `| undefined` — a real distinction Declaring `level?: string` is *not* the same as declaring `level: string | undefined`. Both permit `undefined` as a value, but only the first lets the caller leave the argument out entirely; the second still requires an argument in the list. That difference is the usual follow-up to this question. ## Common mistakes - Assuming the parameter is still `string` inside the body. Without narrowing, the compiler flags every use. - Writing `?` on a middle parameter and then adding a required one after it. - Believing the arity rule is enforced at runtime, or that omitting an argument produces anything other than `undefined`. - Reaching for `?` when the API really wants an explicit choice from the caller — if forgetting the argument is a bug, make it required.

  • How would you let a caller skip a middle parameter while still passing the last one?
    You cannot skip it positionally — the caller has to pass `undefined` explicitly in that slot, as in `f('a', undefined, true)`. If that reads badly, the signature is telling you to switch to a single options object, where each field can be omitted independently and named at the call site.
  • Does adding `?` to a parameter of an already-published function signature break existing callers?
    No — every call that previously supplied the argument still type-checks, since the parameter's type still accepts the value it was given. Making a required parameter optional is a widening change on the caller side. Going the other way, removing the `?`, breaks every caller that omitted it.
  • If `?` is erased, what stops a JavaScript caller from passing the wrong number of arguments?
    Nothing. The arity check exists only where the compiler can see the call. Untyped JavaScript, an `any`-typed reference, or a dynamic dispatch can pass any number of arguments, and the extras are silently ignored while the missing ones are `undefined`. Validate at trust boundaries rather than relying on the signature.

saying these in an interview costs you the question

  • Claims the parameter's type stays string inside the body
  • Thinks TypeScript checks argument count at runtime
  • Puts a required parameter after an optional one
  • Writes both a question mark and a default value
  • Says optional and `| undefined` are interchangeable at call sites

context

open as a page

In TypeScript, why is a function that takes fewer parameters assignable where one taking more is expected, and what bug can that allow through?

level: seniorimportance: must knowfreq 52%

basics

~20 s

A function that ignores trailing parameters can safely stand in for one that receives them, so TypeScript allows fewer parameters but never more. The risk is that a callback silently receives extra arguments it does not expect, which then land in a parameter that has a default.

open as a page

In TypeScript, what does giving a parameter a default value do to the function's type — what is inferred for `mode` in `function run(mode = 'fast') {}`, and is the parameter optional?

level: middleimportance: should knowfreq 58%

basics

~20 s

A default makes the parameter omittable and lets the compiler infer its type from the initializer. mode is inferred as string, not the literal 'fast', because inference widens literals in mutable positions. Inside the body the type excludes undefined.

open as a page

In TypeScript, what is the difference between declaring a function parameter as `x?: number` and declaring it as `x: number | undefined`?

level: middleimportance: should knowfreq 55%

basics

~20 s

Both allow the value undefined, but only x?: number lets the caller omit the argument. With x: number | undefined the argument is still required, so the caller must write undefined explicitly. Inside the body both have type number | undefined.

open as a page

In TypeScript, how do you type a rest parameter, and what changes when you type it as a tuple instead of an array?

level: middleimportance: should knowfreq 42%

basics

~20 s

A rest parameter must be typed as an array or a tuple, and must be the last parameter. An array type such as ...args: number[] accepts any number of arguments; a tuple type such as ...args: [x: number, y: number] pins the exact arity and names each position.

open as a page