TypeScript or Flow: Which is Better Suited for Your Next Project?

As JavaScript projects become more complex, new tools and practices arise to improve code quality and workflows. In addition to unit testing, static type checkers such as TypeScript and Flow are becoming the norm among professional development teams. Regardless of project size, the benefits of making code more comprehensible while also catching errors during the development phase have proved invaluable. As a result, the question being asked by many is: which tool should I use, Flow or TypeScript? For the context of this article, we’ll be focusing on and using examples with respect to React (given its current popularity), but the analysis will be relevant regardless of what framework you may be using.

As a professional web developer with over five years of experience, I’ve worked on large JavaScript codebases without the use of a type-checking tool. I’ve experienced the associated pitfalls and have seen situations where runtime errors that could have been caught in dev reached production more times than I can count. For the last three years, I’ve primarily used TypeScript, and I can tell you that as a result these kinds of errors were effectively eliminated. Through side projects, I’ve also gained exposure to Flow and have found it to be a great tool to add type safety to a new or existing JavaScript codebase. It’s become clear to me that the use of some form of static type checking is essential in the modern JavaScript ecosystem.

In this article, I’ll provide a brief introduction to these tools and illustrate the way they function. I’ll also explain how to integrate both TypeScript and Flow into a simple React application to show them in action. Then, I’ll weigh their benefits and compare them in terms of functionality, ease of use, community support, and more. Finally, I’ll review the key points to help you decide which of these two tools is best suited for your project.

TypeScript Overview

TypeScript is a programming language developed by Microsoft. It is open-source and is supported by a large and active community. As mentioned on its official website:

“TypeScript is a typed superset of JavaScript that compiles to plain JavaScript.”

Let’s focus on two words of the previous statement, “typed” and “superset.” The word “typed” indicates that TypeScript requires that the programmer declare the datatype of a given variable. The word “superset” indicates that TypeScript allows the programmer to use all the features that JavaScript provides, plus some additional TypeScript-specific features like interfaces, generics, decorators, etc.

The following images present a high-level overview of the way that TypeScript functions. The compiler receives TypeScript files (.ts or .tsx) and then “transpiles” them into valid JavaScript code that can be run by the browser.

Let’s take a look at some simple code written in TypeScript:

function getName(person: IPerson): void { 
    console.log(person.name);
}

interface IPerson {
    name: string
    age: number
}

getName({
    name: "John",
    age: 38
}); // logs “John”

getName({
    age: 38
}); // Intellisense indicates an error: Argument of type '{ age: number; }' is not assignable to parameter of type 'IPerson'. 
TypeScript

This is TypeScript in a nutshell. You can find a more detailed analysis and guide on TypeScript in one of our recent articles. As we can see in the above block, we declare a function that expects an object that has two properties, name and age of type string and number respectively. When calling the function, TypeScript checks that the provided object is of the correct type, and if it isn’t – like in the second getName() call – the code will fail to compile and throw an error.

Flow Overview

Flow, in contrast to TypeScript, is not a language; it’s described as a “static type checker for JavaScript”, and was developed by Facebook (the creators of React). We can use Flow by adding special annotations to our regular JavaScript files, indicating the types we expect, or we can just let the tool infer the expected types and warn us if it finds any errors.

Let’s take a look at the following code sample taken from Flow’s official documentation:

// @flow
function square(n: number): number {
  return n * n;
}

square("2"); // Error!
JavaScript

Notice the first line of the above code? In order for the tool to know which files it has to check, we place a special “// @flow” comment in each file we want to be included in Flow’s monitoring process.

Just to clarify, in order to use Flow, you don’t have to change the extension of your files, instead, you continue to write vanilla JavaScript in annotated .js and .jsx files. If we had left the above code as is, the JavaScript engine would have thrown an error because of the annotations; therefore, as an additional step, we have to remove the annotations before we send this code to be executed. We can achieve this with Babel or with a lighter-weight alternative CLI tool called flow-remove-types.

The above image shows how Flow annotations get removed in order for the final JavaScript code to be executable.

Integration with React

Now that we have seen an overview of TypeScript and Flow, let’s look at how these tools integrate with React. In doing so, we’ll later be able to compare both tools to identify their strengths and weaknesses and consider under what circumstances we would choose one over the other.

A Standard React App

The easiest way to create a React app is to use the create-react-app tool. We are going to create two identical React apps, one for testing TypeScript and one for testing Flow. First, let’s look at the vanilla implementation of this tool by creating a React app without any type checking:

npx create-react-app example-app
None

That’s it, our app is ready! Now we have to navigate to the example-app folder and start the development server to view our React app in action:

cd example-app
npm start
None

Your default browser should open a tab pointing at localhost:3000, and the following page should be rendered.

Enabling TypeScript

If we’re starting from scratch, we can create a TypeScript-enabled React application just by using the –template flag like this:

npx create-react-app react-ts --template typescript
TypeScript

And we would have come up with a fully configured React app with TypeScript enabled. That’s a great option for a new project, but let’s imagine we want to implement TypeScript in an existing React application.

First, we have to add the TypeScript package alongside some other dependencies. We are going to use yarn to install the packages but feel free to use npm if you prefer:

yarn add typescript @types/node @types/react @types/react-dom @types/jest
TypeScript

After the installation completes, we are going to rename our existing .js and .jsx files to .ts and .tsx respectively.

If we then restart our development server, a tsconfig.json file will be created in the root folder of our project. This file is the TypeScript configuration file and, if needed, it can be customized to suit the needs of your project. Now you can start developing in TypeScript!

Developing with TypeScript

Let’s create a React component that would get a list of items, as props, and render them as an unordered list.

import React from "react";

interface Props {
    items: Item[]
}

export interface Item {
    id: number,
    name: string
}

function ItemsList(props: Props) {
    const listItems = props.items.map(item =>
TypeScript
  • {item.name}
);
    return (
TypeScript
    {listItems}
);
}

export default ItemsList;
TypeScript

TypeScript lets us declare the type of object that we expect by using interfaces. Here, we declare the Props interface that has a property item, an array of objects of type Item – another interface that has two properties, an id of type number and a name of type string, both required. Then, we say that the props parameter of our function component ItemsList is an object of type Props by adding the annotation props: Props.

Let’s implement the ItemsList component in our App.tsx file and declare a const called items, just as an array with a dummy object in it to see how TypeScript reacts:

You can see that an error is shown stating that Item must include the id and name properties. TypeScript is saving us from producing errors if we tried to run the application at this point. Now let’s remove the typing of our items const to see if this error goes away:

Even if we don’t declare that the items const should be of type Item[], TypeScript is smart enough to catch that using it with our ItemsList component isn’t safe. Now let’s fix this by adding two proper records to the component:

const items: Item[] = [
    {
      id: 1,
      name: "One"
    },
    {
      id: 2,
      name: "Two"
    }
  ];
TypeScript

We now see the application compiles and executes successfully:

Thanks to TypeScript, we’ve saved ourselves from running potentially buggy code, while also making the code itself more readable by explicitly declaring the types used throughout our application.

Enabling Flow

Now let’s repeat the process with Flow. Starting from another newly created React app, we are going to install the flow package. Instead of using a template flag, we do the following:

yarn add flow-bin
None

That’s all we need to install, much easier compared to TypeScript. We then need to add a “flow”: “flow” script to our package.json scripts section in order to run the following:

npm run flow init
None

Developing with Flow

We are going to create the same ItemsList component as we did in the TypeScript example, in order to get familiar with Flow:

// @flow
import React from 'react';

function ItemsList(props) {
    const listItems = props.items.map(item =>
JavaScript
  • {item.name}
);
    return (
JavaScript
    {listItems}
);
}

export default ItemsList;
JavaScript

In order for us to be able to start using Flow, we need to run:

yarn flow

..and then Flow performs a check of all the files where we entered the “// @flow” comment. In our case, the check returns an error:

Let’s fix the above error by adding some types:

// @flow
import React from 'react';

type Item = {
    id: number,
    name: string
}

type Props = {
    items: Item[]
}

function ItemsList(props: Props) {
    const listItems = props.items.map(item =>
JavaScript
  • {item.name}
);
    return (
JavaScript
    {listItems}
);
}

export default ItemsList;
JavaScript

Now, if we run the check again, it reports no errors. As you can see, we have to run the same command every time we want to check our files with Flow. This is counterproductive, so we have to find a way to automate this process. For those using VS Code, the Flow Language Support can be used to automatically perform a Flow check after each save. Other IDEs will have equivalent capabilities, just search to find the implementation relevant to your environment.

Comparing TypeScript and Flow

Now that we have seen both tools in action, let’s contrast them against each other:

TypeScript pros

  • More than just a type checker: TypeScript adds additional data structures like Enums to JavaScript which developers coming from other languages may be missing. It also has interfaces, decorators, and other capabilities that make it more robust – enabling developers to write extremely comprehensive code. These features can be especially powerful in large and enterprise-style projects.
  • Developed by Microsoft: TypeScript is receiving regular updates and will continue to evolve. Many professionals predict that TypeScript is going to “take over the JavaScript world”, as Richard Feldman stated at ReactiveConf 2019. Given its growing popularity, it seems safe to say that TypeScript will have longevity greater than most other “trends” in the rapidly evolving JavaScript ecosystem.
  • Large community: As we will review below, TypeScript has a large and active community of people willing to contribute to its development and to help others by answering their questions or writing helpful tutorials. Beyond its official documentation, you can find plenty of unofficial resources on the subject of TypeScript.

TypeScript cons

  • Steep learning curve: TypeScript can be strict and unforgiving out of the gate, turning developers off of it. It’s more difficult and complex to learn than Flow due to the fact that it’s more robust and is considered to be a programming language (or at least “superset” of JavaScript) on its own. TypeScript can also feel like an all-or-nothing approach, which can complicate things and slow down development in large projects with lots of dependencies.
  • Boilerplate code: Some argue that TypeScript precipitates a large amount of boilerplate code which can increase development time and make files harder to understand. Code minimalists may prefer the lightweight Flow (or no type checking at all) in this case.

Flow pros

  • Ease of use: Flow is more forgiving than TypeScript, and serves as a softer introduction to static typing in JavaScript. It’s quicker to get up and running, and it also may be easier to add Flow into an existing project due to its per-file opt-in methodology.
  • Developed by Facebook: The company that developed React, so you can be sure that both tools are fully compatible and meant to be used together.

Flow cons

  • Smaller community: Flow has a much smaller and less active community, as can be seen in the below section. This means there are fewer resources available for those trying to learn how to use it and debug issues that may arise. It may also mean that its future with respect to support and added features is more uncertain than that of TypeScript.
  • Less Robust: Flow does type checking well, but that’s it. It lacks some of the “quality of life” features for developers that TypeScript has. An argument can also be made that it’s less effective for catching errors due to its relative permissiveness and opt-in behavior.

The above shows that there is no clear winner between the two and context is what matters. TypeScript may be better suited for more enterprise projects with a long support horizon, also by considering that in such a project developers can make use of its more advanced features. Flow may be a better choice for leaner projects, or as a way to introduce type checking into an already existing project without it being too painful. It’s up to you to decide which tool best suits your project and circumstances.

But before doing so, it’s worth expanding on the subject of community size between the two. This impacts how easy it is to find developers having experience with these tools, which can make the staffing process simpler (or more challenging) based on what experience you’re looking for.

We can clearly see that TypeScript is more popular than Flow. It’s also worth noting that Flow has a relatively high number of pull requests and issues given its utilization in comparison to TypeScript. This could potentially indicate that the Flow team is less effective at managing bugs and change requests for the project, or it could just be a statistical blip.

Now let’s compare downloads via npm:

Again we see TypeScript’s popularity versus Flow over the last year. It’s also worth noting the clear upward trend in TypeScript downloads. It looks like Flow may be at the start of a decrease in utilization as well.

If you’re wondering why there’s such a dip around year-end 2019, it’s a pattern seen across all package managers as developers are less active during that period due to holidays, both for professional and personal projects.

We can also compare the tags of both tools on Stackoverflow to have an idea of how likely it is to find an answer to a potential problem. For the [TypeScript] tag we can see that there are about 116.8k questions about TypeScript, and on the other hand, for the [flowtype] tag, we can see that there are about 2.2k questions. This is another indication that TypeScript has a bigger community than Flow and that it’s more likely to have a question answered on the topic. However, I supposed one could make an argument that because Flow is so lightweight and straightforward, it doesn’t create an active discussion on sites like Stack Overflow, which is typically used by those experiencing problems with a piece of technology while looking for solutions.

Conclusion

There are clear differences between both TypeScript and Flow. With respect to functionality, TypeScript is more robust, while Flow is simply a type checker – and potentially all you need if type checking and the related reduction in errors is your main goal. Although Flow is created by Facebook – the creators of React – its framework-agnostic nature (much like TypeScript’s) provides no significant benefits specifically for React, beyond a similar design philosophy.

When choosing one over the other, for those concerned with community and a long-term support horizon, TypeScript would seem to be a better choice. Conversely, for those seeking a lightweight and more accessible approach, Flow is worth taking a look at.

When it comes to staffing a project, it seems clear that it will be much easier to find TypeScript developers given its popularity when compared to Flow. When sourcing for a Flow project, given its ease of use, it’s likely safe to say that most developers could pick it up on the fly. However, due to the learning curve of TypeScript, it may be desirable to find developers with previous experience so that a project is able to move quickly.

Regardless of the tools being used, here at Scalable Path, we’ve got JavaScript developers of all types ready to staff your next project.