Kealan Parr Software engineer, technical writer and member of the Unicode Consortium.

How to pass a TypeScript function as a parameter

3 min read 882

TypeScript Logo

Table of Contents

As a programming language, JavaScript relies on the concept of First-Class functions, meaning that functions are treated like any other variable, such as a number, string, or array. One of the benefits of this is that functions can be passed into other functions, returned from functions, or assigned to variables to be called later if required.

This feature is heavily used in asynchronous code, where functions are often passed into asynchronous functions, often referred to as callbacks. But this can be tricky to use when we utilize TypeScript.

TypeScript offers us fantastic benefits of adding static types and transpilation checks, and it can help us better document what types of variables we expect into our functions — but what happens if we need to pass functions?

It seems fairly clear we need to type these functions, of course, but how do we type them, and how do we pass a function in TypeScript?

In this tutorial, you’ll learn about TypeScript functions and how to pass them as a parameter in your apps.

What is a TypeScript function type?

Typing simple variables is likely something most TypeScript developers are familiar with. But to construct a type for a function is a little more difficult.

A function type (note: this link redirects to old TypeScript docs, but it has a much clearer example than the newer ones) is made up of the types of the arguments the function accepts and the return type of the function.

We can illustrate a very simple example to showcase this:

const stringify = (el : any) : string => { return el + "" } 
const numberify = (el : any) : number => { return Number(el) }

let test = stringify;
test = numberify;

The above example, if implemented in JavaScript, would work fine and have no issues.

We made a custom demo for .
No really. Click here to check it out.

But now we’ve utilized TypeScript, errors throw when we try to transpile our code.

- Type '(el: any) => number' is not assignable to type '(el: any) => string'.
-   Type 'number' is not assignable to type 'string'.

The error message thrown here is pretty descriptive: the stringify and numberify function are not interchangeable.

They cannot be assigned interchangeably to the test variable, as they have conflicting types. The arguments they receive is the same (one argument of type any), but because their return types are different, we receive errors.

We could change the return types here to prove that our theory is correct.

const stringify = (el : any) : number => { return 1 } 
const numberify = (el : any) : number => { return Number(el) }

let test = stringify;
test = numberify;

The above code now works as expected, and the only difference is that we changed the stringify function to match the type of the numberify function. Indeed, the return type was breaking this example.

Using argument numbers in TypeScript

Interestingly, many other languages will create these function types based not only on the types of arguments and return types but also on the number of arguments for the function.

Let’s make one final example to expand on the last working example we had.

const stringify = (el : any, el2: number) : number => { return 1 } 
const numberify = (el : any) : number => { return Number(el) }

let test = stringify;
test = numberify;

Developers familiar with other languages might think that the above function examples aren’t interchangeable, as it’s often referred to as function overloading.

This example throws no errors, though, and it’s totally legitimate in TypeScript because TypeScript implements what’s referred to as duck typing.

It’s a small note, but it’s important to remember: the number of arguments isn’t utilized in type definitions for functions in TypeScript.

Typing our functions example in TypeScript

Now we know exactly how to construct types for our functions. We just need to ensure we type the functions that we pass in TypeScript.

Let’s work through a failing example together again.

const parentFunction = (el : () ) : number => { return el() } 

The above example doesn’t work, but it captures what we need.

We need to pass it to a parent function a callback, which can be called later. So, what do we need to change here?

  1. We need to type the el function argument
  2. We need to type the arguments we pass into the el function (if it requires them)

Upon doing this, our example should now look like this:

const parentFunction = (el : () => any ) : number => { return el() } 

This specific example doesn’t require arguments, but if it did, here is what it would look like:

const parentFunction = (el : (arg: string) => any ) : number => { return el("Hello :)") } 

This example is relatively simple in order to easily explain the concepts of TypeScript functions, but if you have more complicated types, you may spend a large amount of time typing everything.

The community maintains plenty of high-quality open source typings commonly used in TypeScript, called DefinitelyTyped, which can help you simplify and speed up the typing you will need to make use of.

Conclusion

I hope this article has been useful so you better understand the TypeScript landscape around passing functions as arguments to other functions.

Callbacks typically rely on this method, so you’ll often see heavy use of callbacks in any mature TypeScript codebase. Happy coding!

Writing a lot of TypeScript? Watch the recording of our recent TypeScript meetup to learn about writing more readable code.

TypeScript brings type safety to JavaScript. There can be a tension between type safety and readable code. Watch the recording for a deep dive on some new features of TypeScript 4.4.

Kealan Parr Software engineer, technical writer and member of the Unicode Consortium.

Leave a Reply