Typescript `never` type

💻Programming Language
javascript
typescript

Definition

[never] is a special type, the opposite of any. Nothing can be assigned to it.

Taken from here.

What is never[] when using strictNullChecks?

An empty Array (e.g. []) which cannot be typed gets the type never[]. Read about the corresponding PRs here and here.

In earlier TypeScript versions it used to get type any[] but that is not as safe as never[] because whereas never (and any) can be assigned to anything, anything can be assigned to any, but nothing can be assigned to never.

It indeed is a weird thing to see the never type here (see this TS Playground). never marks a value which never exists. But since no value can be assigned never, the type never[] prevents you from assigning a value to the array elements later - which in turn forces you to add a type assignment (or type assertion) to define the array type so one can finally assign values to it!

Example and fix

const [myArray, setMyArray] = setState([]);

Fix it by setting the type of the array with setState:

const [myArray, setMyArray] = setState<MyArrayType>([]);

Read more here about when to use never and unknown in TypeScript.

Functions which return the never type

Definition: A function that doesn’t explicitly return a value implicitly returns the value undefined in JavaScript.

A function that has a never return type never returns. It doesn’t return undefined, either. The function doesn’t have a normal completion, which means it throws an error or never finishes running at all (e.g. if the function body has while(true){}), for example:

function eventLoop(): never {
    while(true) {
       	//process events
    }
}
let loop = eventLoop();
loop = undefined // Error: Type 'undefined' is not assignable to type 'never'.

(Example taken from a blog post of Jan Bussieck).

Another article about never

Question concerning never

The error which got me to learn more about never:

Here Typescript says

Argument of type 'string[]' is not assignable to parameter of type 'never[]'. Type 'string' is not assignable to type 'never'

Question:

Typescript: Under strictNullChecks option, using concat in order to build an array leads to type never[] error.

Why is [node.getData().name] inferred to be of type never[]?

No overload matches this call.
  Overload 1 of 2, '(...items: ConcatArray<never>[]): never[]', gave the following error.
    Type 'string' is not assignable to type 'never'.
  Overload 2 of 2, '(...items: ConcatArray<never>[]): never[]', gave the following error.
    Type 'string' is not assignable to type 'never'.(2769)

In older Typescript versions (earlier than 2.8.1 - that’s how long ago I first encountered this! 😬) you might have gotten this similar error:

Argument of type 'string[]' is not assignable to parameter of type 'ConcatArray<never>'.
  Types of property 'slice' are incompatible.
    Type '(start?: number | undefined, end?: number | undefined) => string[]' is not assignable to type '(start?: number | undefined, end?: number | undefined) => never[]'.
      Type 'string[]' is not assignable to type 'never[]'.
        Type 'string' is not assignable to type 'never'.

What they all have in common: Type 'string' is not assignable to type 'never'..

const nodes = [{ name: 'a' }, { name: 'b' }];

const nodeNames = nodes.reduce(
  (acc, node) => acc.concat([node.name]),
  []
);

You can play around with this example in this typescript playground.

And why does the following, i.e. result: Array<String> fix it?

const nodeName = childNodes.reduce(
    (result: Array<String>, node) =>
        result.concat([node.getData().name!]),
    []
);

Answer:

The problem is the empty array which is passed as starting point of my reduce call. In the first iteration concat will be called like so: [].concat(...). [] has the inferred type ConcatArray<never> because with the strictNullChecks option its inferred type is not automatically any[]. So a fix is to explicitly cast it to any[], i.e.

const nodes = [{ name: 'a' }, { name: 'b' }];

const nodeNames = nodes.reduce(
  (acc, node) => acc.concat([node.name]),
  [] as any[]
);

A better fix is of course to set it to a better type via Array<String> as above.

However, you might ask why node.name is implicitly cast to never (and [node.name] to ConcatArray<never>) in the first place. I found an answer for this in this Github issue reply:

It has to do with type widening. Here (in the context of a reduce function) no contextual type for the empty array [] can be inferred.

Since we are in strictNullChecks mode, we cannot give [node.name] the value of undefined (which would be widened to any[] in non strictNullChecks mode.

More about strictNullChecks: When strictNullChecks option is false, an empty array [] could be widened to undefined since undefined is in the domain of all types (i.e. all objects can take on the value of undefined or null) and would thus be widened to any.)) because in strictNullChecks mode undefined is just undefined and can’t be widened to anything else.

But typescript can’t just set the type of node.name to undefined since it might not be undefined. So the typescript developers thought never would be the best choice here since it prompts you to make a better decision (by explicitly setting the type or by casting the empty array to something suitable) in case the array is going to be modified (i.e. will indeed get some values). BTW: This PR lead to this particular behavior

Them calling this type never[] really seems a bit misleading, because never actually means something different. There was also a suggestion to make it to a type like unknown[] which sounds more reasonable to me..

Perhaps one day typescript will be able to contextually infer that here [] is inside a reduce function and thus [] will really be modified and hence can give it an implicit type of whatever the output of the second argument function is. Until then we now know why it’s the way it is!

Open questions

  1. How does type widening work? I.e. why is [] widened to undefined and that then further widened to any as I write above and as it is stated here.

See ## Type Widening section in Typescript index article

  1. How does strictNullChecks mode work? I.e. why can’t undefined not be widened.

See my article about strict flags.

  1. Difference between String[] and string[]. See this discussion: https://stackoverflow.com/questions/14727044/typescript-difference-between-string-and-string
  2. Further thoughts on this question can be found here: https://stackoverflow.com/questions/54117100/why-does-typescript-infer-the-never-type-when-reducing-an-array-with-concat/62537717#62537717

never to conditionally allow properties of an object

This could also be called “use never to prune conditional types”. See the section named like this in this article about never and unknown.

Also see my SO question

Notes

Generally it’s better to use [ ...items ] instead of [].concat(items) because it’s quite inefficient. See this GitHub reply.

Discuss on TwitterImprove this article: Edit on GitHub

Discussion


Explain Programming

André Kovac builds products, creates software, teaches coding, communicates science and speaks at events.