skip to content

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%

answer

  1. one is about arity, one about the value
  2. can the caller leave it out?
  3. f() versus f(undefined)
  4. identical inside the body
  5. forces the call site to state a choice

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.

solid answer

~40 s

The difference is arity, not the value type. `x?: number` changes the minimum number of arguments the compiler accepts, so `f()` and `f(undefined)` both type-check. `x: number | undefined` leaves the parameter required: `f(undefined)` is fine, but `f()` fails with "Expected 1 arguments, but got 0". Inside the body, under `strictNullChecks`, the two are identical — both are `number | undefined` and both need narrowing. So the choice is a deliberate API decision: use `?` when omitting the argument is a normal, meaningful call, and use `| undefined` when you want the caller to state explicitly that they have nothing to pass, which is useful for a value that must be threaded through rather than forgotten.

go deeper

for a junior

Remember the one-line difference: ? lets the caller drop the argument entirely, while | undefined still requires an argument that may be the value undefined.

for a middle

Explain that ? relaxes the arity check while the union only widens the type, show the f() versus f(undefined) split, and note that the body sees T | undefined either way.

for a senior

Argue the API consequence: the required union forces every call site to make a visible decision, which is what you want for a value being threaded through a system where forgetting it is a real bug.

for a principal

Set the convention for a codebase and say why — where the stricter union is worth the call-site noise, when a trailing run of optionals should become an options object, and how such signature changes ripple through consumers.

## Two things that look the same and are not A parameter declared with `?` and a parameter declared with `| undefined` both accept `undefined` as a value. What separates them is whether the *argument itself* may be left out of the call. ```ts function a(x?: number) {} function b(x: number | undefined) {} a(); // ok a(undefined); // ok a(1); // ok b(undefined); // ok b(1); // ok b(); // error: Expected 1 arguments, but got 0 ``` The `?` relaxes the compiler's **arity check**; the union only widens the **type**. An optional parameter implicitly gets `undefined` added to its type as well, which is why the first three calls all pass. ## Inside the body they are indistinguishable With `strictNullChecks` on, both parameters have the type `number | undefined`, and both require narrowing before use: ```ts function a(x?: number) { x.toFixed(2); // error: 'x' is possibly 'undefined' if (x !== undefined) x.toFixed(2); // ok } ``` So the distinction buys you nothing about safety inside the function. It is entirely about what you demand of callers. ## Why you would deliberately require the argument The union form is the stricter contract, and that strictness is the reason to reach for it. Consider a function that forwards a value onward: ```ts function createUser(name: string, invitedBy: string | undefined) { /* ... */ } ``` Every call site must now say something about `invitedBy`. A caller that genuinely has no inviter writes `createUser(name, undefined)`, which reads as a decision. If the parameter were `invitedBy?: string`, a caller who simply *forgot* to thread the value through produces the same silent result as a caller who deliberately had nothing — and the compiler cannot tell the two apart. When a later refactor adds an inviter to some flow, only the union form forces you to revisit every call. That is the general rule: use `?` when omission is a normal, meaningful call the API endorses; use `| undefined` when omission is more likely a mistake than an intent. ## What each looks like in a function type Both forms are expressible in function type expressions and both survive into emitted declaration files: ```ts type A = (x?: number) => void; type B = (x: number | undefined) => void; const f: A = (x) => {}; const g: B = f; // ok — A accepts every call B accepts ``` An optional parameter is the *more permissive* signature, so a value of type `A` is usable where `B` is expected. The reverse is not true: a function that requires an argument cannot stand in where callers may omit it — assigning `B` to `A` fails, because an `A` caller is allowed to call with zero arguments. ## Both are erased Neither form emits anything. The compiled JavaScript for both is `function f(x) {}`, and a caller reaching the function from untyped code can pass zero, one, or five arguments regardless of the declaration. The whole distinction lives in the checker. ## The property analogy — and why it is only an analogy The `?` marker also appears on object properties, where it likewise means "may be absent". Do not carry assumptions across: for parameters, the difference is purely about how many arguments a call must supply, and there is no notion of a parameter being "present but undefined" versus "absent" once the call is running — in both cases the binding is `undefined`. ## Choosing in practice - Optional (`?`) for genuinely optional configuration: a formatting hint, an abort signal, a comparator. - Required union (`| undefined`) for a value that is always conceptually part of the call, where the answer may just be "none" — a nullable id being forwarded, a parent reference, an override that must be considered. - If you find yourself with three or four trailing optionals, that is a signal to take a single options object instead, where each field is independently omittable and named at the call site. ## Common mistakes - Saying the two are interchangeable. They differ at every call site. - Expecting `x?: number` to reject `f(undefined)`. It does not; optionality includes the explicit `undefined` argument. - Expecting one of them to make narrowing unnecessary inside the body. Neither does. - Assuming `| undefined` behaves any differently when `strictNullChecks` is off — in that configuration the whole distinction between a nullable and non-nullable type disappears and only the arity difference remains.

  • Can a function typed with an optional parameter be assigned where one with a required `| undefined` parameter is expected?
    Yes. `(x?: number) => void` accepts every call that `(x: number | undefined) => void` accepts, including a call with one argument, so it is assignable. The reverse fails: a function demanding an argument cannot stand in for one whose callers may pass none.
  • Which form would you pick for a value that is always conceptually part of the call but may be absent, and why?
    The required union. Writing `parentId: string | undefined` makes every call site state its answer, so a caller that forgot to thread the value through looks different from one that deliberately had none. An optional parameter collapses those two cases into the same silent call.
  • Does the distinction still matter when strictNullChecks is off?
    Only the arity half survives. Without strictNullChecks, `undefined` is assignable to `number`, so `number | undefined` collapses and the type layer stops distinguishing missing values. The compiler still refuses a zero-argument call to a required parameter, which is all that remains of the difference.

saying these in an interview costs you the question

  • Says the two forms are equivalent
  • Thinks `x?: number` rejects an explicit undefined argument
  • Believes one of them removes the need to narrow
  • Claims the union form emits a runtime check
  • Confuses parameter optionality with optional object properties

context