Niranjan Ramadas (u/nr4madas)

Engineering Manager

Earlier this year, our CEO, Steve, mentioned we are redesigning the site. Great! But how? Frontend engineering is in a very different state than it was when Reddit was first conceived. We have a large depth of options for just about every layer of web app development. From how to render views, style content, serve assets, and write code, frontend development has at least a couple of options. One of the first questions we had to answer was “what language should we use?”

Surprisingly, the answer does not have to be Javascript. Ultimately, whatever language you pick will compile down to Javascript. But what the language compiles down to is perhaps less important than what the developer writes. And the choices are numerous:

Bucklescript ClojureScript Coffeescript Elm Elixirscript Javascript 2016 and beyond Javascript + annotations Nim PureScript Reason Typescript

…just to name a few. Each language comes with its pros and cons, so to aid in picking one, we needed to establish a few requirements:

Must have types. Types serve as documentation at the micro-level, help ensure correctness (to a certain degree), and most importantly, make refactoring code less stressful. Another consideration was the speed of development. We wanted to add types because we wanted to move quickly. This sounds a bit antithetical to how many people view types, in that it adds overhead to development and therefore slows developers down. However, in moving quickly, it is much easier to introduce bugs. We looked to types to help keep our code correct even in the fast pace we were moving at. Types also help codebases scale. Our engineering team is growing fast ( we’re hiring! ), and the number of people making contributions is only going to increase. Must have good tooling already. Considering the scope of the product work (major redesign), we don’t have the time to build out a significant amount of tooling ourselves. It was pretty important that we could get started fairly quickly with readily available open source solutions. To be more specific, the sort of tooling we were looking for was integration with popular build tools (e.g., Webpack), support for linting, and easy integration with testing frameworks. If integrating the language tooling was not obvious, we skipped it. Powers major production apps. If a language looks great, but is really only used for hobbyist projects, it might not be the right fit. It also creates uncertainty with respect to the lifetime of the language and the cadence with which issues would be resolved. Our devs should be able to onboard fairly quickly. There are some excellent languages in the list above that would simply take too long for our devs to adapt to. Languages like Elm and Purescript are prime examples. We seriously debated their use, but in the end, it felt like it would be too much work to teach new programming concepts to unfamiliar devs while trying to meet the demanding product goals. Should work on both the client and the server. SEO is very important to Reddit, so lack of universal rendering is a deal breaker. Good library support. We don’t want to write everything from scratch. There are some problems where a library solution is the best. We want to keep that as an option.

After considering these requirements, our two best options seemed to be either Typescript or Javascript + Flow. Before we could pick between them, we needed to understand where they differ.

Compilation vs Annotation

One major difference between Typescript and Flow is that Typescript is a language that compiles down to Javascript, whereas Flow is a set of annotations you can add to existing Javascript that can then be checked for correctness by a tool.

This has subtle implications for how code is written. Consider, for example, enums in both systems:

Typescript

enum VoteDirection { upvoted = 1 , notvoted = 0 , downvoted = - 1 , }; const voteState : VoteDirection = VoteDirection . upvoted ;

Flow

const voteDirections = { upvoted: 1, notvoted: 0, downvoted: -1, }; type VoteDirection = $Keys<typeof voteDirections>; const voteState: VoteDirection = voteDirections.upvoted;

Since Typescript will compile, it can create types that also have a runtime definition. In Flow, however, the types are just annotations, so we can’t rely on any sort of code transform to create runtime representations of our types.

Typescript’s compilation, however, has its drawbacks. When integrating Typescript into existing code bases, Typescript’s compilation has the possibility of complicating the build process. At Reddit, we had an existing build process using Babel as a transpilation layer. There were a few optimizations that we wanted to preserve even as we added Typescript, so we needed a way of integrating Typescript without significantly disrupting what we had already. The end result was a much more complex build step.

By contrast, Flow’s type annotations are automatically removed by Babel. Had we adopted Flow, our build step would have stayed simple.

Soundness/Correctness

Flow generally does a better job here. Flow, by default, does not allow nullable types. Typescript added support for non-nullable types in 2.x, but it is something that you need to remember to enable. Flow also does a better job inferring types, whereas Typescript will often fallback to using the “any” type.

Beyond nullability and type inference, Flow also has a better story for covariance and contravariance (you can read more about Flow’s variance here). One such example where this ends up being a problem is in arrays. By default, arrays in Flow are invariant. That means in Flow, this will throw an error:

Flow

class Animal {} class Bird extends Animal {} const foo : Array<Bird> = []; foo .push( new Animal ()); /* foo.push(new A); ^ A. This type is incompatible with const foo: Array<B> = []; ^ B */

But in Typescript, this is okay:

Typescript

class Animal {} class Bird extends Animal {} const foo : Array<Bird> = []; foo .push( new Animal ()); // ok in typescript

There are more examples you can find online, but the general consensus is that Flow does a better job at type-checking compared to Typescript.

Ecosystem

So far, Flow seems to have a simpler setup and is better at type-checking. So why did we go with Typescript?

One of the Typescript’s largest strengths is its ecosystem. Typescript’s library support is fantastic; just about all the libraries we use have either type descriptors in the library itself or a representation in DefinitelyTyped. In addition, Typescript has great Intellisense support in VSCode and plugins for other popular editors we use (like Atom and Sublime Text). Furthermore, we discovered that Typescript parses JSDoc annotations. This was especially helpful as we were migrating over to Typescript from Javascript.

Typescript also came with a lot of “social proof” and better assurances about its longevity There are several large projects using Typescript (examples include VSCode, Rxjs, Angular, and Typescript itself), so we felt confident that its feature set could support our product goals, and the language would stick around for several years. One of the worries we had with Flow was that it was built to solve specific needs at Facebook and that its future would be determined by that scope. Typescript, on the other hand, is a more general purpose offering from Microsoft, so we felt that if we surfaced issues, they would be heard.

In addition, because Typescript is a language that functions as a superset of Javascript, we could expect that any ES6+ features that Typescript supports would have corresponding types. The language and its type system would move in lock-step because both are developed together.

Summary

We picked Typescript because we are confident we could onboard devs quickly (the number of frontend engineers has tripled in the last year), the language could support our product goals of redesigning the entire site, it would stick around for a while, and it would integrate well with our codebase. But most importantly, we switched to using a language that is typed. Using a typed language in our frontend has already paid dividends: our code has fewer type-related bugs, we are more confident making large refactors, and our inline documentation is focused around concepts instead of object shapes and function parameters. Overall, we are very happy with our choice.

If the future of frontend engineering excites you, if you welcome the challenges of building large-scale web apps, if you want to work on a product that impacts 300 million monthly users, or if you just happen to love Reddit, join us! You can find open positions on our Careers Page.