A Gentle Intro to TypeScript

Deno
5 min read3 days ago

(Originally published on deno.com/blog.)

For many who came to programming via JavaScript it is easy to fall in love with its low barrier to entry and versatile nature. JavaScript runs in a browser, can be written in notepad, is interpreted line by line and requires no complicated compilation or tooling. JavaScript has democratized software development by allowing developers from all backgrounds to pick it up and start coding. But with Javascript’s forgiving nature, comes increased chances to make mistakes and create bugs.

Consider this JavaScript program:

function add(a, b) {
return a + b;
}

console.log(add(1, 2)); // 3

This is a simple addition program that is designed to take two numbers and add them together, returning the result. Unexpected, often unpredictable things can start happening when we call this function with values that aren’t numbers.

We only really want to call this function with numbers — it doesn’t make sense otherwise — and in fact, if you pass values to it that are not numbers, you’ll end up with often bizarre and unpredictable results:

console.log(add(1, "2")); // 12
console.log(add(1, true)); // 2
console.log(add(1, "hello")); // 1hello

JavaScript is a dynamically typed language. This means that the types of data that we store in our variables can change at runtime. This can make it complicated to capture our intention in our JavaScript code — that our add function should only be called with numbers.

What we are seeing happen here is called type coercion — JavaScript automatically converts values from one data type to another. This can happen explicitly, through the use of functions and operators, or implicitly, when JavaScript expects a certain type of value in a particular context. Implicit type coercion can sometimes lead to unexpected results, especially in complex expressions. Here are a few cases:

console.log(1 + "2"); // "12" (number 1 is converted to string)
console.log("2" + 1); // "21" (number 1 is converted to string)
console.log("5" - 1); // 4 (string "5" is converted to number)
console.log("5" * "2"); // 10 (both strings are converted to numbers)
console.log(0 == false); // true (number 0 is converted to false)
console.log(0 === false); // false (no type coercion, different types)

Confusing right?!

In a JavaScript codebase, the best we can do is add some guard checks to our program that will throw errors if invalid values are provided. The problem with having only these runtime checks to protect us from errors is that we’ll often find out when a bug has been introduced into our code at the same time that our users do. So how do we protect ourselves from this often confusing JavaScript behavior?

TypeScript to the Rescue

TypeScript can help us to describe the intention of our code by being explicit about what types we expect where. TypeScript is a superset of JavaScript that adds additional type information to the language. When you compile TypeScript, JavaScript is produced that can run anywhere, so it’s really easy to incrementally use it to make your development experience better without having to rebuild all of your software.

Types allow you to catch errors in your code before it runs. If you accidentally assign a value of the wrong type to a variable, you’ll get a compile error. Types also make your code easier to read because you can explicitly state what kind of value you expect. We can also use tools to make types even more powerful. Code editors and IDEs have inbuilt tools to help autocomplete your code as you write when you’re using types correctly.

How do we add type annotations?

You add a type annotation in TypeScript by appending a colon (:) followed by the desired type after the function parameter’s name. If we were to extend our previous add example to convert it to TypeScript it would look like this:

function add(x: number, y: number) {
return x + y;
}

console.log(add(1, 2)); // 3

This is the simplest change we can make to our code to make it more robust. Now the compiler knows that any code that attempts to call add() with anything but two numbers will fail at runtime, so it will throw an error upon compilation to tell you your program isn’t valid.

If we try and call add with a number and a string, for example, we’ll get a compiler error:

TypeScript is smart enough to work out some of the types in your program for you based on the information you’ve given it, we call this “type inference”. If we take the above example and expand it out, adding all the type annotations that we can, it’d look like this:

function add(x: number, y: number): number {
return x + y;
}

const result: number = add(1, 1);
console.log(result);

Here we’ve explicitly added annotations for the function parameters, a return type of the add function, and the variable result. As a general rule, developers add “just enough” type annotations to tell the TypeScript compiler what’s going on, and let it infer the rest. In our original example, by adding type annotations to the x and y parameters, the TypeScript compiler could inspect the code and realize we only ever add two numbers, so would infer both the function return type, and the type of the variable result to be of type number.

Even if you only ever use TypeScript to annotate function parameters you’ll immediately remove an entire category of errors from your code.

Turning your TypeScript back into JavaScript

If your project is built using Node, you will need to add the typescript package and run the tsc compiler tool. We’ve written an introduction to configuring your TypeScript compiler.

Deno comes with the TypeScript compiler built right in, so if you’re using Deno, you do not need any other configuration or tools. Deno supports TypeScript out of the box, automatically turning TypeScript into JavaScript as we execute your source code.

On the client side, you can use Vite, a tool after our own heart, which does a similar transparent compilation for you, so if you’re a front end developer, you can still get TypeScript joy in your code.

Next up

In our next Deno Bite we’ll talk about common types that you’ll need in your TS code and how to use them to build more complex types to make your code clear and bug-free!

Are there any topics on TypeScript you would like us to cover? Let us know on Twitter or Discord!

--

--