An occasional user's thoughts on TypeScript

Motivation to publish

My exposure to TypeScript has been limited, but is likely to increase in the future. I’m bound to change my current opinions as my familiarity with TypeScript grows. The reason that I decide to hit “publish” despite - or rather because of - my limited competence with TypeScript is twofold:

  1. For future self-reference: Archiving my current thoughts will allow me to reflect on my learnings and the development of opinions.
  2. To the reader, my beginner’s perception might show an alternative view to that shared by TypeScript veterans1. At the same time, what I lack in depth of experience2 with TypeScript, I do have with JavaScript.

There’s plenty content out there evangelizing and/or teaching TypeScript. But I’ve stumbled across little nuanced discussion when to chose TypeScript over JavaScript and when not to. Or at least acknowledgements of TypeScript’ trade-offs. TypeScript is a tool, so yes, it comes with its own set of trade-offs. Nevertheless, most developers I spoke to about TypeScript shared the stance that the right time to chose TypeScript over JavaScript is always. Such absolutism makes me skeptical - the right answer to complex questions tends to be “it depends”. 😜 Coming from JavaScript, I perceive enough friction and learning curve steepness that I never felt the urge to adopt TypeScript as my primary tool of choice, and this feels like a perspective worth sharing:

If TypeScript never clicked with you, you’re not alone!

Learning TypeScript is hard - even (or especially?) if you know JavaScript

The points laid out are likely to be much less relevant or even moot for someone experienced with TypeScript. But getting there is - in my opinion - not easy, at least not when coming from JavaScript.

Over the past years I spent more time with TypeScript than I spent learning any individual JavaScript frameworks or new programming language like Swift. But compared to those, it feels like I progressed the least in my learning journey with TypeScript. To this day, TypeScript doesn’t feel intuitive to me. TypeScript is a superset to JavaScript, so it should be easy to write JavaScript and add a few required type definitions to satisfy TSC. In reality I keep finding that it doesn’t come natural to me: Typing well requires code patterns which clash with my JavaScript habits. JavaScript etched a mindset into my brain that goes “against the grain” of TypeScript. Instead of benefiting from already knowing ECMA script and only having to learn a few new keywords for types, knowing JavaScript is more of a hinderance than a boon for learning TypeScript in my case:

Reprogramming (the brain) is more effort than learning something new from scratch.

Where JavaScript is lean on (enforcing) concepts, writing TypeScript seems to require understanding quite a few on them to master moderately complex situations. Where JavaScript is permissive, TypeScript is restrictive. It’s natural for a typed language to be stricter - but I never experienced typing to come across as “needy” in Java or Swift. Maybe tacking static types onto a dynamic language results in an inherently flawed Frankenstein compared to a language designed to be typed from the ground up? Or does my perception that TypeScript is harder to learn than a new typed language just reflect my disappointed expectation that JavaScript mastery would transmit 1:1 to TypeScript without any pain or proper training?

Here’s why I perceive the learning curve to be steep:

Terminology and concept theory first

Learning TypeScript feels like “proper computer science”: One has to dig into terminology and grasp a bunch of concepts before being able to type seemingly simple every-day code snippets. For the self-thought web developer persona, learning about generics or magic terms like “type predicates”, “polymorphic this types” and “distributive conditional types” requires effort and dedication. The assumption that one can write the usual JavaScript code and attach obvious “quick-win” primitive types to the code, and that one would automatically and gradually learn TypeScript is - in my experience - false. Sure, one can get started that way, but the result won’t satisfy TSC in halfway decently strict settings, and there’s naught any benefit to that level of typing.

Getting to know type errors

When moving from JavaScript to TypeScript, one thing that’s new is TSC errors. TSC errors are not always self-explanatory. Errors describing type discrepancies can be multiple lines long, and finding the differences from the expected to the actual data shape can be tedious. Good luck finding which property from a 20 property object is optional in the supplied object, but required in the type. From my brief experience, TypeScript errors have gotten clearer and more “to the point” as TypeScript evolved. Still: You’ll find yourself googling TSC errors, and - more often - how to type a certain code snippet.

Finding help

And that’s tricky.3 The best way to find problems is to know the name of the relevant TypeScript concept. Unfortunately, plenty of a beginner’s problems are not knowing about these concepts and when to apply them. So yeah, don’t skimp on the theory and learn the “TypeScript lingo”, or you won’t be able to phrase your problem. It doesn’t help that TypeScript has gotten vastly more capable with every version. It’s not uncommon to find answers that are far from current best practice, because they stem from TypeScript 2.x times. Secondly, there’s often multiple ways to approach typing a specific situation. Power and flexibility are good for experienced practitioners, but can be confusing and lead to bad choices with beginners.

Code readability: new verbosity and complexity

With my limited exposure to TypeScript, when I read TypeScript code, I’m always getting the same first impression: Everything feels harder to understand.

The code is more verbose than required for human reading. Some of the verbosity helps human readers to understand the code better too4, but some of it is just there to satisfy the compiler.

The way that types are codified can itself feel complicated and hard to understand to a TypeScript novice. Someone who doesn’t know (some of the more advanced) TypeScript typing syntax ends up being confused when reading the same code they could easily understand if just the JavaScript parts of it were there.

Code is read more often than written. Therefore, that I perceive TypeScript harder to read than JavaScript is my primary reservation with TypeScript.

For me, TypeScript’s benefits have to offset downsides in reading and also my - at least initially - reduced productivity in authoring code:

Code authoring: helpful vs. interruptive

TypeScript code authoring is a two-edged sword:

On one hand there’s benefits from precise auto-completion and linting. These saves multiple seconds in frequent, small interactions from the get-go.

On the other hand, I tend to loose minutes up to hours when running into scenarios where I don’t know how to write (easy to understand) types that satisfy TSC. As novice, I’m much slower getting the same amount of functionality implemented than with JavaScript. More frustrating than the slowness:

My flow when authoring code is constantly interrupted by trying to find ways to satisfy the TypeScript compiler.

Especially when that same code is working perfectly for users, it feels like work is put into a worthless satisfaction of a machine instead of to the benefit of users. Extra brainpower is spent thinking about the type system. I assume that past a certain skill threshold, these stumble-stones are gone: Once writing type syntax doesn’t need dedicated thinking, states of flow might be interrupted less, e.g. because jumping between code to understand interfaces is reduced.

Shift left: Quicker feedback loops on defects

Above, I bitched about loosing minutes to hours needed to write good types as a beginner. But even these hours might be quickly re-gained:

Certain type of bugs are less likely to go unnoticed, due to TSC errors. Better yet: When TSC catches errors, it does so right away, while authoring the code, and while being in context. There’s no code change iteration quicker than whilst in the editor. For those of us who are not practicing TDD, finding common issues of badly handled data before having written a single unit tests speeds up the time to having reasonable confidence that the code is working. I don’t have sufficient experience to know whether having TypeScript reduces the need to write a part of the unit test cases. If so, that would be another relevant time saving.

Any bug that ends up as noise in a monitoring tool like Sentry needs to be analyzed. That’s time consuming, since a developer looking at a long list of different exceptions in the monitoring tool shuffled together needs to jump between exceptions, and between exception details and code, which the developer has likely not written themselves - costly! After the 1st pass of analysis, the issue needs to be put into an issue management system, and triaged. Then, the bug needs to be solved. If that’s another person at a later time, there’s another round of “self-onboarding” into the faulty piece of code. These “issue management” round-trips and the cost of missing context tend to accumulate to hours, even for the most trivial null pointer. We should not conflate static type safety with runtime safety and correctness. But there’s a correlation. And having to deal less with defect handling crap is pure value.

If TypeScript increases productivity and/or quality over JavaScript, isn’t it always the right choice?

A valid question to ponder is whether the type of quality issue prevented by TypeScript is the type of defect that is most costly in your program. In teams I worked in, most costly bugs tended not to be null pointers or other concerns of code correctness. Instead they were human mistakes not preventable with TypeScript:5 Overlooked edge-cases and forgotten use-case scenarios. They might not have been considered in the design, or lost in the translation to implemented code. These types of mistakes tend to be made consistently and are prone to group-think. They were overlooked by everyone, and hence forgotten to be tested.

Whereas above I state that any NPE avoided with TypeScript likely means a time saving compared to not having TypeScript and having the NPE, it’s still true that a team has a finite total capacity for changes. So even if TypeScript is a net-increase of productivity and/or quality, the quick wins to raise them might be organizational and team process related. Hence, choosing to spent some of your “change budget”6 on migrating from one technology (JavaScript to TypeScript) might have a higher opportunity cost than its gains. It might be a loss of productivity or quality of the team’s output compared to their potential. Yes, “productivity” and “quality” are squishy, multi-dimensional terms.

Migrating an existing code base

Strategy

If your team concludes that the expected gains in productivity &/ quality surpass the migration cost at the relevant time horizon, there’s organizational questions to answer, which impact whether and how a migration to TypeScript should happen: What people on the team know TypeScript best? How do we scale their knowledge to the rest of the team? Is structured training needed, or can we get away using existing processes like pair programming and PR reviews? What’s the expected output of the team in the time during the migration? Can we afford to move at a slower pace for the time of the migration? How do we migrate? Is the code base small enough to migrate everything at once? Or do we establish rules like that every new file should be TypeScript, or that every file that is changed should be migrated? What strictness do we want to enforce initially, and do we want to tighten it over time?

Getting started: easy and expected

When starting the migration, things start simple: Add a tsconfig with JavaScript interop enabled, hook TSC into your build process. Done. You’re ready to (gradually) migrate to TypeScript, according to the chosen migration strategy.

Now the team starts to pay down the pre-assessed investment cost. The reduced productivity when authoring new code described above. Time spent on training and learning. And every time an existing file’s ending is changed to .TypeScript, TSC will ask for more specific types and raise issues that need to be addressed manually.

The long tail

Then there’s the long-tail of considerations flowing from the decision to migrate to TypeScript:

Type definitions are extra code. How should that code be structured? Where should it be put? What naming scheme applied?

Only once a part of the system is statically typed, it becomes apparent with how much other code and systems it interfaces with.

Ideally, to run the safest system, all these systems should be correctly typed, and types auto-updated as these interfaces evolve. Some of that is solved with extra work adding extra tooling, e.g. to:

But you’ll run into plenty of areas where perfect auto-maintained typing is not easy to reach, or out of your power:

Don’t underestimate migration costs

The cost of this extended setup required before TypeScript’ benefits cover a broad part of the system is easy to under-estimate.

I’m not a fan of the extra NPM-heavy tooling required to help to type everything, and the tooling and process required to keep types up to date and in-sync either. These increase your system’s surface area, and - after the initial setup costs - mean additional continuous maintenance.7

The biggest one got to be change management though. Getting the buy-in. Coordinating the migration. Keeping everyone aligned. And, most crucially, ensuring that everyone is assisted in learning TypeScript. I’ve seen TypeScript forced upon a team by a loud, freshly-joined minority without any change management, and I can tell you, it ain’t pretty. Without proper investment into the people, a TypeScript migration is bound to incur costs like churn and burn-out, vastly exceeding the benefits.

Opinions on when to use TypeScript

Cases where I’m leaning against

But let’s say that the majority of the team is motivated to migrate an existing code base to TypeScript. I think there’s still cases in which not to migrate to TypeScript:

The 1st one is when there’s insufficient knowledge of TypeScript in the team. In this case I believe that going with TypeScript is going to be a hard road with high opportunity costs and plenty of early mistakes. There needs to be a few people with experience, who can up-skill the team.

Secondly, migrating an existing code base in a phase when the team can’t afford to slow down or halt feature work for a while. E.g in a start-up which didn’t start with TypeScript, which is still finding product-market-fit, and churning out features and changing things at a rapid pace.8

Thirdly, simple representational websites which would otherwise not require tooling, are an obvious “no”.

But I’d go further and claim that using TypeScript on the front end is generally lower in value than in other parts of the stack: Not all UI libraries / frameworks make typing easy9, and - in my experience - the impact of bugs related to type-mismatches in front ends tends to be limited. A null pointer in an SPA is not a glorious user experience, but can often be remedied with a reload - no big harm done. Front ends are inherently more fuzzy about strictness and correctness, since they deal with dynamic, user-generated content, invalidated inputs, and a big plethora of dynamic client capabilities and needs, be it in network conditions and reliability, OS system and browser (versions) and their idiosyncrasies, viewport sizes, input capabilities, needs for localization…10

Situations in which to use TypeScript

There’s situations in which I think that TypeScript is the right choice too:

When providing a programmatic interface to 3rd parties, that interface should be codified. That’s a clear “yes” in favor of using TypeScript when authoring an NPM package.

When working on an API, and/or interfacing with a database, using static typing also makes sense. In these cases, in- and outputs anyway have a defined type contract, and TypeScript types can usually be auto-generated with tooling. Ensuring that this type contract is kept in both directions through the code flow should usually not require too much work. The characteristics of being an API and/or interfacing with a DB probably entail most back end systems.

When building a large code base, and/or working in a large team or organization. Under these circumstances it’s unlikely that one person can have a good grasp of the full system in their head, so getting real time validation on the large count of interfaces is more valuable. Big code bases tend to be old, and hence experience developer churn over the lifetime, which further reduces the contributor’s knowledge about how the system works. This point would seem to cover most open source too, as (new time) contributors are foreign to the code, and come and go.

A question of personality?

Independent of “objective” factors influencing how much sense TypeScript makes in a team’s situation, the culture of the team is probably the biggest deciding factor in favor or against TypeScript.

A senior JavaScript candidate I once interviewed said something along the lines: We’re creatures of our upbringing:

If your first programming language was typed, you’ll always prefer that. If it was dynamic, you’ll always gravitate to dynamic languages.

People having “grown up” with types feel unsafe without them. And people used to dynamic languages feel less free when working with typed languages.

There’s something to this hypothesis of two different “schools of mind”. Maybe it’s not even about the upbringing, and instead about how risk-averse someone is?

I heard another job candidate who was used to TypeScript saying that he was “flying blind” when changing unknown code without types, claiming that the lack of types made the code hard to understand, and impossible to tell whether changes had bad side-effects. He was lacking a safety net that would at least warn him about mistakes. He was expecting the computer to tell him what’s allowed and what’s not.

Whereas I think that the act of coding consists of reading and understanding code before changing it. Even if I can place my cursor in the middle of an unknown function and have the computer tell me what the “interface boundaries” to the surrounding code are without me reading it:

I don’t want to write a single line of code without understanding how the surrounding works.

It’s the full context that allows me to build the new code in a way that’s “native” to the existing structure, to build the best version of the new code, or to even improve the structure whilst making my change. The full understanding is what allows me to come to creative or elegant solutions that go past fulfilling the pre-defined requirements that the new code needs to fulfill.

A team containing people of both “schools of mind” will experience internal frictions around topics like JavaScript vs. TypeScript. A coherent team however will either want or not want to use TypeScript over JavaScript, and trying to impose the opposite does not make sense.

Factors boosting TypeScript

With these thoughts about programming culture in mind, I wonder: Why has TypeScript grown popular? Is it part of JavaScript’ evolution to a “real” programming language? The phenomenon of SPAs with large JavaScript code bases? The rise of JavaScript in the back ends? The strong reliance on OSS and 3rd party dependencies? A resurgent fashion in favor of typed languages? Does it bundle the fervor of all developers working in web development who always had a preference for typed languages? Is it that new developers are being educated with TypeScript instead of JavaScript and that TypeScript is the “new default”? Or because TypeScript has the attractiveness of being “the new kid on the block”, which makes attracting developers easier for companies using it? (Some of) all of the above?

Whatever it is: Developer surveys show high satisfaction of developers who participate in these surveys with TypeScript. Which makes it likely that these satisfied developers (try to) carry TypeScript into new projects they join. Which fits to my perception: Developers with TypeScript experience tend to evangelize it! It’s a dynamic which suggests continued, maybe even exponential growth of TypeScript. If it’s not yet, it will soon be “the default distro” of JavaScript.11

The future

The best of both worlds

TypeScript is pushing further into the mainstream through simplification. The type annotations proposal would add a better, standardized JSDoc to ECMAScript. This would allow to conquer use-cases without build step and bake the core of TypeScript forever into the web platform. Once something’s part of the web platform, browsers stay compatible with it forever (a small risk for the ever evolving TypeScript syntax?). Without tooling requirement, TypeScript is bound to face less opposition by platform purists, and the friction to get started and scale into full-blown TypeScript will be vastly reduced.

Intelligent tooling

The idea that the editor should provide type hints and warnings makes sense to me. A typed code-base provides the desired dev experience today. But I’m lazy - it feels tedious for me to have to provide types myself.

With recent launches of “AI” based code generation tools I wonder:

How hard is it to build “intelligent” LSPs, which can infer intended types from the code and find type issues and interface compatibilities by simulating fuzz testing?

That’s the future I’m in for!12 Dev tooling today is surprisingly primitive in light that its users are the ones using and capable of building it. The programs we build using the primitive tooling could not get away with such a bad UX. Intelligent LSPs to me sound like a more desirable and archivable intermediate goal compared to full autonomous code generation.

Footnotes

  1. The points laid out in this post are likely to be much less relevant or even moot for someone experienced with TypeScript.

  2. When I started developing, Angular was the new star in the sky. It was years before TypeScript gained traction. At my 1st job I wrote typed Java code for back end systems and untyped React and JavaScript code. In 2017 I wrote a complex SaaS billing back end micro-service in Node.JavaScript with TypeScript 2.x, at which point TypeScript felt limiting due to it not being easy to type many code patterns. In 2019 I experienced Flow in a smallish React code base, and everyone happily agreed to quickly drop it. When I joined Bird in 2020, the code base was not typed. We repeatedly explored migrating at least our Fastify Node.JavaScript API to TypeScript, but always concluded that the immediate (opportunity) costs were higher than short to mid term value. I explored to author the Bird SDK in TypeScript, but dropped a nearly finished migration PR to ship the SDK quickly, and never got back to the migration.

  3. Finally I can emphasize with Junior developers again. You can’t just paste your non-working code into Google. You need to understand your code well enough to be able to know the terms for the abstract concepts used by your non-working code.

  4. E.g. knowing the data shape of parameters of a function called from some other piece of code currently not in view.

  5. Your mileage will vary. When working on systems possibly impacting lives, or systems directly sealed to money flow, any code correctness issue is one too many.

  6. Akin to “Innovation tokens”.

  7. There’s upsides on the tooling front though, which have to do with the fact that any TypeScript project runs a compiler: Configuring TypeScript is friendlier than to configure Babel and Core.JavaScript. Staying on an up-to-date version of TypeScript allows to use the latest (or even only proposed) ECMA script syntax right away.

  8. A start-up might have the benefit of having more confidence that the wreckage of moving fast is somewhat managed, if its code-base was started with TypeScript. This confidence might allow it to move faster. By that logic, if the codebase is started by someone who is as productive with TypeScript as with JavaScript, starting out with TypeScript would be an advantage paying out quickly.

  9. E.g. Vue.JavaScript 2, Svelte.

  10. Maybe this inherent chaos is why the front end developers I know are more relaxed and’t don’t fall for the illusion that the own system can ever be perfect. Whereas the typical back end developer I know seems to believe in and strive for an unflawed, correct system, and be a stickler about it.

  11. Supporting evidence: Plenty job offers with a headline referencing JavaScript, state in the descriptions that the code base is in TypeScript (and require experience with it). Or does this just suggest that companies know that more people know JavaScript than TypeScript still, and - without openly stating it - suggest that TypeScript can be learned on the job?

  12. Isn’t the // @TypeScript-check comment in JavaScript files a 1st version of this?