In TypeScript, what is the difference between declaring a function parameter as `x?: number` and declaring it as `x: number | undefined`?
answer
- one is about arity, one about the value
- can the caller leave it out?
- f() versus f(undefined)
- identical inside the body
- forces the call site to state a choice
basics
~20 sBoth 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 sThe 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
Remember the one-line difference: ? lets the caller drop the argument entirely, while | undefined still requires an argument that may be the value undefined.
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.
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.
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