After using Flow for a while, here are my grievances:

inexact types

The following compiles with no complaints:

// @flow
type Foo = {
  a: string
}
function foo(arg: Foo) {}

foo({})
//  ^ no error!??!

exact types

More trouble than their worth. TypeScript doesn’t have exact types, but it has heuristics for warning about extra properties that work well:

type Foo = {
  a: string
}

function foo(arg: Foo) {}

foo({ a: "", b: "" })
//           ^^^^^
// Argument of type '{ a: string; b: string; }' is not assignable to parameter of type 'Foo'.
//   Object literal may only specify known properties, and 'b' does not exist in type 'Foo'.

const bar: Foo = {
  a: "",
  b: ""
  // ^
  // Type '{ a: string; b: string; }' is not assignable to type 'Foo'.
  //  Object literal may only specify known properties, and 'b' does not exist in type 'Foo'.
}

type imports

You can’t import a type like a value, you have to prefix the import with type. Otherwise you get an error:

Cannot import the type Foo as a value. Use import type instead. [import-type-as-value]

This adds unnecessary friction, TypeScript also has type imports, but they’re optional.

refinement invalidations are more trouble than their worth

Yes, invalidating a refinement avoids some possible runtime errors, but it makes a lot of code more verbose. I think the tradeoff of more unsoundness for easier usage that TypeScript makes is better.

no Pick type and many more

Flow has a limited set of utility types compared to TypeScript. This makes it harder to properly type functions.

this. is accessible in functions without any errors

// @flow
function bar() {
  this.foo()
}

This compiles with no errors and explodes at runtime.

empty type shows up in error messages, but is no where to be found in the docs

https://github.com/facebook/flow/search?q=empty

$Shape exists and is unsound