Nomadic cattle rustler and inventor of the electric lasso.
Company Website
Follow me on twitter
Contact me for frontend answers.

Is typescript worth it?

January 05, 2020

I initially wrote this post at logrocket.

Before I start, I would like the jury to know that I am, for the most part, a typescript fan. It is my primary programming language for frontend react projects and for any backend node work that I do. I am on board, but I do have some nagging doubts that I would like to discuss in this post. I have programmed in nothing but typescript for at least 3 years now across numerous contracts, so typescript is doing something right or is fulfilling a need.

Typescript has defied some insurmountable odds to become mainstream in the realm of frontend programming. Typescript is no.7 in this stackoverflow post that lists the most in-demand programming languages.

Before typescript existed, the following good practices should be in place for any software team irrespective of size:

  • Well written unit tests should cover as much of the production code as is deemed reasonable
  • Pair programming. An extra set of eyes can catch a lot more than syntactical errors
  • A good PR process. Proper peer reviews catch many bugs that a machine cannot
  • use a linter such as eslint

Typescript can add an extra layer on of safety on top of these, but I feel it is coming last by a country mile in the above list.

Typescript is not a sound type system

I think this is possibly the main problem with the current incarnation of typescript but first of all, let me define what sound and unsound type systems are.

Soundness

A sound type system is one that ensures your program does not get into invalid states. For example, if an expression’s static type is string, at runtime, you are guaranteed only to get a string when you evaluate it.

In a sound type system, you should never get into the position at compile-time OR RUNTIME that the expression does not match the expected type.

There are of course degrees of soundness and soundness is open to interpretation and typescript is sound to a certain degree and catches the following type errors below:

// Type 'string' is not assignable to type 'number'
const increment = (i: number): number => { return i + "1"; }

// Argument of type '"98765432"' is not assignable to parameter of type 'number'.
const countdown: number = increment("98765432");

Unsoundness

Typescript is entirely upfront about the fact that 100% soundness is not a goal, and NON-goal no.3 on the list of NON-goals of typescript clearly states:

Apply a sound or “provably correct” type system. Instead, strike a balance between correctness and productivity.

What this means is that there is no guarantee that a variable has a defined type during runtime. I can illustrate this with the following somewhat contrived example:

interface A {
    x: number;
}

let a: A = {x: 3}
let b: {x: number | string} = a; 
b.x = "unsound";
let x: number = a.x; // unsound

a.x.toFixed(0); // WTF is it?

The above code is unsound because a.x is known to be a number from the A interface. Unfortunately, after some reassignment shenanigans, it ends up as a string, and the following code compiles but errors at runtime.

Unfortunately, the expression below compiles without any error.

a.x.toFixed(0);

I think this is possibly the biggest problem with typescript, as soundness is not a goal. I still encounter many runtime errors that are not flagged by the tsc compiler that would be if typescript had a sound system. Typescript has one foot in both the sound and unsound camp with this approach. This halfway house approach is enforced with the any type, which I mention later.

I still have to write just as many tests, and I find this frustrating. When I first started getting typescript, I wrongly concluded that I could stop the drudgery of writing so many unit tests.

TypeScript challenges the status quo, claiming that lowering the cognitive overhead of using types is more important than type soundness.

I understand why typescript has gone down this path, and there is an argument that states that the adoption of typescript would not have been as high if a sound type system is 100% guaranteed, this is disproved as the dart language is finally gaining popularity as flutter is now widely used. Soundness is a goal of the dart language as is talked about here.

Unsoundness and the various ways that typescript exposes an escape hatch out of strict typing make it less effective and unfortunately make it better than nothing right now. My wish is that as typescript gains popularity, more compiler options are available to enable power users to strive for 100% soundness.

Typescript does not guarantee any runtime type checking

Runtime type checking is not one of typescript ‘s goals, so this wish will probably never happen. Runtime type checking would be beneficial when dealing with JSON payloads returned from API calls, for example. A whole category of errors and many unit tests would not need to exist if we could control this at the type level.

We cannot guarantee anything at runtime so this might happen

const getFullName = async (): string => {
  const person: AxiosResponse = await api();
  
  //response.name.fullName may result in undefined at runtime
  return response.name.fullName
}

There are some supporting libraries like io-ts, which is great but can mean you have to duplicate your models.

The dreaded any type and strictness an option

The any type means just that, and the compiler allows any operation or assignment.

Typescript tends to work well for small things, but there is a tendency for people to use the any type for anything that takes longer than 1 minute to type. I recently worked on an angular project and saw a lot of code like this:

export class Person {
 public _id: any;
 public name: any;
 public icon: any;

Typescript is letting you forget the type system.

You can blast the types out of anything with an any cast.

("oh my goodness" as any).ToFixed(1); // remember what I said about soundness?

The strict compiler option enables the following compiler settings which do make things more sound.

  • —strictNullChecks
  • —noImplicitAny
  • —noImplicitThis
  • —alwaysStrict

There is also the eslint rule @typescript-eslint/no-explicit-any.

The proliferation of any can destroy soundness in your typing.

Conclusion

I must reiterate that I am a typescript fan, I use it on my day to day job, but I do feel it is coming up short and the hype is not entirely justified. Airbnb claim 38% of bugs could have been prevented with typescript. I am very sceptical of this precise percentage. Typescript is not turbocharging existing good practices. I still have to write just as many tests. You could argue, I am writing more code, and I might have to write type tests. I am still getting unexpected runtime errors.

Typescript offers above basic type checking, but soundness and runtime type checking are not goals, and this leaves typescript in an unfortunate halfway house with one foot in a better world and one where we currently are.

Where typescript shines is with good IDE support like vscode where we get visual feedback if we mistype something. typescript error in vscode

Refactoring is also enhanced with typescript and breaks in code such as changes in method signatures are instantly identified when running the typescript compiler against the modified code.

Typescript has enabled good type checking and is definitely better than no type checker or just plain eslint but I feel it could do much more and sufficient compiler options could be exposed to those of us who want much more.


Paul Cowan

Nomadic cattle rustler and inventor of the electric lasso.
Company Website
Follow me on twitter
Contact me for frontend answers.