The problem with dynamic types is that it slows down development of an application over time. This decrease in velocity can be attributed to a couple of factors.
Bugs introduced by type mismatches (such as
nulltypes) take time away from feature development.
Each time existing code is examined, the programmer needs to figure out what the intended types are again.
But Why Worry About Types At All?
Programs should be written for people to read, and only incidentally for machines to execute.
- Abelson and Sussman in “Structure and Interpretation of Computer Programs”
The first question you may ask is why we should bother with types at all? The answer to this is simple: Types are like the weather. There is nothing you can do about it. Weather happens.
For example, say you implemented an
add function as follows.
The types for
b are implicitly
number, but there is no way to write this down in code.
Or how about a
greet function as follows.
Can you guess the type for the parameter
name? If you guessed
string then you are close but not quite
correct. The correct type is an optional
string – meaning it can be
Without explicit types, we will inevitably make a mistake and use the wrong ones. Is
add(null, 2) a valid invocation? Obviously not!
But what about
add('1', 2). Maybe? It is hard to know the original intention of the function’s author.
In short, it is very difficult for another programmer (or the future you) to understand what the originally intended types are for every function in an application.
Types To The Rescue!
Let’s look at both functions again with type declarations (using Flow).
It is now clear that
add('1', 2) is not a valid invocation of the function. Furthermore, the Flow
type checker will actually throw a useful error.
(We also know that
add(add(1, 2), 3) is valid, because the return type of
As for the
greet function, it should be clear which of the following statements are valid or not.
Getting Rid Of That Dreaded Null error
null is not a function error, or some variation of it.
This class of error is completely due to type mismatches: Not dealing with nullable types properly.
For example, given a function such as the following.
It isn’t clear whether there is a bug in that code or not.
However, if I were to add some type annotations to it, then it is very clear that there is a bug!
In fact, you don’t even need to manually check the code since Flow will check for you.
Aren’t Types Too Constraining and Troublesome?
There are two common concerns of using a type system:
They are too troublesome to set up.
They constrain the programmer too much.
While both are valid concerns, Flow type – and TypeScript to some extent – provide an almost seemless experience.
For starters, if we take the original
add function, and simply add the
// @flow comment to the module,
then we can already run it through the type checker!
Of course, without type annotations, the type checker is limited in usefulness. It can catch errors such as
add(, 2) but not
add(null, 2), since the latter
As for contraints caused by static types, it simply isn’t true. If you want dynamism, you can
any type to forgo type checking. In fact, the
any type is implicitly the only type available
Adding Types To Your Project Today!
Now that I’ve convinced you that types are really great, then the next step is setting it up in your own
project. I recommend Flow if want to perform a quick experiment, since the set up is faster than the alternatives.
TypeScript is also great, but it will require a bit more work (e.g. configuring
tsconfig.json and renaming
.ts). In a real project you will need to weigh the pros and cons of each technology choice.
Setting Up Flow
You only need to do two things to get Flow running in your project.
Firstly, install the flow binary globally.
npm install -g [email protected]
Secondly, go to your project root and run:
flow init # This creates the .flowconfig file
That’s it! You can now verify that Flow is working in your project.
This should produce no errors since you have not added the
// @flow comment to any modules yet.
Gradually Adding Types
The next step is to start adding types to your modules. I recommend starting with modules that don’t have any dependencies (i.e. no imports), or as little imports as possible. If a module has dependencies, then you will need to type those first before typing the dependent module.
If you are working with third-party modules, you will need to install those type definitions
before you can import them. The best way to get third-party type definitions is using
npm install -g flow-typed
You can search and install type definitions using this binary.
flow-typed search redux
flow-typed install -f 0.30 redux
-f option specifies the Flow version you want to install the definition for.
To automatically include the installed definitions, add an entry to the
[libs] section in the
If you cannot find type definitions using
flow-typed , you can simply provide them yourself.
Start by adding a new
[libs] entry to the
Then define the third-party modules in the
./manual-typings/ folder. For example, the
module can be defined in the
./manual-typings/lodash.throttle.js file as follows.
Here, I’m being lazy with the types and simply declared the default export as
any, which means it will not be type checked.
any type for module declaration is a good starting point for your project, and you can always go back to define
the correct types when you have the time to.
So should you start using types in your projects? It is really up to you! If you do choose to start using types though, you will find that your application becomes easier to maintain because:
The type checker will catch type mismatches for you, resulting in less bugs in production.
It will be easier to read existing code, because it will be immediately obvious what the valid inputs and outputs are.
It is also seemless to integrate types into your application through Flow (or TypeScript). For even more powerful type systems in the front end, you can also check out Elm and PureScript – both of which are in the ML family of languages.