That TypeScript argument...

4 min read

The TypeScript craze

There are many articles about how great or horrible TypeScript is, and they are full of comments debating the presented arguments.

Generally, the arguments for both "sides" are understandable. The people supporting TypeScript will mention everything about how great the DX is and how the learning curve is worth it, while those against it will say that the learning curve is too steep and the added value of TS is too low to consider it.

I'm ok with both opinions because, at the end of the day, they are just opinions, and as such, we should use the tool that is more convenient for our needs, and TypeScript is not for everyone. My problem is when the arguments are straw mans, and today I'll focus on one that I have seen a lot recently:

The argument

"TypeScript isn't useful because it doesn't do runtime type checking"

The problem with this argument is not that it's "against TS," but that it's asking something from TS that doesn't even exist in other typed languages like it. The argument ignores the difference between type checking and data validation.

The examples that folks use when they present this argument are usually APIs, file system access, user input, and other types of "unpredictable data." They say that even if we type those, we could get unpredictable data, so "TypeScript is useless." That's a "straw man" because it presents an external problem unrelated to type checking and then uses it as an argument against it.

TypeScript is a tool for developers, not for consumers. As such, it is at the same level that JSDocs, ESLint, prettier, and other dev tools like those are. It allows us to catch some errors earlier than production in our editor. Still, once it is "compiled," it is just JavaScript, so as developers, we are responsible for validating the data we "can't trust."

So, a function like this in TypeScript is just fine:

const add = (value2: number) => (value1: number) => value1 + value2;

Because when we try to use it, passing strings, for example, it will yell at us in development time. But now, if we do something like this:

fetch("https://swapi.dev/api/people/1")
	.then(response => response.json())
	.then(({ name }: People) => console.log(`Hello ${name}`))
	.catch(console.error);

We are doing things wrong because we are typing that response as People, and maybe we got something else from the API. In those scenarios, we have several options; one is to use something like Partial, which makes all the properties of an object optional, so TypeScript will tell us that name could be undefined:

fetchPromise.then(({ name }: Partial<People>) =>
	typeof name === "string"
		? console.log(`Hello ${name}`)
		: Promise.reject("Response is not of type People"),
);

Another solution is to have an abstraction layer on top of the API that generates the types and fallback values for us (we need to have a contract with our API, using stuff like swagger, GraphQL, or others). Finally, we can also use libraries such as Zod, which checks all for us at runtime while keeping it type-safe in development.

Now, going back to the argument: Saying that TypeScript is useless because it doesn't do validations at runtime only proves that the person making that statement missed the point of TypeScript. It would be the same as saying that ESLint is useless because it doesn't throw errors in production when the user doesn't follow a linting rule.

TypeScript is a tool to help in the development process. People that tried it know that even if it doesn't do validations at runtime, it is precious as a tool for refactoring, documentation, auto-completion, etc.

It would also be a false argument to say that the only way to achieve this is with TypeScript. We can use JSDocs and type our code with it, and thanks to the TypeScript server, we can get almost the same experience in vanilla JavaScript.

Conclusion

My idea with this article is to clarify why that argument is misinformed. The short version:

  1. TypeScript wasn't designed to do validations in production.
  2. All typed languages have the same issue; we should never trust stuff like user input, the file system, APIs, or any other "external source."

I hope that, at this point, I clarified why the initial argument is a straw man. Then, the fixed version would look something like this:

"TypeScript isn't useful because it doesn't do something it wasn't designed to do in the first place"

Which is ridiculous.

Share this article