Overcoming TypeScript’s type guards limitations in the nested scope

TypeScript has a cool feature – control flow analysis which allows to narrow down the variable’s type inside the control structure block. Like in this example:

Very different from C# or Java, right? It made me thrilled when I first learned about it. It has a limitation, though:

It is a bit puzzling at first glance. Why? How in the world x is not a number in the else block? The root cause here is that TypeScript doesn’t know what map function is going to do with the lambda function passed to it. If map will invoke the lambda right away – that’s fine, but what if it, for example, will call setTimeOut and pass the lambda function into it? In such a case i => i * x will be executed after the timeout, well after control flow will leave the else block. Because of this possibility TypeScript takes a pessimistic position and considers x to be number | string inside lambda function i => i * x.

How to deal with this problem? There are several approaches. First, you can tell TypeScript that nothing bad is going to happen by limiting the scope of x. For now, it is global. Limiting it to the function, which does not change x fixes the problem:

Second, introduce another variable of needed type inside else block. Since TypeScript now knows exactly the type of the variable, it will not complain:

Finally, let TypeScript know that you are not going to change x after it’s initialization and it will calm down:

I have introduced someFunciton only to stop type inference mechanism to infer the type of x from the assignment. If I wrote const x: number | string = 5, for example, the type inference mechanism would have inferred the type of x to be number (never inside if block) even though I have declared it to be number | string.


Leave a Reply

Your email address will not be published.