How typescript infer works

Learn how the infer keyword works in typescript with examples

How typescript infer works

Have you ever wondered what exactly the keywordinfer means in typescript? Today we will look at how you can use this keyword to infer types in your derived types.

The infer keyword is an advanced topic of Typescript. In case you are not that familiar with the advanced features of Typescript - keyof, generics, and other advanced concepts - we highly recommend checking out our article Advanced Typescript article.

💡
This article was written after the Frontend ZG #14 with Reactor Studio that took place in Zagreb. Many people asked to have an article (and a talk) explaining the infer keyword. Hope you enjoy it as much as we did creating the article.

A brief overview of the importance of type inference in TypeScript

There are two main types that separate programming languages. Statically typed programming languages and dynamically typed. A statically-typed language is a language (such as Java, C, or C++) where variable types are known at compile time. Dynamic programming language is a class of high-level programming languages, which at runtime execute many common programming behaviors that static programming languages perform during compilation.

Typescript is a statically-typed language that allows developers to catch errors during development, rather than runtime. While we have to write more code, it offers a safety net for us to introduce fewer bugs to our code base.

The so-called type inference in Typescript is nothing more than the process of the compiler deducing the type of a variable, a function parameter, or a function return type based on the context where it is being used. This process gives the power to developers to write less code while maintaining the benefits of static type checking.

A simple example is if a variable is initialized with a string value, Typescript will infer that the type of that variable is a string. Another example is if a function takes a parameter of type string, and the parameter is passed as a string value, Typescript will automatically infer that the type of the first parameter is also a string.  

Overall, type inference in TypeScript helps to reduce the amount of boilerplate code required when writing TypeScript, while still providing the benefits of static type checking. This can lead to more concise, maintainable, and reliable code.

Explanation of how type inference works in TypeScript

Now that we have the formal definition of type inference, let's take a look at a couple of code examples that exemplifies the formal definition.

As discussed above, the type definitions can be inferred from a variable. A simple example would be

const firstName = "Bruno";
declaring const literal "Bruno"

In the example above the variable firstName is inferred  as a literal Bruno. Typescript is smart enough to pick up that we are defining a const that is of type string with a literal type of Bruno. Now, what if we would use let instead of const?

let firstName = "Bruno";
declaring string firstName as let

Now that we are using let instead of const, Typescript understands that we might change the value of firstName later, therefore it infers the type of firstName as a string and not as a literal Bruno.

Examples of how TypeScript can infer types from values and expressions

Typescript not only can infer types from simple variables but also from function return types and objects. Let's see the inferring of a function return type:

function sum(a: number, b: number) {
  return a + b;
}
sum function

We could have manually added a return type in the function by adding : number as a return type

function sum(a: number, b: number): number {
                               // ↑ adding the function return type here
  return a + b;
}

but typescript is smart enough to understand that the + operator will add two numbers together, therefore returning a number.

Inference does not only work for function return and simple variables but complexes one as well. For example, I could define an interface to represent a user in our system and use it in a variable declaration

interface User {
  id: number;
  firstName: string;
  lastName: string;
}

const user: User = {
  id: 1,
  firstName: "Bruno",
  lastName: "Francisco",
}

But Typescript can also infer complex objects. For example, if I would omit the User type from my user variable, it would still be able to infer the type from the object being created:

const user = {
  id: 1,
  firstName: "Bruno",
  lastName: "Francisco",
}

As an aside you might encounter situations where you would like to define an enumeration in your code that represents some static information. You could define something like this:

const MEETUP_STATE = {
  DRAFT: 1,
  PUBLISHED: 2,
  MARKED_FOR_DELETION: 3,
}

The problem here is that Typescript will infer your object as

const MEETUP_STATE: {
    DRAFT: number;
    PUBLISHED: number;
    MARKED_FOR_DELETION: number;
}

This creates a problem where what you really need is to tell Typescript to treat this variable as a read-only variable. To solve this issue we can simply add as const after my object

const MEETUP_STATE: {
    DRAFT: number;
    PUBLISHED: number;
    MARKED_FOR_DELETION: number;
} as const;

This will create the following type definition

const MEETUP_STATE: {
    readonly DRAFT: 1;
    readonly PUBLISHED: 2;
    readonly MARKED_FOR_DELETION: 3;
}

Typescript infer keyword

Typescript language allows us to use the infer keyword to define generic types that will depend on other types. The infer keyword will allow you to extract and use the type of a value - parameter of functions, generics, and many more - or expression in a generic type parameter, allowing you to create more flexible and reusable code.

In Typescript, the infer keyword usually is used together with conditional types, which is a feature in Typescript that allows you to define types based on certain conditions. By using the infer keyword alongside conditional types, you will be able to create type definitions that will depend on other types, allowing you to create highly flexible and expressive types.

In the following sections, we'll explore how the "infer" keyword can be used in TypeScript, with examples that demonstrate how it can be used to create powerful and flexible generic types.

Understanding the infer keyword in Typescript

It is extremely important to create a mental model of how the infer works. For that, let's take a look at a Javascript example that many developers had to go through. Imagine that we would like to create a regular expression to get all dashes (-) and underlines (_) in a string and double it - - becomes --  and _ becomes __

const toDoubleUnderline = "bruno-mendes_francisco".replace(
  /(-|_)/g,
  (item) => {
    if (item === "-") {
      return "--";
    }
    
    if (item === "_") {
      return "__";
    }
  }
)

We don't know what item is, so we need to check what it is and do the appropriate logic to transform the - into -- and _ into __. This is the same mental model that we need to apply to understand the infer keyword: we do not know what is the type of item, therefore we need to infer it, and depending on the type inferred, we would like to do something with it.

💡
This mental model is inspired by the same mental modal that Matt Pocock explains in his Infer is easier than you think video. You should definitely check out the guy. He's a pillar of the Typescript community that definitely deserves a visit

Simple examples of infer keyword

The most basic form of the usage infer can be for extracting the type property of an object. Imagine that I have the following User interface:

interface User {
  name: string;
  address: {
    streetName: string;
    houseNumber: number;
  }
}

Now, we would like to create a type helper to extract the type of a property in an object. The usage would be something like type AddressType = ExtractProp<User, 'address'>. We could use the infer keyword to create this type helper

type ExtractPropType<O, K extends keyof O> = O[K] extends infer U ? U : never;

interface User {
  name: string;
  address: {
    streetName: string;
    houseNumber: number;
  };
}

type UserAddressType = ExtractPropType<User, "address">; // UserAddressType is "{ streetName: string; houseNumber: number; }"

Example of using "infer" to extract the return type of a function

In Typescript, we have a utility type named ReturnType. This utility type, as the name describes, gives us the return type of a function. Here's a usage of ReturnType

function getFullName(user: { firstName: string; lastName: string }) {
  return `${user.firstName} ${user.lastName}`;
}

type RETURN_TYPE_FULL_NAME = ReturnType<typeof getFullName>;
  // ↑ string

We can create a definition to extract the return type of a function using the infer keyword - the code below is the same used in the standard Typescript library

type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;

In the example above, we first constraint our generic T to only functions with T extends (...args: any) => any . Then, we make a conditional check  T to check that T extends a function and infers the return value of the function with infer R . The final step is simply to return R.

Advanced examples of infer keyword

While most developers will be happy using the infer keyword on simple examples such as ReturnType the infer keyword is much more powerful than it might seem.

Using infer keyword to check the inner type of a PromiseLike type

In the Types Challenges (a set of challenges that you can challenge your Typescript knowledge by completing challenges from easy to extreme levels) you might encounter crazy examples of the usage of the infer keyword

For example, the challenge Easy awaited says

If we have a type which is wrapped type like Promise. How we can get a type which is inside the wrapped type?

Check out one of the many solutions using the infer keyword. This solution not only uses the infer keyword but it also uses recursion to continually check what is the type in the wrapped type

type MyAwaited<T> = T extends PromiseLike<infer TYPE> ? MyAwaited<TYPE> : T

Conclusion

In Typescript, the inference is an extremely powerful feature that allows developers to write safer, more reliable code with less overhead. Overall, it allows Typescript to automatically deduce the types of values and expressions based on how they are being used, reducing overall the number of characters the developer has to write.

The infer keyword gives developers a powerful mechanism for working with generic types, making it possible to extract and use the type of a value or expression in a type. This makes it possible to create highly reusable and maintainable code that can adapt to different contexts.

In the end, the combination of type inference and the infer keyword can help developers improve their productivity and code quality reducing the cognitive load required to work with types.

😍
Are you tired of trying to organize tech meetups on your own? Look no further! Codotto makes it easy for you to plan and host successful tech meetups. With our platform, you can invite speakers to present at your meetup, and even hold Q&A sessions to encourage audience participation. And after the meetup, attendees can leave reviews and ratings for both the meetup itself and the individual presentations, so you can get valuable feedback on how to improve future events. Join now and start organizing your own tech meetups with ease!