In JavaScript and TypeScript, functions are generic, which means a:
type f = (...args: [number]) => unknown
// aka: (foo: number) => unknown
is automatically a
type f = (...args: [number, ...any[]]) => unknown
Reasonable. If a function uses only the first few arguments, it is no harm to provide more.
And here come "optional parameters" in TypeScript. No worry in JavaScript since there are no "non-optional parameters":
type g = (foo: number, bar?: number) => unknown
It is also a:
(foo: number) => unknown
Why not? the second parameter is optional, it can be used like that.
So now, a g is also an f.
But wait, remember we have the second form of f:
const H = (h: (foo: number, bar: string) => void) => {
h(0, '')
}
const F = (f: (foo: number) => void) => {
H(f)
}
const g = (foo: number, bar?: number) => {
console.log(bar ?? 0 + foo + 1)
}
F(g)
TypeScript would gladly accept these code even in its most strict type checks, including strictFunctionTypes: a g is an f, we already know that, and an f is an h, we know that too. But is a g also an h ?
That is the question.
We have been using a lot of functional APIs. Array.prototype.map for example, accepts an executor (element, index?, array?) => any, which is practically an element => any.
But if the executor is from somewhere else in the later form, the "g is not h" can be a problem, a problem TypeScript unable to detect:
class Foo<T> {
private foo: T[]
...
function bar<U>(f: T => U) {
return this.foo.map(f)
}
...
}
Let's imagine what could happen here.

