TypeScript Types Deep Dive - Part 3: Functions
Tags: typescript, javascript, webdev
Published: 2019-12-26 22:31:32

---
title: TypeScript Types Deep Dive - Part 3: Functions
published: true
description: An epic journey of discovery into the mysterious world of TypeScript's type system. Part 3 discuss how to use types in functions within TypeScript.
tags: [typescript,javascript,webdev]
canonical_url: https://www.barbarianmeetscoding.com/blog/2019/12/26/typescript-types-deep-dive-part-iii-functions
cover_image: https://www.barbarianmeetscoding.com/images/typescript-types-deep-dive.jpg
series: TypeScript Types Deep Dive
---

*This article was originally published on [Barbarian Meets Coding](https://www.barbarianmeetscoding.com/blog/2019/12/26/typescript-types-deep-dive-part-iii-functions).*


TypeScript is a modern and safer version of JavaScript that has taken the web development world by storm. It is a superset of JavaScript that adds in some additional features, syntactic sugar and static type analysis aimed at making you more productive and able to scale your JavaScript projects. 

This is the **third part** of a [series of articles](https://www.barbarianmeetscoding.com/blog/categories/typescript-deep-dive) where we explore TypeScript's comprehensive type system and learn how you can take advantage of it to **build very robust and maintainable web apps**. Today, we shall look at **functions**!

<!-- more -->

> Haven't read the first and second parts of this series? If you haven't you may want to <a href="https://www.barbarianmeetscoding.com/blog/categories/typescript-deep-dive">take a look</a>. There's lots of interesting and useful stuff in there.

Functions are one of the most fundamental composing elements of a JavaScript program, and that doesn't change at all in TypeScript. The most common way in which you'll use types in functions within TypeScript is inline, intermingled with the function itself. 

Imagine a simple JavaScript function to add a couple of numbers: 

```javascript
function add(a, b){
  return a + b;
}
```

Although, since there's no static typing in JavaScript, there's nothing saying you will only add numbers with this function, you could add anything (which isn't necessarily a bug, it could be a feature). 

```javascript
add(1, 2)            // => 3
add(1, " banana")    // => "1 banana"
add(22, {"banana"})  // => "1[object Object]"
add([], 1)           // => "1"
```

In our specific context though, where we're trying to build a magic calculator to help us count the amount of dough we need to bake 1 trillion gingerbread cookies (cause we love Christmas, and baking, and we're going to get that Guinness world record once and for all). 

So we need `a` and `b` to be numbers. We can take advantage of TypeScript to make sure that the parameters and return types match our expectations:

```typescript
// Most often you'll type functions inline
function add(a: number, b: number): number{
  return a + b;
}
```

So when we exercise this function it works only with numbers:

```typescript
add(1, 2)            // => 3
add(1, " banana")    // => 💥
add(22, {"banana"})  // => 💥
add([], 1)           // => 💥
```

Since the TypeScript compiler is quite smart, it can infer that the type of the resulting operation of adding two numbers will be another number. That means that we can omit the type of the returned value:

```typescript
function add(a: number, b: number) {
  return a + b;
}
```

And if you prefer the arrow function notation you can write it like this:

```typescript
const add = (a: number, b: number) => a + b;
```

> TypeScript implements <a href="https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-0.html#control-flow-based-type-analysis"><strong>control flow-based type analysis</strong></a> which is a complicated way of saying that it runs through your code and can compute and understand the types of the different variables and the resulting values of the operations performed. Using this information it can infer types and save you lots of work (like above), or even refine the types within the body of a function to offer better error messages, more type safety and improved statement completion (we shall see more of that in future articles).

Typing functions inline will be by far the most common way in which you'll use types with functions in TypeScript. Now let's dive further into the different things you can do with parameters and typing functions as values.

## Optional Parameters

JavaScript functions can be extremely flexible. For instance, you can define a function with a set of parameters but you don't necessarily need to call the function with that same amount of parameters. 

Let's go back to the `add` function:

```javascript
function add(a, b) {
  return a + b;
}
```

In JavaScript, there's no one stopping you from calling this function like so:

```javascript
add(1, 2, 3); // => 3
add(1, 2);    // => 3
add(1);       // => NaN
add();        // => NaN
```

TypeScript is more strict. It requires you to write more intentional APIs so that it can, in turn, help you adhere to those APIs. So TypeScript assumes that if you define a function with two params, well, you are going to want to call that function using those two params. Which is great because if we define and `add` function like this:

```typescript
function add(a: number, b: number) {
  return a + b;
}
```

TypeScript will make sure that we call that function as the code author designed it, and thus avoid those awful corner cases that resulted in `NaN` previously:

```javascript
add(1, 2, 3); // => 💥 Expected 2 arguments, but got 3
add(1, 2);    // => 3
add(1);       // => 💥 Expected 2 arguments, but got 1
add();        // => 💥 Expected 2 arguments, but got 0
```

It is important to keep the flexibility of JavaScript, because there will be legitimate cases where parameters should be optional. TypeScript lets you be as flexible as you are accustomed to in JavaScript, but you need to be intentional by explicitly defining whether a parameter is optional or not.

Imagine we're adding some logging to our application to have a better understanding of how our users interact with it. It is important to learn how our users use our applications so that we can make informed decisions as to which features are more or less important, more or less useful, how we can make important features more easily discoverable, etc... So we define this logging function:

```typescript
function log(msg: string, userId) {
  console.log(new Date(), msg, userId);
}
```

Which we can use like this:

```typescript
log("Purchased book #1232432498", "123fab");
```

However, in our system, a user is not required to log in. Which means that the `userId` may or may not be available. That is, the `userId` parameter is optional. We can model that in TypeScript using **optional parameters** like so:

```typescript
// Optional params
function log(msg: string, userId?: string){
  console.log(new Date(), msg, userId ?? 'anonymous user');
}
```

So that now the function can be called omitting the second parameter:

```typescript
log("Navigated to about page");
```

or with an `undefined` as second parameter:

```typescript
// get userId from user management system
// because the user isn't logged in the system
// returns undefined
const userId = undefined;
log("Navigated to home page", userId);
```

This gives you a hint that the optional param is a shorthand for this:

```typescript
function log(msg: string, userId: string | undefined){
  console.log(new Date(), msg, userId ?? 'anonymous user');
}
```

Optional parameters always have to be declared at the end of a function parameter list. This makes sense because in the absence of an argument it would be impossible for the TypeScript compiler to know which param one is trying to refer to when calling a function. If you happen to make this mistake when writing a function the TypeScript compiler will immediately come to your aid with the following message: `💥 A required parameter cannot follow an optional parameter.`

## Default Parameters

I don't quite enjoy having `undefined` values rampant in my functions (for [the many reasons we discussed earlier](https://www.barbarianmeetscoding.com/blog/2019/12/21/typescript-types-deep-dive-part-ii)), so when possible I favor **default parameters** over optional parameters. 

Using **default parameters** we could rewrite the function above as:

```typescript
// Default params
function log(msg: string, userId = 'anonymous user'){
  console.log(new Date(), msg, userId);
}
```

This function behaves just like our previous function:

```typescript
log("Navigated to about page");
log("Sorted inventory table", undefined);
log("Purchased book #1232432498", "123fab");
```

But there's no null reference exception waiting to happen.

## Rest Parameters

JavaScript has this nifty feature called **rest parameters** that lets you define *variadic functions*. A *variadic function* is the fancy name of a function that has *indefinity arity* which is yet another fancy way to say that a function can take any number of arguments.

Imagine we'd like to create a logger that lets us log any arbitrary number of things attached to a timestamp that describes when those things happened. In JavaScript we would write the following function:

```javascript
function log(...msgs){
  console.log(new Date(), ...msgs);
}
```

And in TypeScript, since `msgs` is essentially an array of arguments we'll annotate it like so:

```typescript
// Typed as an array
function log(...msgs: string[]){
  console.log(new Date(), ...msgs);
}
```

And now we can use it to pass in as many arguments as we like:

```typescript
log('ate banana', 'ate candy', 'ate doritos');
// Thu Dec 26 2019 11:10:16 GMT+0100 
// ate banana
// ate candy
// ate doritos
```

Since it is a fancy variadic function it will just gobble all those params. Also, Thursday December 26th was a cheat day in this household.

## Typing Functions as Values

Ok. So far we've seen how you type a function inline using a function declaration for the most part. But JavaScript is very, very fond of functions, and of using functions as values to pass them around and return them from other functions.

This is a function as a value (which we store inside a variable `add`):

```typescript
const add = (a: number, b: number) => a + b;
```

What is the type of the variable `add`? What is the type of this function?

The type of this function is:

```typescript
(a: number, b: number) => number;
```

Which means that instead of using inline types we could rewrite the `add` function like so:

```typescript
const add : (a: number, b: number) => number = (a, b) => a + b;
```

or using an alias: 

```typescript
type Add = (a: number, b: number) => number
const add : Add = (a, b) => a + b;
```

> 
> This example above is quite intesting because the type information is flowing in the opposite way to what we're used to. So far we've been defining types on the expression to the right which flow to the variable that is being assigned (on the left). In this case however, we're defining the type of the variable on the left, and the types flow to the expression on the right. Interesting, isn't it?
>
> This feature of been able to grab types from the context is known as <strong>contextual typing</strong> and it is a great feature to have because it improves TypeScript type inference capabilities and saves you from typing more annotations than the minimum required.
>
>If this sounds interesting, you may want to take a look at <a href="https://www.typescriptlang.org/docs/handbook/type-inference.html#contextual-typing">the documentation on TypeScript type inference</a>.

After rewriting the function to use the new full-blown type definition, TypeScript would nod at us knowingly, because it can roll with either inline types or these other separate type definitions. If you take a look at both ways of typing this function side by side:

```typescript
// # 1. Inline
const add = (a: number, b: number) => a + b;

// # 2. With full type definition
const add : (a: number, b: number) => number = (a, b) => a + b;
```

You are likely to prefer option 1 since it's more pleasant, easier to read and the types are very near to the params they apply to which eases understanding. So when is option 2 useful?

Option 2 or full type definitions is useful whenever you need to store a function, and when working with **higher-order functions**. 

> A <strong>higher-order function</strong> is a function that either takes another function as a paremeter or returns a function. You can learn more about higher-order functions and other functional programming concepts in <a href="https://www.barbarianmeetscoding.com/blog/2016/06/14/functional-programming-in-javascript">this excellent article on the topic</a>.

Let's illustrate the usefulness of typing functions as values with an example. Imagine we want to design a logger that only logs information under some circumstances. This logger could be modelled as a higher-order function like this one:

```typescript
// Takes a function as a argument
function logMaybe(
  shouldLog: () => bool,
  msg: string){
    if (shouldLog()) console.log(msg);
}
```

The `logMaybe` function is a higher-order function because it takes another function `shoudLog` as a parameter. The `shouldLog` function is a predicate that returns whether or not something should be logged.

We could use this function to log whether some monster dies a horrible death like so:

```typescript
function attack(target: Target) {
  target.hp -= 10;
  logMaybe(
     () => target.isDead, 
     `${target} died horribly`
  );
}
```

Another useful use case would be to create a factory of loggers:

```typescript
type Logger = (msg: string) => void
// Returns a function
function createLogger(header: string): Logger {
    return function log(msg: string) {
       console.log(`${header} ${msg}`);
    }
}
```

`createLogger` is a higher-order function because it returns another function of type `Logger` that lets you log strings. We can use `createLogger` to create loggers to our heart's content:

```typescript
const jaimeLog = createLogger('Jaime says:')

jaimeSays('banana');
// Jaime says: banana
```

TypeScript is great at inferring return types so we don't really need to explicitly type the returning function. This would work as well:

```typescript
function createLogger(header: string) {
    return function log(msg: string) {
       console.log(`${header} ${msg}`);
    }
}
```

## Function Overloading

One of the features I kind of miss from strongly typed languages like C# is function overloading. The idea that you can define multiple signatures for the same function taking a diverse number of parameters of different types, and upon calling that function the compiler will be able to discriminate between functions and select the correct implementation. This is a very nice way to provide slightly different APIs to solve the same problem. Like, the problem of raising an army of the undead:

```javascript
raiseSkeleton()
// don't provide any arguments and you raise an skeleton
// => raise a skeleton
raiseSkeleton(4)
// provide a number and you raise a bunch of skeletons
// => raise 4 skeletons
raiseSkeleton('king')
// provide a string and you raise a special type of skeleton
// => raise skeleton king
```

JavaScript however doesn't have a great support for function overloading. You can mimick function overloading in JavaScript but it does require a bunch of boilerplate code to manually discriminate between function signatures. For instance, a possible implementation for the `raiseSkeleton` function above could be this:

```javascript
function raiseSkeleton(options) {
  if (typeof options === 'number') {
    raiseSkeletonsInNumber(options)
  } else if (typeof options === 'string') {
    raiseSkeletonCreature(options)
  } else {
    console.log('raise a skeleton')
  }

  function raiseSkeletonsInNumber(n) {
    console.log('raise ' + n + ' skeletons')
  }
  function raiseSkeletonCreature(creature) {
    console.log('raise a skeleton ' + creature)
  }
}
```

>You can read more about <a href="https://www.barbarianmeetscoding.com/blog/2015/05/26/mastering-the-arcane-art-of-javascript-mancy-for-c-sharp-developers-chapter-5-more-useful-function-patterns-function-overloading">the perils of function overloading in JavaScript in this other article</a>.

TypeScript tries to lessen the burden of writing function overloading somewhat but it doesn't get all the way there since it is still a superset of JavaScript. The part of function overloading in TypeScript that is really pleasant is the one concerning the world of types. 

Let's go back to the log function we used in earlier examples:

```typescript
function log(msg: string, userId: string){
  console.log(new Date(), msg, userId);
}
```

The type of that function could be defined by this alias:

```typescript
type Log = (msg: string, userId: string) => void
```

And this type definition is equivalent to this other one:

```typescript
type Log = {
  (msg: string, id: string): void
}
```

If we wanted to make the `log` function provide multiple APIs adapted to different use cases we could expand the type definition to include multiple function signatures like this:

```typescript
type Log = {
  (msg: string, id: string): void
  (msg: number, id: string): void
}
```

Which now would allow us to record both string messages as before, but also message codes that are messages obfuscated as numbers which we can match to specific events in our backend.

Following this same approach, a type definition for our `raiseSkeleton` function would look like this:

```typescript
type raiseSkeleton = {
  (): void
  (count: number): void
  (typeOfSkeleton: string): void
}
```

Which we can attach to the real implementation in this manner:

```typescript
const raiseSkeleton : raiseSkeleton = (options?: number | string) => {
  if (typeof options === 'number') {
    raiseSkeletonsInNumber(options)
  } else if (typeof options === 'string') {
    raiseSkeletonCreature(options)
  } else {
    console.log('raise a skeleton')
  }

  function raiseSkeletonsInNumber(n: number) {
    console.log('raise ' + n + ' skeletons')
  }
  function raiseSkeletonCreature(creature: string) {
    console.log('raise a skeleton ' + creature)
  }
}
```

And alternative type definition which doesn't require the creation of an alias (but which I find quite more verbose) is the following:

```typescript
// Alternative syntax
function raiseSkeleton(): void;
function raiseSkeleton(count: number): void;
function raiseSkeleton(skeletonType: string): void;
function raiseSkeleton(options?: number | string): void {
  // implementation
}
```

If we take a minute to reflect about function overloading in TypeScript we can come to some conclusions:

* TypeScript function overloading mostly affects the world of types
* Looking at a type definition it is super clear to see the different APIs an overloaded function supports, which is really nice
* You still need to provide an implementation underneath that can handle all possible cases

In summary, **function overloading in TypeScript provides a very nice developer experience for the user of an overloaded function, but not so nice a experience for the one implementing that function**. So the code author pays the price to provide a nicer DX to the user of that function.

Yet another example is the `document.createElement` method that we often use when creating DOM elements in the web (although we don't do it as much in these days of frameworks and high-level abstractions). The `document.createElement` method is an overloaded function that given a tag creates different types of elements:

```typescript
type CreateElement = {
  (tag: 'a'): HTMLAnchorElement
  (tag: 'canvas'): HTMLCanvasElement
  (tag: 'svg'): SVGSVGElement
  // etc...
}
```
Providing an API like this in TypeScript is really useful because the TypeScript compiler can help you with statement completion (also known in some circles as IntelliSense). That is, as you create an element using the `a` tag, the TypeScript compiler knows that it will return an `HTMLAnchorElement` and can give you compiler support to use only the properties that are available in that element and no other. Isn't that nice?

## Argument Destructuring

A very popular pattern for implementing functions these days in JavaScript is argument destructuring. Imagine we have an ice cone spell that we use from time to time to annoy our neighbors. It looks like this:

```javascript
function castIceCone(caster, options) {
  caster.mana -= options.mana;
  console.log(`${caster} spends ${options.mana} mana 
and casts a terrible ice cone ${options.direction}`);
}
```

I often use it with the noisy neighbor upstairs when he's having parties and not letting my son fall asleep. I'll go **BOOOOM!! Ice cone mathafackaaaa!**

```javascript
castIceCone('Jaime', {mana: 10, direction: "towards the upstairs' neighbors balcony for greater justice"});
// => Jaime spends 10 mana and casts a terrible ice cone
// towars the upstairs' neighbors balcony for greater justice
```

But it feels like a waste to have an `options` parameter that doesn't add any value at all to this function signature. A more descriptive and lean alternative to this function takes advantage of argument destructuring to extract the properties we need, so we can use them directly:

```javascript
function castIceCone(caster, {mana, direction}) {
  caster.mana -= mana;
  console.log(`${caster} spends ${mana} mana 
and casts a terrible ice cone ${direction}`);
}
```

This removes a lot of noise and it also allows us to set sensible defaults inline which makes sense because the second paremeter should be optional:

```javascript
function castIceCone(
  caster, 
  {mana=1, direction="forward"}={}) {
  caster.mana -= mana;
  console.log(`${caster} spends ${mana} mana 
and casts a terrible ice cone ${direction}`);
}
```

So how do we type this param in TypeScript? You may be tempted to write something like this:

```typescript
function castIceCone(
  caster: SpellCaster, 
  {mana: number, direction:string}): void {
  caster.mana -= mana;
  console.log(`${caster} spends ${mana} mana 
and casts a terrible ice cone ${direction}`);
}
```

But it wouldn't work. Because that's legit ES2015 destructuring syntax. It's the pattern you use when you want to project a property of an object into a variable with a different name. In the example above we're projecting `options.mana` into a variable named `number`, and `options.direction` into another variable `string`. Ooops.

The most common way to type the function above is to provide a type for the whole parameter (just like we normally do with any other params):

```typescript
function castIceCone(
  caster: SpellCaster, 
  {mana=1, direction="forward"}={} : {mana?: number, direction?:string} 
  ): void {
  caster.mana -= mana;
  console.log(`${caster} spends ${mana} mana 
and casts a terrible ice cone ${direction}`);
}
```

Both parameters are optional because they have defaults so the user of this function doesn't have to provide these as arguments if they don't want. There's something particularly interesting about this example that you may not have noticed: **the types of the parameters as defined in the function declaration are not the types of the parameters inside the function**. What? The caller of this function and the body of this function see different types. What??

* A caller of `castIceCone` sees `mana` as required to be of type `number` or `undefined`. But since `mana` has a default value, within the body of the function it will always be of type `number`.
* Likewise, the caller of the function will see `direction` as been `string` or `undefined` whilst the body of the function knows it'll always be of type `string`.

TypeScript argument destructuring can get quite verbose very fast so you may want to consider declaring an alias:

```typescript
type IceConeOptions = {mana?: number, direction?: string}
function castIceCone(
  caster: SpellCaster, 
  {mana=1, direction="forward"}={} : IceConeOptions): void {
  caster.mana -= mana;
  console.log(`${caster} spends ${mana} mana 
and casts a terrible ice cone ${direction}`);
}
```

or opting out of inline types entirely:

```typescript
type castIceCone = (caster: SpellCaster, options: IceConeOptions) => void;

const castIceCone : castIceCone = (
  caster, 
  { mana = 1, direction = "forward" } = {}
  ) => {
  caster.mana -= mana;
  console.log(`${caster} spends ${mana} mana 
and casts a terrible ice cone ${direction}`);
}
```

## In Summary

JavaScript functions are extremely flexible. TypeScript functions are just as flexible and will support the most common patterns used with functions in JavaScript but they expect you to be more intentional and explicit with the APIs that you design. This isn't a bad thing, it means that your APIs are constrained to only the use cases that you as an author define. This additional constraint will help prevent your APIs from being used in mischiveous or unexpected ways (like calling a function with no arguments when it expects two argumenst).

The most common way to type your functions is using types inline, having the types sitting just beside the stuff they affect: your arguments and return types. TypeScript is pretty good at inferring return types by taking a look at what happens inside your function, so in lots of cases you'll ber OK omitting your return values.

The function patterns that you're accustomed to in JavaScript are supported in TypeScript. You can use **optional parameters** to define functions that may or may not receive some arguments. You can write type safe functions with **default params, rest params and argument destructuring**. You even have a much better support for writing **function overloads** than you do in JavaScript. And you have the possibility of expressing the types of functions as a value, which you'll often use when writing higher-order functions.

In summary, TypeScript has amazing features to help you writing more robust and maintainable functions. Wihoo!

Hope you enjoyed this article! Take care and be kind to the people around you!