standel.io

What is TypeScript & why should I use it?

Ethan Standel 9 min read
Published 9.22.22 // Updated 9.26.22
FAQ
TypeScript
JavaScript
DX

This article is not meant to break any new ground, it's just meant to answer the title question. I'm writing this because I've answered this question for dozens of people before and so I think I've developed a good & well detailed answer. I have also seen a lot of answers that I feel fail to explain TypeScript to the type of developer who is usually asking this question. So if you are a JavaScript developer, who hasn't worked outside of JavaScript or has only worked with dynamically typed languages, then this post is for you!

What is TypeScript?

TypeScript is a superset of JavaScript, which is to say that all JavaScript can be considered valid TypeScript. TypeScript itself compiles back down to JavaScript and adds no behavior to your code at runtime. What it does add is a compiler that scans your code and identifies potential bugs. Here's some TypeScript code that declares a string with the reference identifier hello.

let hello = "world";

Looks pretty familiar, right? Even though you often don't need to, TypeScript gives you the ability to make type declarations. In this example, hello is declaratively identified as type string.

let hello: string = "world";

This piece of code acts exactly the same as the previous example, because the type string was inferred by the value that was assigned to hello. So why would we use that syntax if it doesn't add anything? We'll get there.

The advantage you get from TypeScript is that if you use hello in a way that you couldn't use a string, then you will likely see in your editor if you've made a mistake. So for instance, if you think hello is an object and you try to access arbitrary properties off of it, then you will get a little red squiggly under that code. If you hover over it you'll get an error message describing what is wrong with that code.

hello.somePropertyThatStringsDontHave;
   // ^ TS Error: Property 'somePropertyThatStringsDontHave' does not exist on type 'string'.

TypeScript will also permanently recognize that your variable is the type you've declared or that it has inferred. So later in your code, if you were to assign that variable like this, then you'd get an error.

hello = 123;
// ^ TS Error: Type 'number' is not assignable to type 'string'.

But what if you did want hello to be able to be a string sometimes and also a number other times. JavaScript lets you do that, so why should TypeScript hold you back? TypeScript will actually let you do this! But in cases like that, you have to declare the type you want hello to be, if the type that would be inferred from the initial assignment doesn't tell the full story. I told you we'd get there.

let hello: string | number = "world";

Now you can reassign hello to a number or a string as the | (pipe) in the type declaration says that your value can be this or that. But you will have to be careful, because the TypeScript compiler will now limit how you use that variable. For instance, if you wanted to perform a slice operation on hello as you know you could with a string, TypeScript won't let you.

hello.slice(-1);
   // ^ TS Error: Property 'slice' does not exist on type 'string | number'. Property 'slice' does not exist on type 'number'.

That warning may seem annoying. You're pretty sure hello is a string here! But TypeScript is protecting you from yourself. When the compiler yells at you, just listen to it. You should correct that code to this.

if (typeof hello === "string") {
  // TypeScript now knows hello is a string in this block
  hello.slice(-1);
} else {
  // And it knows that hello is a number in this block
}

Even though TypeScript doesn't actually add any runtime logic or syntax to JavaScript code, it can interpret JavaScript code and infer types. So this syntax, typeof hello, is actually a runtime JavaScript operation that returns a string of value "undefined", "object", "boolean", "number", "bigint", "string", "symbol", or "function". Forcing TypeScript to identify types in more depth than these primitives can get more tricky, but this post is only intending to go over some of the basic concepts.

Why is TypeScript important?

If you make mistakes with types, your code will also fail to compile. This is TypeScript not letting you make some of the most common mistakes that developers make. Without TypeScript, you're left to discover these bugs at run-time as opposed to compile time. Discovering bugs at run time also means that it may be your users finding your bugs, rather than you.

It also makes pulling in new libraries or using new DOM APIs much less intimidating.

import React from "react";

React.
   // ^ suggestions will appear here in your editor

Once you hit . on this block of code, you'll get a series of suggested actions & properties you can use in React in this example.

If you are a JavaScript developer and you say, "well my editor already does this sometimes!" That's because the libraries that you are using are exporting their TypeScript types and your editor is using them automatically to help you with your JavaScript. And then it will also help you with any types that it deems "statically analyzable" (these are the types that TypeScript would infer for you). But if you actually use TypeScript, you can get these advantages all the time. For instance, there's no way to tell the intended type for arguments of a function in JavaScript because they're not instantiated. But you can strongly type function arguments in TypeScript!

When should I use TypeScript instead of JavaScript?

You should use TypeScript in every project that you intend to work on and update regularly and deploy to users. It is invaluable even if you are the only developer as it will help you understand your past intentions with code. It's more important than tests & does a lot of what tests do, but faster and with less work on your part. It provides the most basic level of application stability with its compile step.

Conclusions

I know that a lot of developers start playing with TypeScript and find it annoying or nagging, like an overbearing parent. But like an overbearing parent, TypeScript really does know what's best for you and it's only trying to help. When it nags, you should listen. There are several tricks you can use to override TypeScript's safety (that I refuse to give away in this post), which you will eventually need. However, as a default, you should try to teach yourself to understand the errors as they're given to you, and that will make you a better developer.

Update

I got more pushback on this article than I expected. I just wanted to address some of the arguments that were made by seasoned JavaScript developers who are still hostile to TypeScript.

I don't want to have to add a build pipeline

If you're not already using a build pipeline, then you should consider doing so. If you're just deploying hand written code, then you're sending your users far bigger JavaScript bundles then you need to. At the very least, you should be uglifying & minifying your code. There is far more value to be gained in having a build pipeline & true production builds than just TypeScript, but it does fit nicely into most existing pipelines.

And if it's the compile time you're worried about; using modern tooling like esbuild, Vite, Snowpack, or SWC, gives you compile & bundle times that are imperceivably fast with or without TypeScript (like under 1/2 second total). The stability provided is definitely worth fractions of fractions of a second.

Setting it up is annoying

If you're building a web app, then the de-facto project scaffolding tools for all major frameworks support TypeScript out of the box at no extra effort: React (Vite, create-react-app), Next (SWC/custom-CLI), Vue/Nuxt (Vite), Svelte/SvelteKit (Vite), SolidJS/solid-start (Vite), or Angular (ESBuild/custom-CLI).

If you're building an app in Node, in a lot of cases you can just use ts-node pretty safely. If you're just running on a server, then ts-node has an incredibly negligible compilation time at initial startup. If you're building out to something like Lambda services, then it'll be a little more tricky; but properly scaling that kind of deployment is a pain with or without TypeScript.

You can just do this with JSDoc

The JSDoc syntax is both far less powerful/reusable/sharable than TypeScript, but it's also far more verbose. For those who don't like TS due to it's verbosity, I doubt that this

/**
 * @typedef {Object} Party
 * @property {object} defaults
 * @property {number} defaults.players
 * @property {string} defaults.level
 * @property {object} defaults.treasure
 * @property {number} defaults.treasure.gold
 */

/** 
 * @param {Party} config
 */
const getPartyGold = (config) => {
  return config.defaults.treasure.gold;
}

could possibly be a preferred solution over this.

type Party = {
  defaults: {
    players: number;
    level: string;
    treasure: {
      gold: number;
    }
  }
};

const getPartyGold = (config: Party) => {
  return config.defaults.treasure.gold;
}

Alongside that point, I cannot be clear enough about this: TypeScript is not just autocomplete. It's meant to be a validating step in your build process to prevent bad code from making its way through the pipeline by any developer, even if they want to write all their code in Notepad. Unless you add a build step that is validating your JSDoc comments to be accurate, then you're not actually getting anywhere near the same levels of protection.

It just slows me down when I'm building things

If TypeScript is giving you a lot of grief, it could be a code-smell to how you're managing data in your applications. Generally, outside of function arguments and API request & response bodies, there shouldn't be too much manual typing required in applications using TypeScript.

I can confirm that providing strict typing in a very generic-driven library can be hard & messy. But because that library is meant for others to use, there's no way I wouldn't put types there, because they are needed to help the dependent developer understand how the data is managed, and the types can protect against certain potential footguns.