Skip to content

Artwork: Violet Reed

TypeScript and the dawn of gradual types

Why choose between static or dynamic when you can get the best of both?

Mike Melanson // July 11, 2023

The ReadME Project amplifies the voices of the open source community: the maintainers, developers, and teams whose contributions move the world forward every day.

The FullScreenMario project burned brightly for a few short weeks in October 2013 after Boing Boing lauded it as “a pretty impressive example of what HTML5, in-browser functionality can do.” A few days later, it went viral on Reddit and by November, attention turned to scrutiny, and Nintendo took the project down with a DMCA request. 

Josh Goldberg speaks of his former project with a bit of pride—it had level-editing long before Super Mario Maker, after all. He is less nostalgic about his experience using JavaScript to build something as complex as a full-screen video game, however.  

“Untyped JavaScript is like stumbling around in the dark,” says Goldberg. “Constant toe-banging.”

He says that JavaScript’s dynamic nature made it unpredictable in various ways that complicated his development process.

”I had no sense of when values would be initialized or what odd interactions they'd have,” he says. “Bugs would frequently pop up because I'd use something before it was initialized, or in the wrong way, or just in a way that I hadn't designed it for.”

Lucky for him, Microsoft had run into the same issues (and then some). The year before, it had released a public preview of TypeScript, an open source superset of JavaScript that adds optional types with the goal of making it easier to work with large codebases and “strike a balance between correctness and productivity.” 

Types (short for “data types”) are descriptions of what sort of data a variable contains—for example, string, integer, or boolean—and the allowed operations on that data. Statically-typed languages like Java and C++ ensure that the operations performed on variables are compatible with the type of data stored in those variables during a build step and before the code is run. Dynamically-typed languages like JavaScript and Ruby, by contrast, evaluate operations and variables for compatibility dynamically during runtime. Each style of language comes with its own benefits and drawbacks, depending on where and how it is used.

“Scripting languages are great for small programs, but a program with millions of lines of code that a large number of developers will collaborate on needs better encapsulation and modularity,” explains Amanda Silver, who was a Group Program Manager on the original TypeScript team, and now leads product for Microsoft’s Developer Division overseeing developer tools, runtimes, and services like Visual Studio Code, .NET, C#, TypeScript, and Azure’s Developer Experience.

Goldberg took advantage of TypeScript’s static qualities when he later refactored FullScreenMario into EightBittr, a game engine for 2D 8-bit games. It was pretty much TypeScript from there on out for him, and he isn’t alone. 

TypeScript was the third fastest-growing and fourth most-used language in GitHub’s Octoverse 2022 report. RedMonk puts it at number eight on its top 20 languages list, jumping up from 17 in 2017. Stack Overflow has it as the fourth most-loved and third most-wanted language in its 2022 Developer Survey.

“We developed TypeScript to help programming at scale, but it really wins at almost every level of development,” says Daniel Rosenwasser, the current Product Manager on TypeScript. “TypeScript really proved out a lot of what works and what’s possible for existing dynamic languages, and has gotten a lot of love. Now each of those languages has different challenges, but often there’s something they can take away from that.”

Now, none of that’s to say that TypeScript is pushing out dynamic languages. In fact, JavaScript has held the top spot on Octoverse’s list of most-used languages for nine years running, with Python holding the number two spot, having recently overtaken Java. 

But this isn’t a zero-sum game. The story of TypeScript, and the recent paradigm shift of the past decade, is less about “either or” and more about getting the best of both worlds—with gradual typing. In fact, while Python is still dynamic, it too has made the move toward gradual typing, as have several other languages of its ilk.

The pendulum swing of programming language paradigms

Starting with FORTRAN and COBOL in the 1950s and continuing into the 1990s with languages like C++, Delphi, and Java, statically-typed languages dominated commercial software development because of their ability to scale. They could be difficult to learn and slow to use, but waterfall development practices and desktop software released on physical media meant that velocity wasn’t a top concern, safety was. Release timelines were measured in months not minutes, so static languages, alongside extensive testing, helped developers catch bugs before they couldn’t be fixed. 

The 90s into the 00s also saw the rise of the web and agile programming, and with them the rise of dynamic languages like JavaScript, Ruby, Python, and PHP. Dynamic languages weren’t new—they trace their lineage back to LISP—but they found a wave to ride with the web. They were quick to learn and write, and allowed developers to “move fast and break things,” as was Facebook’s motto at the time. With bug fixes an upload away, their velocity and agility overshadowed the safety of their static counterparts. But only for a time. 

When Brendan Eich created JavaScript in 1995, he’d set out to build a lightweight scripting language that would finally bring interactivity to the browser. That’s all. But within two years, JavaScript was standardized as ECMAScript to ensure it could work in all browsers, and it became the de facto language of the fast-rising web. The push to bring aspects of those static languages to JavaScript began soon after.

By 1999, ECMAScript 4 proposed features like classes, interfaces, and optional types, all aimed at making JavaScript more like the static languages companies were already comfortable with. The proposal grew in scope for years, languishing in debate until it was put to bed in 2008 with most of those features dying with it. 

At the same time, companies were building increasingly complex software with JavaScript—think Google Maps and Docs—and the lack of those capabilities was increasingly apparent. When Microsoft realized it would need to port millions of lines of C++ and C# to JavaScript to bring the Microsoft Office suite to the web, it tapped Delphi and C# co-creator Anders Hejlsberg to help solve the problem. TypeScript was the answer.

TypeScript consists of a language, a type checker, a compiler, and a language service. As a superset of JavaScript, it adds TypeScript-specific syntax on top of JavaScript to define types. But TypeScript is not compiled and run as-is. Rather, the type checker analyzes the TypeScript code to ensure that when it is executed, the operations performed will correctly match the types. In other words, the type checker makes sure that an operation that requires numbers and not letters will only get numbers, for example. The compiler then converts, or transpiles, TypeScript code into its JavaScript equivalent. 

Because of this build step, developers can intermingle pure JavaScript and TypesScript as much as they want, because it all transpiles down to JavaScript in the end, anyway. Transpilation to JavaScript also eliminates the need for updated runtimes, allowing code written in TypeScript to run wherever JavaScript is supported. It can also target specific runtimes, ensuring backwards compatibility while using new features.

Combined with the typescript-language-server, which interfaces with the IDE, these tools give JavaScript developers the benefits of a statically typed language. They catch errors before runtime and add support for things like code refactoring, completion, navigation, error checking, and more. 

TypeScript: JavaScript that scales…?

TypeScript’s rise to prominence was anything but certain. The project met some initial hesitation from the open source community and JavaScript developers alike. The open source community didn’t fully trust Microsoft, and JavaScript developers were leery of going back to the static ways of Java and C++. Silver credits the language’s greater acceptance to the Angular team’s decision to rebuild the framework almost entirely in TypeScript, starting with Angular 2—even choosing TypeScript over Dart, Google’s in-house language. 

“The rise of Angular became the rise of TypeScript,” says Silver. “Now we have the best of all worlds, because JavaScript developers get to benefit from the hard work that framework developers do without the complexity of understanding strong typing."

Rosenwasser points to the rich editing experience popularized by TypeScript and VS Code as something often overlooked. “People often get caught up in the type system itself, but there’s so much more that we’ve also built on top of that. People who get a taste of what a great editing experience is like never want to go back,” he says.

Goldberg, who nowadays works full-time in open source and is a maintainer for typescript-eslint, the tooling that enables ESLint and Prettier to run on TypeScript code, credits TypeScript with contributing to JavaScript’s ubiquity by making it a better language to use at scale. At the same time, he says that TypeScript certainly gained from JavaScript’s popularity.

“TypeScript is an exemplar because it got many things right early and targets the always-ubiquitous JavaScript,” says Goldberg. “The days of using untyped languages on non-trivial projects are over. The developer world has seen that untyped languages just do not scale, while typed languages scale in all sorts of ways.”

One such way, explains Emily Samp, a contributor to the Ruby type checker Sorbet, is that static typing creates a sort of self-documenting code.

“Static typing can codify much of the systemic and institutional knowledge needed to maintain applications of a certain size and complexity,” says Samp. “Mission-critical parts of information that must adhere to specific assumptions can be encoded in the code, rather than relying on comments or tests that may become outdated.”

But Jordan Harband, a prolific open source maintainer, editor of the ECMAScript specification from 2018 to 2021, and a TC39committee delegate since 2014, warns that building with TypeScript can have some unintended consequences. TypeScript can lull developers into a false sense of security. While TypeScript behaves like a strongly-typed language when writing, it runs as JavaScript, a weakly typed language. This can cause unexpected errors unless developers take care with proper tests.

“Type systems provide a benefit, that's objective. But the over-reliance on type systems is a crutch. Many people think that because they write types, they don't have to write runtime type checks, or tests, and they’re wrong,” says Harband. “A type system is a faster feedback loop that you could get 30 seconds later by running the tests on the command line. With types you get that inline with autocomplete. That's great, but it's not a valuable trade off for having your code be less robust and less correct.”

Brad Zacher co-maintains typescript-eslint with Goldberg and previously worked for Meta on Flow, a static type checker for JavaScript originally developed by Facebook. He says that TypeScript doesn’t go far enough in its type safety for Facebook, but it’s all in the name of developer experience

“Flow is much safer than TypeScript. But the caveat is that making things really safe also makes them really strict and a lot harder to work with,” says Zacher. “TypeScript has a number of flaws that make it really unsafe, but a lot are intentional. They either help simplify TypeScript's internals or they improve the DevEx by allowing more dynamic, ‘JavaScript-y’ code.”

While TypeScript may not be adequate for Meta’s overall purposes, its popularity indicates that it strikes a balance that developers prefer over Flow’s strictness. In fact, even Meta’s own developers sometimes choose TypeScript. Jest started its move to TypeScript in 2019, Yarn announced its migration in 2020, and React Native just made TypeScript the default for new templates earlier this year.

“Developers love dynamism, but they also want safety at scale,” says Zacher. “But if you go all the way to the other end, to a completely strict system like Flow, they feel too restricted.”

The dawning of the age of gradual type systems

Programming language paradigms are often represented and discussed in terms of binaries, but the reality is that they exist on numerous spectrums. TypeScript’s relative unsafety compared to something like Flow might be seen as a feature by some, but as a bug by others. 

“TypeScript proved that it’s best to not swing the pendulum in one direction or the other, but to recognize that it's all the same ecosystem,” says Silver.

TypeScript’s 2012 launch may not have been the spark that lit the fire, but rather the first flames from the embers below. By 2014, TypeScript hit 1.0 and Facebook declared the end of its “move fast and break things” era with the release of Flow, alongside Hack, a language that brought static typing to PHP. By 2015, PHP and Python both added type features, and Sorbet brought types to Ruby the following year. Ruby itself hopped aboard the gradually-typed train in 2021 with RBS, its native type checker. Elixir, a dynamic language released in 2012, recently jumped on board when the language’s creator announced they were researching and developing a type system. And we’ve even seen some examples from the opposite end of the spectrum: C# added gradual typing with a dynamic type in C# 4.0, and Go recently added generics after years of debate.

Many of these projects influence each other. For example, Rosenwasser says the Typescript team, the Python team, and the team behind the Python type checker Pyright are in communication and exchange ideas.

Gradual typing allows developers to adopt varying levels of typing in their code. They can declare types and have them checked at compile time, leave them undeclared and have them checked dynamically at runtime, or even a mix of both in the same code. In the case of TypeScript, the developer gets type checking directly in the IDE and during transpilation from the TypeScript tooling, and then undeclared types end up being checked at runtime by the JavaScript engine, as they normally would. Since TypeScript transpiles into JavaScript, it offers this final ability to intermingle plain JavaScript within TypeScript code.

“If adoption required going all-in from the beginning, it would be an unreasonable workflow for most teams,” explains Samp. “Being able to say which parts of the type system are important or not important is really powerful, because it allows teams to focus on important tasks and gradually increase their standards over time.”

In TypeScript, developers can opt into typing on a variable-by-variable basis. If a variable has no declared type, TypeScript will try to infer the type by how it is used. If it can’t determine the type from the context, it will assign the any type to the variable, which essentially turns off TypeScript's strict type-checking for that variable. From there, TypeScript can be made progressively stricter using compiler options that broadly enforce static type checking rules, including a strict mode that turns them all on.

Even for fans of TypeScript, though, there’s just one problem remaining that they don’t want to deal with—the build step and the tooling that comes with it.

“The big thing that people really love about dynamic languages is there’s no compilation. You don't have to worry about managing build tools and weird tool chains,” says Zacher. “The developer experience you get from that is amazing, because there's no waiting when you've finished writing your code.”

Can we finally kill the build step?

Svelte, the open source front-end framework, recently stopped using TypeScript—the language—in favor of declaring types using JSDoc, a markup language used to annotate JavaScript code. Svelte-creator Rich Harris tweeted that the approach offered all the benefits of TypeScript without the hindrances of the build step. But if it were that simple, wouldn’t everyone be doing it?

In 2020, Gil Tayar, now a developer at Microsoft working on the Kusto Query Language (and with no TypeScript affiliation), wrote a blog post describing this process titled JSDoc typings: all the benefits of TypeScript, with none of the drawbacks. While he does concede a few potential downsides, such as verbosity and types bloating your code since they're not removed during the transpilation step, he says that these minor inconveniences are ultimately worth it, just to remove that step.

“I hate transpilation with a vengeance,” he says. 

That same blog post caught the attention of Daniel Ehrenberg, a TC39 committee member who had a similar idea. Ehrenberg contacted Tayar on Twitter with a proposition: Would he be interested in authoring the first draft of a proposal to add type annotations to JavaScript? A year later, the proposal was announced with the support of the TypeScript team and a number of notable co-authors.

Originally conceptualized as “types as comments,” the proposed change would allow developers to add typing directly in their JavaScript code, which could then be checked by an external type checker. The JavaScript engine would ignore these type annotations and treat them as comments at runtime. 

“The aim of this proposal is to enable developers to run programs written in TypeScript, Flow, and other static typing supersets of JavaScript without any need for transpilation, if they stick within a certain reasonably large subset of the language,” it reads.

Tayar is hopeful that the proposal will be adopted, though it would buck the trend. Adding types to JavaScript, in some format or another, has been discussed and shot down numerous times over the past 20-something years, ECMAScript 4’s collapse included. These days, the idea finds backing from languages like Python and Ruby, which have preceded JavaScript down similar paths. But for the type annotations proposal to succeed, the TC39 committee needs to reach a consensus.

Harband, a TC39 member since 2014, remains unconvinced. 

"If a language feature were added to JavaScript that completely removed the need for TypeScript to do any transformation—meaning it only did type checking—that would be valuable,” says Harband. “This doesn’t do that, but I'm hopeful that the idea will evolve. The solution will very likely shift over time.”

Tayar’s proposal argues that, much like gradual types, it isn’t a zero-sum game.

“The JavaScript ecosystem has been slowly moving back to a transpilation-less future. The sunsetting of IE11 and the rise of evergreen browsers that implement the latest JavaScript standard means that developers can once again run standard JavaScript code without transpilation,” it reads. “Implementing this proposal means that we can add type systems to this list of ‘things that don't need transpilation anymore’ and bring us closer to a world where transpilation is optional and not a necessity.”

After nearly a year and a half, the proposal remains at the first of four maturity stages in TC39’s process for changing the ECMAScript specification and recent discussions show that the proposal is young and the committee is far from consensus.

While the possibility of a transpilation-free, gradually-typed JavaScript remains murky, it’s not the only way to ease developers’ pain, says Silver. She sees AI as smoothing the path even more for strongly typed languages like TypeScript. When you have a type incompatibility in your program, if you're not well versed in type systems, it can be a little daunting to figure out how to fix it, she says. While it’s important for developers to learn and understand these features of a programming language, the experience could be made easier. 

“I think we're going to have much more self-healing programs going forward,” she says. “We're going to be able to provide more and more AI-driven guidance to help users understand where their program is off and how to fix it.”

Samp says she’s already seen the first effects of AI on dealing with types—even just gradual types.

“GitHub Copilot is really good at generating Sorbet signatures. They can be a blocker for people, because they don’t want to have to do that for every single method. The fact that AI can do that for you really boosts the effectiveness of a gradual type system in a dynamic language, which is so, so cool,” she says.

About The
ReadME Project

Coding is usually seen as a solitary activity, but it’s actually the world’s largest community effort led by open source maintainers, contributors, and teams. These unsung heroes put in long hours to build software, fix issues, field questions, and manage communities.

The ReadME Project is part of GitHub’s ongoing effort to amplify the voices of the developer community. It’s an evolving space to engage with the community and explore the stories, challenges, technology, and culture that surround the world of open source.

Follow us:

Nominate a developer

Nominate inspiring developers and projects you think we should feature in The ReadME Project.

Support the community

Recognize developers working behind the scenes and help open source projects get the resources they need.

Thank you! for subscribing