Why does TypeScript require optional parameters after required parameters?

? isn't shorthand for string | undefined. It's an optional parameter. Meaning - you can either pass the expected value type, or not.

string | undefined, by contrast, means you must explicitly pass a value that is either a string or undefined. The value may be undefined, but the parameter still exists.

It would be paradoxical to pass a property after an optional property - you wouldn't be able to set it, since setting it would mean setting one before it, which means its predecessor is then not optional!


Most importantly, this is a javascript fact that is just made more explicit by strongly typing a function signature.

Function parameters are bound positionally in the arguments list. The first passed argument is bound to the first parameter. The second passed argument is bound to the second parameter. And so on. With the optional parameters at the end, a javascript engine knows to bind the end parameters to undefined when the arguments list is too short. However many short the invocation is, that's how many of the end parameters get to be undefined.

If the optional parameters could be in the middle or at the beginning, there wouldn't be enough information at the call site to know which parameters should be bound to which arguments.

function fn(a?: number, b: number) { /* ... */ }

fn(42);

If fn's signature were valid, fn(42) would be ambiguous. Should b be bound to 42 leaving a undefined? Or should a be bound to 42 with b left undefined (and therefore a type error?) In this particular situation, you might want the typescript type checker to ASSUME that there is no error and instead try its hardest to find a valid interpretation of the invocation (the one in which b gets 42) but that would be a more costly overload resolution algorithm and there would still be cases that are ambiguous and the rules would be too complicated to be useful (i.e. fn2(a?: number, b: number, c?: number) { /* ... */ } with fn(42, 3.1415))

Javascript, by having positional arguments with the trailing arguments optional, and with Typescript allowing us to explicitly annotate the signature overloads which are allowed, strike a great balance of expressiveness, reasonability (performance of the type-checker) and bug avoidance.

If overload resolution were more flexible in that way, then it necessarily have to consider more invocations to be valid which might actually be errors. Or it would put the burden on the function body to deal with all the variations completely, such as making all the parameter combinations being union types with eachother and then having to tease those apart in the code, making it harder to read.


public console(message: string | undefined, suppressLevel: boolean) {}

Uses the pipe (|) operator to declare a parameter that is either of type string or undefined. See union types for more detail.

public console(suppressLevel: boolean, message?: string) {}

Declares an optional parameter which as per docs need to follow required parameters.

Any optional parameters must follow required parameters.

So, while in practice, an optional string parameter IS indeed a parameter that is of type string or undefined (or null depending on your settings), those two are different syntax that follow separate rules. Forcing optional parameters to come after required parameters increase readability and functionality, and typescript can not just treat union types as optional since they are two distinctly different things. Hence the difference.