Back

javascript-conference.com

TypeScript's Limitations and Workarounds

12/16/2024Updated 3/24/2026
https://javascript-conference.com/blog/typescript-limitations-workarounds/

div TypeScript, while a powerful programming language, has limitations that arise from its type system's attempt to manage dynamically typed JavaScript code. From handling return types and function expressions to the behavior of else statements, developers often encounter challenges when working with TypeScript files. Issues can emerge at compile time, especially when using generic functions, creating an instance, or managing type information. This article explores the blind spots in TypeScript, such as handling function objects, top-level constructs, and dynamically typed scenarios, offering insights into workarounds and practical solutions. … This issue extends beyond people to include their tools and machines. ... TypeScript is no exception: while it can accurately describe 99% of JavaScript features, one percent remains beyond its grasp. This gap doesn’t only consist of reprehensible anti-features. Some JavaScript features that TypeScript doesn’t fully understand can still be useful. Additionally, for some other features, TypeScript operates under assumptions that can’t always align with reality. Like any tool, TypeScript isn’t perfect; and we should be aware of its blind spots. This article addresses three of these blind spots, offers possible workarounds, and explores the implications of encountering them in our code. … In web development, where developers don’t have to manually create every object from a class constructor, this rule is very pragmatic. On one hand, it results in relatively minor semantic errors (Listing 3), but on the other, it can also lead to more significant pitfalls. **Listing 3:** Structural subtyping triggers an error … Regardless of how you approach it, rejecting parameters that are subtypes of a given type or enforcing an exact type at the type level isn’t possible. TypeScript has a blind spot here. But is this truly a problem? … In our case, the key factor is that a *Set* and a *WeakSet* have very different semantics, even though the *WeakSet* API is a subset of the API of *Set*. In TypeScript’s type system, this means that *Set* is evaluated as a subtype of *WeakSet*, leading to the assumption of a relationship and substitutability where none exists. This blind spot in the type system leads us to solve a problem that isn’t actually a problem at all, and which we ultimately can’t resolve, especially at the type level. … Imperative programming doesn’t get any easier than this: you take a bunch of variables and manipulate them until the program reaches the desired target state. But as we all know, this programming style can be error-prone. Every *for* loop is an off-by-one error in training. So it makes sense to secure this code snippet as thoroughly as possible with TypeScript. … ### The problem with the imperative iteration bBefore we add *Combine<K, V>* to the signature of *combine(keys, values)*, we should fire up TypeScript and ask what it thinks of the current state of our function (without return type annotation). The compiler is not impressed (Listing 23). **Listing 23:** Current state of combine() … The truth is, nothing is correct. The operation that *combine(keys, values)* performs is not describable with TypeScript in the way it’s implemented here. The problem is that the result object *obj* mutates from *{}* to *Combine<K, V>* in several intermediate steps during the *for* loop, and that TypeScript doesn’t understand such state transitions. The whole point of TypeScript is that a variable has exactly one type, and it can’t change types (unlike in vanilla JavaScript). However, such type changes are essential in scenarios where objects are iteratively assembled because each mutation represents a new intermediate state on the way from A to B. TypeScript can’t model these intermediate states, and there is no correct way to equip the *combine(keys, values)* function with type annotations. … ### What to do with intermediate states that can’t be modeled?o The TypeScript type system is a huge system of equations in which the compiler searches for contradictions. This always happens for the program as a whole and without executing the program. This means that, by design, TypeScript can’t fully understand various language constructs and features, no matter how hard we try. ... The more pragmatic solution is to accept the possibilities and limitations of our tools and work with what we have. Unmodelable intermediate states are bound to occur when writing low-level imperative code. If the type system can’t represent them, we need to handle them in other ways. Unit tests can ensure that the affected functions do what they’re supposed to do, documentation and code comments are always helpful, and for an extra layer of safety, we can use runtime type-checking if needed.

Related Pain Points2