Deprecating Function Signature Using Function Overloads in TypeScript

Altrim Beqiri

Altrim Beqiri /

In this article we will see how we can leverage TypeScript’s function overloads to deprecate a function signature.

Usually when you need to deprecate a function you would add a @deprecated notice to the function, then create a new function with probably a similar or different name and point developers to use the new function in the JSDoc message and/or display a console warning. And eventually get rid of the deprecated function on a future version of the library.

What if you don't want to rename the function or define entirely new one? Instead you have few changes you want to make and just want to update the function signature to support additional options and at the same time deprecate the old API usage.

To demonstrate how we can achieve this we will use the getColorForStockAmount() function from the previous article Replace If Statements With Array.find and we will try and add some new changes to the function. In the end we will also see how our code editor helps us by visualizing the deprecated signature.

This is how getColorForStockAmount() function looks like at the moment

// We have few constants for the colors and use it as a return type in the function
enum AlertColor {
  Red = "red",
  Yellow = "yellow",
  Green = "green",
}

// The items type definition
type Amount = {
  amount: number;
  color: AlertColor;
};

// The function currently accepts only one parameter and that is `stock` of type number
const getColorForStockAmount = (stock: number = 0): AlertColor => {
  const items: Amount[] = [
    { amount: 100, color: AlertColor.Red },
    { amount: 500, color: AlertColor.Yellow },
    { amount: Number.MAX_SAFE_INTEGER, color: AlertColor.Green },
  ];

  const item = items.find((item) => stock <= item.amount);

  return item?.color;
};

As we can see from the above code we have this (stock: number): AlertColor function signature.

Now let's introduce some changes. What we want to do is to change the parameter from a number to an object with options and we want to add additional property to the options object e.g updatedAt (indicating when the stock was updated).

Applying the above changes we get to the following code

enum AlertColor {
  Red = "red",
  Yellow = "yellow",
  Green = "green",
}
type Amount = {
  amount: number;
  color: AlertColor;
  updatedAt: Date; // We add the new `updatedAt` prop in Amount
};

// Define the type for the new options object
type Options = {
  amount: number;
  updatedAt?: Date;
};

// Define the interface for the function type with a list of overloads
interface StockOptions {
  // The new signature
  (options: Options): AlertColor | undefined;

  /** @deprecated
   * Use { amount: number; updatedAt?: string; } object instead
   * */
  (stock: number): AlertColor | undefined;
}

const getColorForStockAmount: StockOptions = (options: number | Options) => {
  const { amount, updatedAt } =
    typeof options === "number" ? { amount: options, updatedAt: undefined } : options;

  const items: Amount[] = [
    { amount: 100, color: AlertColor.Red, updatedAt: new Date("2021-01-21") },
    { amount: 500, color: AlertColor.Yellow, updatedAt: new Date("2021-01-21") },
    { amount: 750, color: AlertColor.Green, updatedAt: new Date("2021-01-21") },
  ];

  const item = items.find((item) => {
    if (updatedAt) {
      return amount <= item.amount && updatedAt < item.updatedAt;
    }
    return amount <= item.amount;
  });

  return item?.color;
};

Let's break down the code above. First we take a look at the important part that is the StockOptions interface

interface StockOptions {
  (options: Options): AlertColor | undefined;

  /** @deprecated
   * Use { amount: number; updatedAt?: string; } object instead
   * */
  (stock: number): AlertColor | undefined;
}

We have defined an interface with two function signatures as a list of function overloads :

  • The first signature (options: Options): AlertColor | undefined that accepts an Options object and returns AlertColor | undefined.
  • The second signature (stock: number): AlertColor | undefined that was the initial signature accepts a number and has the same return type AlertColor | undefined.

Keep in mind with function overloads you can have different return types as well, in our case we use the same one. Additionally as we can see from the code above we have marked the second signature as @deprecated to warn developers and notify them that they can use the options object instead. The @deprecated keyword used in in the JSDoc is also used by editors to visually indicate a deprecated function as we will see a bit later.

Once we have defined the interface we can define the function type as follows

const getColorForStockAmount: StockOptions = (options: number | Options) => {};

Now that we have the proper function type definition what's left to do is handle both cases so we don't break the previous implementation. To do that we first check the argument type and based on the type we define the variables

const { amount, updatedAt } =
  // If the argument type is a number ( the initial signature )
  // we assign it to amount and we keep updatedAt as undefined
  // otherwise we use the object from the new signature
  typeof options === "number" ? { amount: options, updatedAt: undefined } : options;

Lastly in items.find we check if updatedAt is defined we use the date as well in the condition check otherwise fallback to the previous implementation (first signature) for backward compatibility

const item = items.find((item) => {
  if (updatedAt) {
    return amount <= item.amount && updatedAt < item.updatedAt;
  }
  return amount <= item.amount;
});

Finally with everything in place when you call the function with the both signatures you should see something like the following in your editor (screenshot from VSCode)

Deprecated Function Signature

As you can see from the screenshot above only when we call the function with the initial signature we get a strikethrough warning, otherwise looks ok when we use the new signature with the options object. And if we would hover over the deprecated function call in our editor we would see our deprecation message like we've defined it in the docblock

Deprecated Function Signature Popup

That's it. Hope this has been helpful to you.

Happy Coding!