Was this page helpful?

The Basics

Welcome to the first page of the handbook. If this is your first experience with TypeScript - you may want to start at one of the 'Getting Started' guides

Each and every value in JavaScript has a set of behaviors you can observe from running different operations.That sounds abstract, but as a quick example, consider some operations we might run on a variable namedmessage.

js
// Accessing the property 'toLowerCase'
// on 'message' and then calling it
message.toLowerCase();
// Calling 'message'
message();

If we break this down, the first runnable line of code accesses a property calledtoLowerCase and then calls it.The second one tries to callmessage directly.

But assuming we don’t know the value ofmessage - and that’s pretty common - we can’t reliably say what results we’ll get from trying to run any of this code.The behavior of each operation depends entirely on what value we had in the first place.

  • Ismessage callable?
  • Does it have a property calledtoLowerCase on it?
  • If it does, istoLowerCase even callable?
  • If both of these values are callable, what do they return?

The answers to these questions are usually things we keep in our heads when we write JavaScript, and we have to hope we got all the details right.

Let’s saymessage was defined in the following way.

js
constmessage ="Hello World!";

As you can probably guess, if we try to runmessage.toLowerCase(), we’ll get the same string only in lower-case.

What about that second line of code?If you’re familiar with JavaScript, you’ll know this fails with an exception:

txt
TypeError: message is not a function

It’d be great if we could avoid mistakes like this.

When we run our code, the way that our JavaScript runtime chooses what to do is by figuring out thetype of the value - what sorts of behaviors and capabilities it has.That’s part of what thatTypeError is alluding to - it’s saying that the string"Hello World!" cannot be called as a function.

For some values, such as the primitivesstring andnumber, we can identify their type at runtime using thetypeof operator.But for other things like functions, there’s no corresponding runtime mechanism to identify their types.For example, consider this function:

js
functionfn(x) {
returnx.flip();
}

We canobserve by reading the code that this function will only work if given an object with a callableflip property, but JavaScript doesn’t surface this information in a way that we can check while the code is running.The only way in pure JavaScript to tell whatfn does with a particular value is to call it and see what happens.This kind of behavior makes it hard to predict what the code will do before it runs, which means it’s harder to know what your code is going to do while you’re writing it.

Seen in this way, atype is the concept of describing which values can be passed tofn and which will crash.JavaScript only truly providesdynamic typing - running the code to see what happens.

The alternative is to use astatic type system to make predictions about what the code is expected to dobefore it runs.

Static type-checking

Think back to thatTypeError we got earlier from trying to call astring as a function.Most people don’t like to get any sorts of errors when running their code - those are considered bugs!And when we write new code, we try our best to avoid introducing new bugs.

If we add just a bit of code, save our file, re-run the code, and immediately see the error, we might be able to isolate the problem quickly; but that’s not always the case.We might not have tested the feature thoroughly enough, so we might never actually run into a potential error that would be thrown!Or if we were lucky enough to witness the error, we might have ended up doing large refactorings and adding a lot of different code that we’re forced to dig through.

Ideally, we could have a tool that helps us find these bugsbefore our code runs.That’s what a static type-checker like TypeScript does.Static type systems describe the shapes and behaviors of what our values will be when we run our programs.A type-checker like TypeScript uses that information and tells us when things might be going off the rails.

ts
constmessage ="hello!";
 
message();
This expression is not callable. Type 'String' has no call signatures.2349This expression is not callable. Type 'String' has no call signatures.
Try

Running that last sample with TypeScript will give us an error message before we run the code in the first place.

Non-exception Failures

So far we’ve been discussing certain things like runtime errors - cases where the JavaScript runtime tells us that it thinks something is nonsensical.Those cases come up becausethe ECMAScript specification has explicit instructions on how the language should behave when it runs into something unexpected.

For example, the specification says that trying to call something that isn’t callable should throw an error.Maybe that sounds like “obvious behavior”, but you could imagine that accessing a property that doesn’t exist on an object should throw an error too.Instead, JavaScript gives us different behavior and returns the valueundefined:

js
constuser = {
name:"Daniel",
age:26,
};
user.location;// returns undefined

Ultimately, a static type system has to make the call over what code should be flagged as an error in its system, even if it’s “valid” JavaScript that won’t immediately throw an error.In TypeScript, the following code produces an error aboutlocation not being defined:

ts
constuser = {
name:"Daniel",
age:26,
};
 
user.location;
Property 'location' does not exist on type '{ name: string; age: number; }'.2339Property 'location' does not exist on type '{ name: string; age: number; }'.
Try

While sometimes that implies a trade-off in what you can express, the intent is to catch legitimate bugs in our programs.And TypeScript catchesa lot of legitimate bugs.

For example: typos,

ts
constannouncement ="Hello World!";
 
// How quickly can you spot the typos?
announcement.toLocaleLowercase();
announcement.toLocalLowerCase();
 
// We probably meant to write this...
announcement.toLocaleLowerCase();
Try

uncalled functions,

ts
functionflipCoin() {
// Meant to be Math.random()
returnMath.random <0.5;
Operator '<' cannot be applied to types '() => number' and 'number'.2365Operator '<' cannot be applied to types '() => number' and 'number'.
}
Try

or basic logic errors.

ts
constvalue =Math.random() <0.5 ?"a" :"b";
if (value !=="a") {
// ...
}elseif (value ==="b") {
This comparison appears to be unintentional because the types '"a"' and '"b"' have no overlap.2367This comparison appears to be unintentional because the types '"a"' and '"b"' have no overlap.
// Oops, unreachable
}
Try

Types for Tooling

TypeScript can catch bugs when we make mistakes in our code.That’s great, but TypeScript canalso prevent us from making those mistakes in the first place.

The type-checker has information to check things like whether we’re accessing the right properties on variables and other properties.Once it has that information, it can also startsuggesting which properties you might want to use.

That means TypeScript can be leveraged for editing code too, and the core type-checker can provide error messages and code completion as you type in the editor.That’s part of what people often refer to when they talk about tooling in TypeScript.

ts
importexpressfrom"express";
constapp =express();
 
app.get("/",function (req,res) {
res.sen
         
});
 
app.listen(3000);
Try

TypeScript takes tooling seriously, and that goes beyond completions and errors as you type.An editor that supports TypeScript can deliver “quick fixes” to automatically fix errors, refactorings to easily re-organize code, and useful navigation features for jumping to definitions of a variable, or finding all references to a given variable.All of this is built on top of the type-checker and is fully cross-platform, so it’s likely thatyour favorite editor has TypeScript support available.

tsc, the TypeScript compiler

We’ve been talking about type-checking, but we haven’t yet used our type-checker.Let’s get acquainted with our new friendtsc, the TypeScript compiler.First we’ll need to grab it via npm.

sh
npm install -g typescript

This installs the TypeScript Compilertsc globally.You can usenpx or similar tools if you’d prefer to runtsc from a localnode_modules package instead.

Now let’s move to an empty folder and try writing our first TypeScript program:hello.ts:

ts
// Greets the world.
console.log("Hello world!");
Try

Notice there are no frills here; this “hello world” program looks identical to what you’d write for a “hello world” program in JavaScript.And now let’s type-check it by running the commandtsc which was installed for us by thetypescript package.

sh
tsc hello.ts

Tada!

Wait, “tada”what exactly?We rantsc and nothing happened!Well, there were no type errors, so we didn’t get any output in our console since there was nothing to report.

But check again - we got somefile output instead.If we look in our current directory, we’ll see ahello.js file next tohello.ts.That’s the output from ourhello.ts file aftertsccompiles ortransforms it into a plain JavaScript file.And if we check the contents, we’ll see what TypeScript spits out after it processes a.ts file:

js
// Greets the world.
console.log("Hello world!");

In this case, there was very little for TypeScript to transform, so it looks identical to what we wrote.The compiler tries to emit clean readable code that looks like something a person would write.While that’s not always so easy, TypeScript indents consistently, is mindful of when our code spans across different lines of code, and tries to keep comments around.

What about if wedid introduce a type-checking error?Let’s rewritehello.ts:

ts
// This is an industrial-grade general-purpose greeter function:
functiongreet(person,date) {
console.log(`Hello${person}, today is${date}!`);
}
 
greet("Brendan");
Try

If we runtsc hello.ts again, notice that we get an error on the command line!

txt
Expected 2 arguments, but got 1.

TypeScript is telling us we forgot to pass an argument to thegreet function, and rightfully so.So far we’ve only written standard JavaScript, and yet type-checking was still able to find problems with our code.Thanks TypeScript!

Emitting with Errors

One thing you might not have noticed from the last example was that ourhello.js file changed again.If we open that file up then we’ll see that the contents still basically look the same as our input file.That might be a bit surprising given the fact thattsc reported an error about our code, but this is based on one of TypeScript’s core values: much of the time,you will know better than TypeScript.

To reiterate from earlier, type-checking code limits the sorts of programs you can run, and so there’s a tradeoff on what sorts of things a type-checker finds acceptable.Most of the time that’s okay, but there are scenarios where those checks get in the way.For example, imagine yourself migrating JavaScript code over to TypeScript and introducing type-checking errors.Eventually you’ll get around to cleaning things up for the type-checker, but that original JavaScript code was already working!Why should converting it over to TypeScript stop you from running it?

So TypeScript doesn’t get in your way.Of course, over time, you may want to be a bit more defensive against mistakes, and make TypeScript act a bit more strictly.In that case, you can use thenoEmitOnError compiler option.Try changing yourhello.ts file and runningtsc with that flag:

sh
tsc --noEmitOnError hello.ts

You’ll notice thathello.js never gets updated.

Explicit Types

Up until now, we haven’t told TypeScript whatperson ordate are.Let’s edit the code to tell TypeScript thatperson is astring, and thatdate should be aDate object.We’ll also use thetoDateString() method ondate.

ts
functiongreet(person:string,date:Date) {
console.log(`Hello${person}, today is${date.toDateString()}!`);
}
Try

What we did was addtype annotations onperson anddate to describe what types of valuesgreet can be called with.You can read that signature as ”greet takes aperson of typestring, and adate of typeDate“.

With this, TypeScript can tell us about other cases wheregreet might have been called incorrectly.For example…

ts
functiongreet(person:string,date:Date) {
console.log(`Hello${person}, today is${date.toDateString()}!`);
}
 
greet("Maddison",Date());
Argument of type 'string' is not assignable to parameter of type 'Date'.2345Argument of type 'string' is not assignable to parameter of type 'Date'.
Try

Huh?TypeScript reported an error on our second argument, but why?

Perhaps surprisingly, callingDate() in JavaScript returns astring.On the other hand, constructing aDate withnew Date() actually gives us what we were expecting.

Anyway, we can quickly fix up the error:

ts
functiongreet(person:string,date:Date) {
console.log(`Hello${person}, today is${date.toDateString()}!`);
}
 
greet("Maddison",newDate());
Try

Keep in mind, we don’t always have to write explicit type annotations.In many cases, TypeScript can even justinfer (or “figure out”) the types for us even if we omit them.

ts
letmsg ="hello there!";
let msg: string
Try

Even though we didn’t tell TypeScript thatmsg had the typestring it was able to figure that out.That’s a feature, and it’s best not to add annotations when the type system would end up inferring the same type anyway.

Note: The message bubble inside the previous code sample is what your editor would show if you had hovered over the word.

Erased Types

Let’s take a look at what happens when we compile the above functiongreet withtsc to output #"use strict";

functiongreet(person,date) {
console.log("Hello ".concat(person,", today is ").concat(date.toDateString(),"!"));
}
greet("Maddison",newDate());
 
Try

Notice two things here:

  1. Ourperson anddate parameters no longer have type annotations.
  2. Our “template string” - that string that used backticks (the` character) - was converted to plain strings with concatenations.

More on that second point later, but let’s now focus on that first point.Type annotations aren’t part of JavaScript (or ECMAScript to be pedantic), so there really aren’t any browsers or other runtimes that can just run TypeScript unmodified.That’s why TypeScript needs a compiler in the first place - it needs some way to strip out or transform any TypeScript-specific code so that you can run it.Most TypeScript-specific code gets erased away, and likewise, here our type annotations were completely erased.

Remember: Type annotations never change the runtime behavior of your program.

Downleveling

One other difference from the above was that our template string was rewritten from

js
`Hello${person}, today is${date.toDateString()}!`;

to

js
"Hello ".concat(person,", today is ").concat(date.toDateString(),"!");

Why did this happen?

Template strings are a feature from a version of ECMAScript called ECMAScript 2015 (a.k.a. ECMAScript 6, ES2015, ES6, etc. -don’t ask).TypeScript has the ability to rewrite code from newer versions of ECMAScript to older ones such as ECMAScript 3 or ECMAScript 5 (a.k.a. ES5).This process of moving from a newer or “higher” version of ECMAScript down to an older or “lower” one is sometimes calleddownleveling.

By default TypeScript targets ES5, an extremely old version of ECMAScript.We could have chosen something a little bit more recent by using thetarget option.Running with--target es2015 changes TypeScript to target ECMAScript 2015, meaning code should be able to run wherever ECMAScript 2015 is supported.So runningtsc --target es2015 hello.ts gives us the following output:

js
functiongreet(person,date) {
console.log(`Hello${person}, today is${date.toDateString()}!`);
}
greet("Maddison",newDate());

While the default target is ES5, the great majority of current browsers support ES2015.Most developers can therefore safely specify ES2015 or above as a target, unless compatibility with certain ancient browsers is important.

Strictness

Different users come to TypeScript looking for different things in a type-checker.Some people are looking for a more loose opt-in experience which can help validate only some parts of their program, and still have decent tooling.This is the default experience with TypeScript, where types are optional, inference takes the most lenient types, and there’s no checking for potentiallynull/undefined values.Much like howtsc emits in the face of errors, these defaults are put in place to stay out of your way.If you’re migrating existing JavaScript, that might be a desirable first step.

In contrast, a lot of users prefer to have TypeScript validate as much as it can straight away, and that’s why the language provides strictness settings as well.These strictness settings turn static type-checking from a switch (either your code is checked or not) into something closer to a dial.The further you turn this dial up, the more TypeScript will check for you.This can require a little extra work, but generally speaking it pays for itself in the long run, and enables more thorough checks and more accurate tooling.When possible, a new codebase should always turn these strictness checks on.

TypeScript has several type-checking strictness flags that can be turned on or off, and all of our examples will be written with all of them enabled unless otherwise stated.Thestrict flag in the CLI, or"strict": true in atsconfig.json toggles them all on simultaneously, but we can opt out of them individually.The two biggest ones you should know about arenoImplicitAny andstrictNullChecks.

noImplicitAny

Recall that in some places, TypeScript doesn’t try to infer types for us and instead falls back to the most lenient type:any.This isn’t the worst thing that can happen - after all, falling back toany is just the plain JavaScript experience anyway.

However, usingany often defeats the purpose of using TypeScript in the first place.The more typed your program is, the more validation and tooling you’ll get, meaning you’ll run into fewer bugs as you code.Turning on thenoImplicitAny flag will issue an error on any variables whose type is implicitly inferred asany.

strictNullChecks

By default, values likenull andundefined are assignable to any other type.This can make writing some code easier, but forgetting to handlenull andundefined is the cause of countless bugs in the world - some consider it abillion dollar mistake!ThestrictNullChecks flag makes handlingnull andundefined more explicit, andspares us from worrying about whether weforgot to handlenull andundefined.