typescript: function overloading

2021-05-02

 | 

~4 min read

 | 

662 words

In languages like Java and C#, function overloading is quite popular. The way I understand it: instead of having multiple functions, you define multiple signatures and then allow the caller to determine which makes most sense for its situation. The benefit is that you only need to have one public method while the method itself handles the complexity of handling the result (i.e. leverage polymorphism).

While this isn’t particularly popular in my experience with Javascript, Typescript does allow it - at least in a limited sense.

I originally came across this through Kent C. Dodds’ post on the topic (the second example in this post was inspired by Kent).

Let’s start with a basic addition function:

function addNumbers(a: number, b: number): number
function addNumbers(a: string, b: string): number
function addNumbers(a: number | string, b: number | string) {
  a = typeof a === "number" ? a : Number.parseFloat(a)
  b = typeof b === "number" ? b : Number.parseFloat(b)
  return a + b
}

console.log(addNumbers(1, 2))
console.log(addNumbers("1", "2"))

I’d argue this isn’t a particularly good example since you can imagine using just the final signature of addNumbers(a: number | string, b: number | string) — so, why wouldn’t you? Well, with the current implementation if you tried to call addNumbers with a mix of numbers and strings, you’d get a type error:

no overload matches

Notice that the error specifically says the call would have succeeded if not for the overloads! That’s kind of nifty and suggests there’s power in using overloads to guide consumers!

Kent explored more interesting examples. The first was about an asyncAdd method (which I’ll explore a bit more below) and the second was related to typing his babel-plugin-macros.

asyncAdd.ts
type asyncAddCb = (result: number) => unknown
function asyncAdd(a: number, b: number): Promise<number>
function asyncAdd(a: number, b: number, cb: asyncAddCb): unknown

function asyncAdd(a: number, b: number, cb?: asyncAddCb) {
  const result = a + b
  if (cb) return cb(result)
  else return Promise.resolve(result)
}

const promised = asyncAdd(1, 2)
  .then((res) => res + 5)
  .then((res) => console.log(res)) // 8
const withCallBack = asyncAdd(1, 2, (res) => res + 5)

console.log(withCallBack) // 8

Note: In Kent’s implementation, asyncAddCB and the second overload for asyncAdd were typed to return void. That didn’t make a lot of sense to me since they could return values (and in fact, withCallBack is assigned the value of 8 in this example). As a result, I updated them to unknown since the callback could return anything, including void, but at this time, we just don’t know what it will be.

Thinking more about this example, I actually think asyncAdd is a peculiar name because the callback approach is not necessarily asynchronous. It might have been if we had typed asyncAddCb like:

type asyncAddCb = (result: number): Promise<number>

This would have been similar to the alternative approach and from the caller’s perspective, the only difference would be whether we pass in a callback or define it in a promise chain. One way to think about this would be:

asyncAdd.ts
- type asyncAddCb = (result: number) => unknown
+ type asyncAddCb = (result: number) => Promise<number>
function asyncAdd(a: number, b: number): Promise<number>
- function asyncAdd(a: number, b: number, cb: asyncAddCb): unknown
+ function asyncAdd(a: number, b: number, cb: asyncAddCb): Promise<number>

function asyncAdd(a: number, b: number, cb?: asyncAddCb) {
  const result = a + b
  if (cb) return cb(result)
  else return Promise.resolve(result)
}

const promised = asyncAdd(1,2).then(res => res + 5).then(res => console.log(res))
- const withCallBack = asyncAdd(1,2,(res) => res+5)
+ const withCallBack = asyncAdd(1,2,(res) => Promise.resolve(res+5))

- console.log(withCallBack) // 8
+ withCallBack.then(res => console.log(`withCallBack resolved:`,res))

Wrap Up

I’m sure there are a ton of good reasons for function overloading and I’m excited to learn how it’s done with Typescript, though it does seem more limited than other languages (or I haven’t figured out how to make it work). In C# for example, it’s possible to change the order of the parameters.


Hi there and thanks for reading! My name's Stephen. I live in Chicago with my wife, Kate, and dog, Finn. Want more? See about and get in touch!