A while back, while proselytizing making the case for TypeScript to a friend, I was asked “what kinds of bugs does it prevent?”

And honestly, it’s a good question.

A lot of blog posts come at type safety from a lot of different perspectives: comparisons to Haskell, comparisons to Java, comparisons to plain Javascript. But the key question is really “what value does this provide?”

Saving me from myself

The simplest and easiest-to-demonstrate value of TypeScript is catching simple type errors:

const someFunc = (name: string, age: number) => { return "whatever"; }
# ... a hundred lines later ...
someFunc(13, "Kevin")
# ^ Throws an error

In this case, I passed the arguments in the wrong order. It’s the sort of error that, in 95% of cases, I’d catch as soon as I ran the code. But instead, my IDE complains that 13 is not a string and age is not a number. Suddenly I’ve saved 60 seconds.

There are a bunch of these sorts of little errors that TypeScript can save you from:

  • Function arguments in the wrong order
  • Passing a numerical string instead of a number
  • Typo-ing an object key
  • Forgetting to handle the case when some variable is undefined
  • Typo-ing a string that’s being used as an enum
  • Trying to call something that’s not a function
  • Forgetting to actually call something that is a function
  • Returning the wrong thing in a callback

TypeScript might only save you 60 seconds, but it saves those 60 seconds 10-20 times a day, every day, for every developer on your team.

That said, while catching these little mistakes is the easiest way to demonstrate what TypeScript does, it’s probably also the least valuable. Many people see examples like this and think, “Eh? Doesn’t seem quite worth a new language.”

Saving me from the evils of Architecture

A more valuable use-case is to make indirection easier to deal with.

We’ve all probably experienced, one time or another, staring down the barrel of some function from elsewhere in the system and having no idea what it returns:

  1. I see const response = getResponse() and I want to know what response looks like.
  2. I trace getResponse back to a component prop
  3. I trace that component prop to a connect call to a higher order component
  4. I trace that HOC back to some glue code written by a long-gone dev who tried to standardize our HOCs
  5. I trace this function through the glue code and find it’s coming from a third-party library
  6. I trace it through that library and find out the library is only regurgitating a function we defined elsewhere
  7. I trace it to the bar and drown my sorrows because for the love of god I just wanted to know if response has an id or a userId field!

Sometimes you can work around this problem by just dumping response to the console, but you have no idea whether some of the keys on that object are null-able, whether there are different types of response objects, whether any of the string values are effectively enums, or anything else, really. You only know the value of the response object at one specific point in time.

On the other hand, if this is in a TypeScript codebase, you just hover over it in your IDE and…

getResponse's input type is shown in an IDE tooltip

…immediately see exactly what the shape of the response object is.

Saving me from refactoring

Recently I changed how our api was returning some value. When requesting a user from the backend, instead of returning:

{
  clinician: "Kevin Kuchta"
}

I wanted to return:

{
  clinician: {
    name: "Kevin Kuchta"
  }
}

We display the clinician associated with a user in a lot of places throughout the codebase. Further, because we pass user objects around a lot, it’s hard to identify them. I can grep the codebase for clinician, but that’ll turn up a ton of false-positives: it’s a pretty common term in a codebase built around teen therapy!

Now, this is not an unsolvable problem. The solution, without JS, would probably look like an hour or two spent tracing through the codebase, aided by grep, finding bits that needed to be updated. A robust test suite would help. Running the app and poking around would help. But what I’d end up with is a best-effort pull request that still probably has a 10% chance of having a missing clinician name hiding in some odd corner of the app.

The TypeScript solution was to simply change one type definition from:

type User = {
	clinician: string
}

to:

type User = {
	clinician: {
		name: string
	}
}

This resulted in a cascade of type errors highlighting exactly which places needed updating. The whole process took five minutes. But more important than the time savings, it ended in a pull request that I was 100% confident would not result in missing clinician names.

That’s the real value of TypeScript with regards to code changes like this: being able to make wide-scale changes with certainty.

Saving me from third-party libraries

The value of types is being able to have the compiler know exactly what data looks like throughout your program. But your compiler can’t help when data comes from outside your program: user input, network responses, or third-party libraries.

And while there’s no way (yet) to control exactly what data users or network requests will send into your code, you can get type safety with third-party libraries if those library authors have written a file declaring the types for each function or object the library exposes.

If I wrote a pure-javascript npm library that counted the number of letters in a string, I would include a .d.ts file in my library containing:

export declare const countLetters(input: string): number;

And now, even though my library was written in javascript, TypeScript developers using my library can have type safety with my library. Their compilers will know that countLetters takes in a string and outputs a number.

Now, you might think “ok, sure, but how many third-party library authors actually do that?” and it’s a fair question. It’d take a pretty gargantuan effort to get all third-party libraries to include type annotations like that.

And that gargantuan effort is the DefinitelyTyped project. Thousands of third-party js libraries have had type definitions created for them by the TypeScript community. In addition to the thousands of libraries that are written in TypeScript to start with, you can count on effectively any library you use having types.

And so, when every third-party library has type annotations, you can do a lot of fun things:

  1. What options can I pass in with this options hash? The library is poorly documented and/or I’m too lazy to go read the docs. TypeScript (with help from an IDE or editor) can tell me instantly:

    The options hash type for doCoolThing

  2. Does this library return a Promise?

    A type error being raised when treating the response as a promise

    (No it does not)

  3. The type of this library’s output depends on the type of the input I give it. Can I depend on the types of the output reflecting that?

    Type assertations on the output of a function that mirror the type put into it

Knowing (and having TypeScript know) the types of every input and output for every third-party library out there is super handy.

Save yourself

You should use TypeScript, full stop.

A classic tradeoff in software is “short-term velocity vs long-term benefits”. A robust test suite will take longer to write today, but save you time tomorrow. Code review takes more time than just merging everything immediately, but saves effort by catching bugs and disseminating knowledge.

Static type checking is traditionally one of these things. It takes longer to annotate your code with types, but it makes refactoring easier and helps you navigate the abstraction of a large codebase.

TypeScript breaks this mold. Sure, it speeds up code changes and helps deal with the indirection endemic to modern javascript—long term benefits—but it also saves time in the short term by preventing little 60-second bugs in the moment-to-moment of writing code. TypeScript starts paying dividends for any project that lasts more than a single day, much less weeks, months, or years.

At this point, I’d use it for pretty much any project at any scale.

Save us?

If you think TypeScript is cool (or even want to have a good-faith discussion on why it’s not), you should come work with us! Daybreak is hiring and although we believe strongly in TypeScript, we’d love to hear one way or another if you’re the sort of person who has thoughts on it!

Updated: