Chris Colborne

Don't use method syntax for your TypeScript function types!

March 31, 2022

Illustration from unDraw

In TypeScript, there are a few ways to type functions. But did you know that using the method syntax to define your functions in TypeScript is not type safe?

Typing Functions

There are three main ways you can type a function in TypeScript.

1. Function type expression syntax

The most common and recommended way is to type it as a function type expression, which uses a syntax like an arrow function:

interface MyObj {
  add: (a:number, b: number) => number
}

2. Call signature syntax

Another less common way is as a call signature. This is often useful if the function has properties as well as being callable.

interface MyObj {
  add: {
    description: string,
    (a: number, b: number): number
  }
}

3. Method syntax

The final way is method syntax. This often makes sense when talking about classes but is also valid for interfaces.

class MyCalculator {
  add(a: number, b: number): number;
}

interface MyObj {
  add(a: number, b: number): number;
}

But did you know if you use the method signature syntax (#3 above), it’s actually not completely typesafe.

strictFunctionTypes flag in TSConfig

The strictFunctionTypes flag in TSConfig, when enabled (as it is with strict: true), causes functions parameters to be checked more correctly.

However from the docs:

During development of this feature, we discovered a large number of inherently unsafe class hierarchies, including some in the DOM. Because of this, the setting only applies to functions written in function syntax, not to those in method syntax.

That means, if you type your function with the method syntax, strictFunctionTypes does not apply! 🙀

Let’s see an example.

interface PrintMethod {
  print(a: string | number): string;
}

interface PrintFunction {
  print: (a: string | number) => string;
}

interface PrintCall {
  print: {
    (a: string | number): string
  }
}

const printFunc = (a: string) => 'hello';

const MyAddingMethod: PrintMethod = {
   print: printFunc // uh oh - this isn't flagged as an error
}

const MyAddingFunction: PrintFunction = {
   print: printFunc // this is correctly flagged
}

const MyAddingCall: PrintCall = {
   print: printFunc // this is correctly flagged
}

TypeScript Playground

It’s not all bad news - it will correctly catch it if the type changes completely, for instance from a `string` to a `number`, however I’d argue that that makes it even more insidious, sometimes it catches it, sometimes it doesn’t.

What should you use?

Long story short, you should avoid using the method syntax (even for classes) when typing your functions in TypeScript. Use the function syntax (or the call signature if you need the additional properties) and your code will be much type-safer for it!


Like what you've read?

Why not subscribe to my mailing list to get my latest articles by email.

I respect the privacy of your email address. You can unsubscribe at any time.

Written by Chris Colborne, an Aussie software engineer from Brisbane, Australia. Follow me on Twitter

Chris Colborne © 2023