One area in which Flow may be "technically" sound but it feels illogical is how in how it infers certain types. TypeScript requires that a variable has one type that doesn't change, but Flow will infer multiple types for a variable at different points in the code depending on how it is used. Also because Flow infers types based on how they are used, there are some interesting situations where you would expect the type checker to complain but it doesn't. E.g. if you have an object with a field that is never used, Flow won't complain about the field even if it is not defined in the type of the object. Overall I feel like TypeScript enforces logical, opinionated defaults, while Flow is happier to go with the illogical structure of your own usage (while enforcing opinionated defaults in other areas like null-checking).
Ultimately it boils down to the notion that inference is about understanding the type, and annotations are about expressing it.
If you write a type annotation for a variable, then Flow will of course not infer anything more or less than your annotation. If, however, you use the variable as multiple types (but do so in a way that is clearly safe), Flow infers the union so that it doesn't give you errors for code that is clearly ok.
I think the example from the talk was something like:
var name = "Jeff";
name = name.toUpperCase(); // safe
if (loggedOut) {
name = null; // safe
}
var firstInitial = name ? name[0] : null; // safe
The above code has no errors in it, and because flow infers `name` as a type `null | string`, Flow is able to verify its safety and thus doesn't error.
OTOH, if we use an annotation to express the type of `name` as intended as only `string`, then we would get an error on the null assignment:
thing like:
var name: string = "Jeff";
name = name.toUpperCase();
if (loggedOut) {
name = null; // Error!
}
var firstInitial = name ? name[0] : null;
So in summary: Inference is the means by which Flow understands, annotations are the means by which you express to Flow your intentions.