TypeScript Yin-Yang - How TypeScript really compares to JavaScript

by Emil — 12 minutes

Once I started using TypeScript, after using JavaScript for several years, I often found that TypeScript documentation and blog posts were somewhat lacking.

Whereas TypeScript documentation and blog posts primarily focus on TypeScript syntax and its configurable features, they often fail to explain how these actually relate to standard JavaScript or what JavaScript problem they are trying to fix.

Sometimes it might even appear in blog posts that some new syntax is TypeScript specific (e.g. optional chaining), whereas it's actually just syntax coming from an ECMAScript proposal that often has already been supported by both Chrome and Babel for quite some time.

As a matter of fact, nearly all the syntax that is TypeScript specific does "not" allow you to actually program anything at runtime (e.g. interfaces, type annotations and conditional types), but solely exists to assist the static type checker of TypeScript at compile time. Exceptions to this rule are the syntactic sugar from the early days of TypeScript, being enums, constructor parameter properties and decorators.

In my opinion TypeScript is best to be seen as a static type checker. And the TypeScript specific syntax extensions are nothing more than a means to end.

Dynamically typed JavaScript

TypeScript was primarily created to counteract the dynamically typed nature of JavaScript.

The fact that JavaScript is dynamically typed is clearly demonstrated by the following example on MDN:

let foo = 42; // foo is now a number
foo = "bar"; // foo is now a string
foo = true; // foo is now a boolean

The sample code beautifully illustrates the following about JavaScript

  • variables are not directly associated to any value type
  • variables can be assigned (and re-assigned) values of all types

Actually, everything that 'holds a value' has "no" associated type in JavaScript:

  • variable
  • property
  • class field
  • parameter
  • return value

To counteract the dynamically typed nature of JavaScript, TypeScript introduces static type checks based on the type that can be associated with TypeScript to everything that 'holds a value'.

Statically typed TypeScript

In TypeScript everything that ‘holds a value’ has an associated type. And whenever a value is assigned with an incorrect type, TypeScript will prohibit this through a compiler error.

Let’s assume that we intended the variable foo to be a number, then in TypeScript we can add a type annotation to specify that:

let foo: number = 42;

In that case TypeScript will give compilation errors on lines 2 and 3:

   foo = "bar";
// ~~~ TS2322: Type 'string' is not assignable to type 'number'.

   foo = true;
// ~~~ TS2322: Type 'boolean' is not assignable to type 'number'.

The compilation errors above demonstrates that TypeScript is statically typed as opposed to JavaScript being dynamically typed.

But in this case we did not need to explicitly specify the number type, since TypeScript can infer in based on the assigned initial value of 42:

let foo = 42; // %inferred-type: number

How to best describe TypeScript

In order to (better) understand TypeScript and how it compares to JavaScript, it might be useful to see how it's being described by others.

Traditionally TypeScript was often described as "a typed superset of JavaScript". This means that all JavaScript code is also valid TypeScript code. Which is easily demonstrated by the fact that converting code to TypeScript takes nothing more than changing the file extension from .js to .ts.

But TypeScript is only a superset when looking at the language syntax. However, semantically TypeScript programs behave more like a subset of JavaScript.

Whereas syntax-wise TypeScript is a superset of JavaScript, semantically not every TypeScript programs will be considered valid by the static type checker of TypeScript.

difference between TypeScript programs and programs passing the type checking

In order to better describe what TypeScript is, the typescriptlang.org website no longer describes TypeScript as being superset, but nowadays instead describes it as:

Typed JavaScript at Any Scale.
TypeScript extends JavaScript by adding types.

Although the description above of TypeScript is much better, in my opinion it's still lacking since it still fails to describe the actual purpose of TypeScript. Therefore, I personally much more prefer the description of TypeScript made by Tero Parviainen in its blog post about Angular 2:

It is just JavaScript - just with the seat belt fastened.
It doesn't really make sense to compare it to other languages.

yellow crashtest dummy with js badge and typescript on its seatbelt

This description nicely uses the metaphor of a fastened seat belt, to illustrate the fact that TypeScript is nothing more than JavaScript, but then safeguarded through its (static) type checker.

TypeScript Yin-Yang

In order to better understand TypeScript, I decided to start searching for a better matching analogy than being "typed superset" or a "fastened seat belt".

Without being an expert on the matter, it appeared to me that the ancient Chinese philosophy of Yin and Yang might be a matching analogy. And after a quick search, I found the following tweet that seems to confirm it:

Tweet of @Mr_Bolorunduro explaining TypeScript vs JavaScript through Yin and Yang

Looking at Wikipedia, Yin-Yang is being described as follows:

…, yin and yang is a concept of dualism, describing how seemingly opposite or contrary forces may actually be complementary, interconnected, …

The dualism of Yin-Yang is also applicable when developing in TypeScript:

  • the passive (compile-time / static) side of TypeScript (yin) interconnects with and is complementary to the active (runtime) side of JavaScript (yang)
  • the Type System of TypeScript is modeled after the actual types occurring at runtime in JavaScript
  • the code completion used while writing JavaScript, is facilitated by the Type System of TypeScript.

The analogy of Yin-Yang is only intended to explain how JavaScript and TypeScript relate to each other while using TypeScript.
Without the context of developing with TypeScript the analogy of Yin-Yang will not hold, because it's off-course perfectly possible (and also legitimate) to develop in JavaScript without using TypeScript.

But even though I personally develop fulltime using TypeScript, I prefer seeing myself as a JavaScript developer and not as a TypeScript developer.
To me developing in TypeScript is nothing more than another (popular) way of writing JavaScript programs.

Side-by-side comparison of JavaScript and TypeScript

Using the analogy of Yin-Yang we can elegantly do a side-by-side comparison of TypeScript with JavaScript on a few key characteristics.

Run-time vs compile-time

  • JavaScript: executes code at run-time
  • TypeScript: checks types at compile-time

Basically, TypeScript is nothing more than an advanced type checker.
Besides some syntactic sugar from its early days (e.g. enums), the TypeScript syntax extensions will be completely removed (type erasure) at compile time.
Therefore, it will have no runtime impact whatsoever.

Differences in how they are typed

  • JavaScript: dynamically typed
  • TypeScript: statically typed

As demonstrated earlier JavaScript is dynamically typed, meaning that a variable (and all other things that ‘holds a value’) does not have a type associated with it.
TypeScript however fixes the dynamically typed nature of JavaScript, but doing static type checks during compilation.

ECMAScript proposals support

  • JavaScript: early support for ECMAScript proposals
  • TypeScript: late support for ECMAScript proposal (stage 3 / 4)

The reason for the existence of TypeScript is to introduce a compile-time (static) type-system to JavaScript. Therefore, there is not much priority in introducing new JavaScript language features to TypeScript.

Typically, new ECMAScript proposals are introduced once they reach stage 3 or 4, but sometimes new features are forgotten and only added to TypeScript after an issue is being logged.

Transpilation of modern / future ECMAScript syntax

  • JavaScript: typically handled by Babel.js
  • TypeScript: handled by either the TypeScript compiler or Babel.js

When developing in JavaScript you will typically use Babel.js to transpile / compile modern JavaScript syntax for browser versions that don’t support it.

When developing with TypeScript you can choose to use the TypeScript compiler to transpile / compile modern JavaScript syntax, or alternatively you use Babel.js (which is preferable IMO) to do the transpilation of both your JavaScript and TypeScript specific syntax.

Intrinsic (built-in) objects

  • JavaScript: implements built-in (intrinsic) objects
  • TypeScript: adds typings for built-in (intrinsic) objects

Besides language syntax, JavaScript also ships with an API offered by built-in (intrinsic) objects like Date and Object.

TypeScript does not ship (nor implement) any additional JavaScript API, and instead only adds typings for built-in (intrinsic) objects.

NOTE: TypeScript does however offer a few built-in mapped types, but these do not have impact at runtime due to type erasure (explained later in this blog post)

Area of innovation

  • JavaScript: innovates in language syntax
  • TypeScript: innovates in the type space

Maybe the best illustration that TypeScript is complementary to JavaScript, is the area on which they innovate. Whereas JavaScript innovates on runtime language syntax, TypeScript exclusively innovates on the type space that only exists at compile time.

Therefore, all syntax that ‘does something at runtime’ is actually coming from JavaScript. Small exception to this rule is some syntactic sugar from the early days of TypeScript, like enums, constructor parameter properties and decorators.

Type System of TypeScript

As mentioned before, TypeScript is basically nothing more than a compile-time type checker.
However, for the type checker to function it needs to have access to the type related information of the code that needs to be checked.

To be able to feed this information to the type checker, TypeScript introduces a type system to the JavaScript language:

…, a type system is a logical system comprising a set of rules that assigns a property called a type to the various constructs of a computer program, such as variables, expressions, functions or modules.

The aim of the Type System of TypeScript is to model the runtime behaviour of JavaScript.
Therefore, TypeScript possibly allows unwanted constructs because they are still valid JavaScript:

const x = 4 + "2"; // evaluates to ‘42’
const y = "4" + 2; // evaluates to ‘42’

However, to prevent bugs (to some degree), TypeScript does prohibit some quirky JavaScript constructs.
For instance the following is perfectly valid JavaScript, but will give compilation errors in TypeScript:

const x = null + 42; // evaluates to 42 in JS
//        ~~~~~~~~~ TS2365: Operator '+' cannot be applied to types 'null' and '42'.

const y = [] + 42; // evaluates to ‘42’ in JS
//        ~~~~~~~ TS2365: Operator '+' cannot be applied to types 'undefined[]' and 'number'.

alert("foo", "bar"); // alerts ‘foo’ in JS
//           ~~~~~ TS2554: Expected 0-1 arguments, but got 2.

Type erasure

It is an explicit design choice of TypeScript to have zero impact at runtime.
Therefore, type checking is only done at compile time.

To have zero impact in bundle size, TypeScript performs Type erasure during compilation and removes all type information during compilation.
The only exception to this rule are classes (being standard JavaScript syntax) and enums, which besides a type at compile time also exist as a value during runtime.

type erasure explained through Babel support of TypeScript

Personally, I find the best illustration of Type erasure, to be the TypeScript support of Babel.js that basically does nothing more than removing all TypeScript specific syntax from your code.

A hopefully changed perspective on TypeScript

Now that this blog post is nearly coming to an end, I hope to have changed your perspective on TypeScript and how it actually relates to JavaScript.
Hopefully the analogy of Yin-Yang also helps to understand how TypeScript really relates to JavaScript.

Even when you still don't like TypeScript after reading this blog post, know that the Type System of TypeScript merely models the runtime behaviour of JavaScript.

Even though the TypeScript syntax extensions might make your code a bit more verbose, they also make the types explicit in your code.
Whereas otherwise the types that exist at runtime would only exist implicitly in the code or as, most likely outdated, documentation.

TS Yin-Yang: to be continued…

Hopefully you liked this blog post and its perspective on TypeScript versus JavaScript.

In my opinion TypeScript syntax / features are best to be described by comparing them with JavaScript, which I often find lacking in TypeScript related blog posts and documentation.

I'm really interested to hear if you found this blog post useful and / or if you would like to read more TS Yin-Yang blog posts in the future. You can reach me on Twitter at @EmilVanGalen.

But for now… let’s just say… to be continued.

(In Dutch) Online Divotion lunch sessie over TS Yin-Yang

Op woensdag 3 maart organiseert Divotion de digitale lunchsessie: TypeScript Yin-Yang van 12:00u tot 13:30u.

Tijdens deze lunchsessie willen we je een nieuwe blik geven door uit te leggen hoe TypeScript zich werkelijk verhoudt tot JavaScript. Dit met als ultieme doel om een beter begrip te krijgen van TypeScript.

Meer informatie over het programma en hoe aan te melden is hier te vinden.