TypeScript vs. Flow

TypeScript and Flow are similar tools that focus on the same problem: JavaScript's lack of static types. Ultimately, they both exist to help you write correct code. Both TypeScript and Flow provide gradual static typing capabilities. They also use a similar syntax for type annotations and declaration files.

Backed by Microsoft and Facebook, both tools enjoy widespread usage among the developer community. When it comes to choosing between TypeScript and Flow, there are valid reasons for picking one over the other. The ecosystem you're working in usually has a big influence on your decision, as does your previous exposure to static type systems.

Deciding Between TypeScript and Flow

If you're working with Angular 2+, you might favor TypeScript because it's the dominant language used in the Angular community. If you're using React, on the other hand, you might prefer Flow because it easily integrates with Babel and other infrastructure you probably already have in place.

There are other factors you might take into account, too. In addition to focusing on static typing, TypeScript wants to provide great tooling and language services for autocompletion, code navigation, and refactoring. Flow, on the other hand, develops a deeper understanding of your code and even does interprocedural analyses.

All of the above are valid reasons for picking TypeScript or Flow over the other. However, I sometimes hear people advocate for Flow (and against TypeScript) because "it's just a type checker, while TypeScript is a separate language". That is not a valid reason, and here's why.

It's Not Just Type Checking

As soon as you're using a single type annotation, type alias, or some other Flow feature, you're no longer writing standard JavaScript. That is, you can no longer directly run that piece of code in the browser or another JavaScript environment like Node. Here's a simple Flow example, taken directly from the landing page of their website:

// @flow
function bar(x): string {
  return x.length;
bar('Hello, world!');

Try to run this piece of code in your browser console. It won't work! Instead, you'll get some error message like Unexpected token : because the parser complains once it encounters the return type annotation. In order to run this code successfully, the type annotations have to be removed.

This is where TypeScript and Flow differ: TypeScript implements both a type checker and a transpiler that emits plain JavaScript. Flow only does type checking and relies on Babel or flow-remove-types or some other tool to remove type annotations.

So, let me be clear about this: No matter if you're using TypeScript or Flow, you're not writing plain JavaScript. You're using non-standard language features. That way, both TypeScript and Flow should be considered to be languages different from JavaScript. TypeScript actively acknowledges this fact by using the .ts file extension rather than the standard .js one.

Putting Type Annotations in Comments

That said, there is a way to use Flow without writing non-standard JavaScript code. Type annotations can be placed within code comments and Flow will still recognize them. Here's the example from before:

// @flow
function bar(x) /* : string */ {
  return x.length;
bar('Hello, world!');

Now, this is valid JavaScript code that you can execute in every environment, no pre-processing necessary. However, I don't really like this approach because I think the additional syntactic noise makes it harder to read the code. Also, comments aren't a suitable place to host a type system.


Both TypeScript and Flow are great products. Both help you write correct code. You don't go wrong using either one, so pick the one that better suits your team's needs. However, please don't suggest that Flow isn't a separate language while TypeScript is — unless you're strictly putting type annotations in comments, you'll be writing non-standard JavaScript either way.

Use the coupon code LAUNCHDAY for $10 off!

Learn ES6


Hernan Rajchert

It would be nice if both languages provide a separate mechanism to type based on comments but like haskell does it, not interwined with the function but on top, something like

// count :: String -> Number function count(str) { return str.length; }

It would be useful to type regardless of how you create the function, for example

// count :: String -> Number const count = str => str.length

And specially useful when having functions with multiple parameters and sometimes with generic types. When you have that and you interwine the code and the types they easily escape the 120 char length

Jochen H. Schmidt

Hi Marius,

Actually TypeScripts Goals are significantly more than just providing "Types". It provides a plethora of other language features. It extends the object system. In general it defines a language that fits better in typical .NET and C# environment. Microsofts stance regarding ECMAscript compatibility is definitely to propose TypeScript features for ECMAscript to make it fit better in their developer ecosystem. I personally think that this ecosystem doesn't fit well with what JavaScript was and what other trends turned out.

Flow on the other side is indeed "just a type checker". The (optional) syntax extensions only are about types. The goal of the project is to check types. Flow emphasizes inference more, because they ideally want to just write Javascript and let the system recognize the constraints. Transpilling Flow is just throwing away type annotations and not to implement any other language feature. This IS a difference. To me it is a big one.


The arguments for Flow were stronger when it first launched: it supported non-nullable types, it worked with CommonJS, supported JSX and it did type inference. But TypeScript does all those things now, too.

This article concludes that "You don't go wrong using either one, so pick the one that better suits your team's needs." But unless your needs include declaring types in JS comments, is there any concrete reason to use Flow over TS in 2017?

Alan Ball

Comment style annotations are also (mostly) possible in typescript. https://github.com/Microsoft/TypeScript/wiki/JSDoc-support-in-JavaScript

Alan Ball

Hernan I totally agree. ML style function notation is soooo much better. No need to declare variable types either as they can be inferred. //add:: int -> int -> int var add = (x,y) => (y) => x + y //addNonPartial:: (int, int) -> int var addNonPartial = (x,y) => x+y

add is a function, which takes an int, returns a function which takes an int, which returns an int.


With flow you can use Babel to remove the typing and get your plain js back the way you wrote it. Say if you wanted to switch to something else right? Or if Flow fades out of use.

Can you do that with Typescript? You're pretty much stuck with the compiled code if you wanted to switch to something else in the future?

Richard Simpson

@Steve you could effectively do the same by targeting ESNext with Typescript. That'd basically strip out all the type information and leave the rest as is. I know some people do this to take advantage of Typescript's typing/tooling, but then pipe Typescript's output into Babel for down compilation.


@Hernan You're describing Google Closure



Yes. Even in the latest version of TypeScript, type inference and control flow analysis is still very weak. When running in no implicit any mode, you need to provide many more annotations than in Flow. That is still true. TS is also much more naive in its type inference, and you more frequently have to define the types for a generic function to pick up the right version. Additionally, while TS made some strides with mapped types recently, Flow has a few more of that kind of features (special types). TS introduces a lot of concepts for historical reasons (eg: namespaces, enums, tons of class features, etc) that you might not want your team to use, and that's one more thing to drop the hammer on.

On the flipside, Flow's Windows support is still very buggy, and IDE support is essentially inexistant (it's there, but so buggy its hard to use, in all of the major editors), and only a small fraction of Flow's features are documented, which makes it look feature anemic (it's not). But Flow using Babel for parser means its easier to integrate with an existing that ecosystem (eg: the recent Prettier project won't work with TypeScript).

So yes, Flow lost a lot of its advantages over the years, and TS is winning the popularity and tooling contests by a landslide, but there's still many reasons for Flow.


At work we have a fairly large react codebase that was typed with Flow from Day 1, for about a year now.

We are currently in the process of completely replacing Flow with Typescript (a fairly straightforward process that consists mostly of changing the file endings and minor syntax adjustments ). In my experience Flow is very far from being ready for production use.

The tooling around Flow is incredibly unreliable. We have a lot of files Flow happily reports a 100% Type coverage on, but when trying to inspect types, they are all shown as (unknown) or any. Flow for us was mostly silent on things that were obvious errors, and complaining loudly with unhelpful error messages that rarely help you find the actual source of a problem. Overall the entire purpose of a type checker, confidence that some kind of errors are absent, is completely defeated by the lax handling of weakly typed expressions.

Development on the Flow project is slow and obscure and the community around it is just way smaller than with TS.

Converting from Flow to Typescript has been a bliss so far. As long as you enable --strictNullChecks and --noImplicitAny, TS will complain early about missing annotations and will always provide very helpful error messages that make it easy to pinpoint the source of a mistake. Combined with editor integration that works great (We're using Visual Studio Code) this is a huge improvement over the Flow experience.


@Francois Can you share an example where Flow can infer types but TS can't?

Francois Ward


I mean, anything that requires call site analysis of parameters will do. Eg:

function foo (a, b) { return a + b; }

const num = foo(1, 2);

function bar(c: string) { console.log(c); }


In current typescript, "num" is "any". In Flow, it's a number and will fail the check when trying to pass it to bar. In Flow you just need to annotate exports (because Flow doesn't do cross module inference. It will, however, infer those as "mixed" which is stricter/better than "any"). But aside for having to annotate exports, you don't need to annotate anything to have a fully typechecked program, give or take a few exceptions in classes (which will fail without loose mode, not infer any).

TypeScript's "any" inference is a pain in the butt. You can use the option to avoid inferring any, but all it does is error out when you're missing type annotations. Modern languages like Elm, and type systems like Flow, let you only add annotations when it actually makes sense or are needed.

I know some people like the added documentation of annotating everything. I find annotating anything but important or public functions to just be a productivity waster.


@Francois I don't want to be able to write a function without specifying the type that it returns. It's a recipe for disaster. Laziness can sometimes be good but in this case it's a terrible idea.