All Articles

Structure of a TypeScript Error

Matt Pocock
Matt PocockMatt is a well-regarded TypeScript expert known for his ability to demystify complex TypeScript concepts.

Understanding TypeScript errors can be extremely tough. They have a reputation for having complicated wording, and can be extraordinarily long. Let's take this example:

Type '() => { something: { excellent: string; awesome: boolean; }; }' is not assignable to type 'ExampleFunction'.
  Call signature return types '{ something: { excellent: string; awesome: boolean; }; }' and '{ something: { excellent: string; awesome: number; }; }' are incompatible.
    The types of 'something.awesome' are incompatible between these types.
      Type 'boolean' is not assignable to type 'number'.

Here's the code that's causing this error:

type ExampleFunction = () => {
  something: {
    excellent: string
    awesome: number
  }
}

const exampleFunc: ExampleFunction = () => {
  //  ^^^^^^^^^^^ Error here!
  return {
    something: {
      excellent: 'str',
      awesome: true,
    },
  }
}

You might be able to see immediately what's causing the error - but let's keep the suspense a little longer. If you can understand why TypeScript throws this strange-looking error, you'll feel much more confident when the next one comes along.

What's happening here is that we're saying that exampleFunc must be of type ExampleFunction, and we're getting an error.

You'll notice that the error is split up into several indented sections:

Type '() => { something: { excellent: string; awesome: boolean; }; }' is not assignable to type 'ExampleFunction'.

---

Call signature return types '{ something: { excellent: string; awesome: boolean; }; }' and '{ something: { excellent: string; awesome: number; }; }' are incompatible.

---

The types of 'something.awesome' are incompatible between these types.

---

Type 'boolean' is not assignable to type 'number'.

This structure mirrors the structure of the code. Since we're comparing a function (exampleFunc) to a type (ExampleFunction), it starts with the function:

Type '() => { something: { excellent: string; awesome: boolean; }; }' is not assignable to type 'ExampleFunction'.

Why isn't my function assignable, TypeScript? Well, it's the return signature.

Call signature return types '{ something: { excellent: string; awesome: boolean; }; }' and '{ something: { excellent: string; awesome: number; }; }' are incompatible.

Great, that means I know that the return types are incompatible, not the parameters. So, what about the return type is incompatible?

The types of 'something.awesome' are incompatible between these types.

Aha, the nested property something.awesome. What's wrong with it?

Type 'boolean' is not assignable to type 'number'. 

There we go! We've finally got at the root of the problem. To fix it, we'd need to change awesome: true to awesome: 123 or similar.

TypeScript errors mirror the structure of the code that's being compared. The more complex the structure, the more complex the error.

Getting better errors

In the example above, we can see that the actual red line is some distance away from the cause:

const exampleFunc: ExampleFunction = () => {
  //  ^^^^^^^^^^^ Red line here...
  return {
    something: {
      excellent: 'str',
      awesome: true,
      //^^^^^ ...but this was the cause.
    },
  }
}

TypeScript had to produce a long error to explain the entire structure between the error's site its cause. We can reduce the complexity of the error by changing the way we assign the type:

type ExampleReturnType = {
  something: {
    excellent: string
    awesome: number
  }
}

const example = (): ExampleReturnType => {
  return {
    something: {
      excellent: 'str',
      awesome: true,
      //^^^^^ Type 'boolean' is not assignable to type 'number'.
    },
  }
}

Now, we're asking TypeScript to only check the return type of the function, not the entire function. That means it's comparing an object to an object.

Objects are less complex than functions (they can't be overloaded), so TypeScript can actually dispense with a long error and show only the line that matters:

Type 'boolean' is not assignable to type 'number'.

So, if you're looking to improve your TypeScript errors - aim to always compare objects to objects, instead of functions to functions. This means you should prefer typing return types and parameters over giving a type to the function itself.

Matt's signature

Share this article with your friends

`any` Considered Harmful, Except For These Cases

Discover when it's appropriate to use TypeScript's any type despite its risks. Learn about legitimate cases where any is necessary.

Matt Pocock
Matt Pocock

No, TypeScript Types Don't Exist At Runtime

Learn why TypeScript's types don't exist at runtime. Discover how TypeScript compiles down to JavaScript and how it differs from other strongly-typed languages.

Matt Pocock
Matt Pocock

Deriving vs Decoupling: When NOT To Be A TypeScript Wizard

In this book teaser, we discuss deriving vs decoupling your types: when building relationships between your types or segregating them makes sense.

Matt Pocock
Matt Pocock

NoInfer: TypeScript 5.4's New Utility Type

Learn how TypeScript's new utility type, NoInfer, can improve inference behavior by controlling where types are inferred in generic functions.

Matt Pocock
Matt Pocock