How TypeScript’s Strict Mode Actually Fixes TypeScript

There is no reason not to turn it on by default

Eran Shabi
5 min readAug 12, 2020
Photo by Clem Onojeghuo on Unsplash.

Strict mode is actually a combination of six other flags (as of TypeScript 3.8):

  • noImplicitAny
  • strictNullChecks (since 2.0)
  • noImplicitThis (since 2.0)
  • strictFunctionTypes (since 2.6)
  • strictPropertyInitialization (since 2.7)
  • strictBindCallApply (since 3.2)

Each option can be enabled or disabled separately. Some of them make TypeScript’s type checking better and some help make your code more readable and less error-prone.

You can enable strict mode in your tsconfig.json:

You can disable any option that you don’t like from the strict family in the compileOptions as well (e.g. "noImplicitAny": false).

I think the most important flags are noImplicitAny and strictNullChecks. These two will really improve the type checking and readability of your code.

Let’s take a look at each flag.

noImplicitAny

Without turning noImplicitAny on, you are only mostly using TypeScript because now you have parts of your code that are of type any without you even noticing.

Because the type any basically disables type check, you really shouldn’t have it unless you don’t have a choice. The problem is that it’s really easy to have any in your code by mistake, so by using noImplicitAny, you will now only have any where you explicitly use it.

Let’s see how easy it is to disable type checking by mistake:

In this example, we define a function (fn) that just returns what it gets. What we can easily miss is that the return type of this function is actually any.
You can see that copyOfNum, which is just the same number as num, is now of type any and we can do all sorts of wrong things with it.

If we turn noImplicitAny on, we won’t be able to define a function like fn that returns any implicitly and would have to define it correctly instead, resulting in copyOfNum not losing its type:

Another common way we put any in our code without noticing is when importing an external module:

Here, we just wanted to add 1 + 1 using lodash but got a result of type any because we didn’t have the types of lodash installed. This can be solved by installing @types/lodash. In case we are using an external module with no available types, we can write them ourselves using declare module ‘lodash’ { /* types goes here */}.

If we are OK with this module being of type any or do not have the capacity right now to fix it, we can signal it to the compiler by declaring the module with no types:

declare module ‘lodash’;

strictNullChecks

The most common JS runtime error is probably Uncaught TypeError: Cannot read property ‘foo’ of undefined. This is caused when trying to access a property or call a method on an undefined object.

Luckily, TypeScript can help.

By default, null and undefined are assignable to all other types, while your code actually has many types that can never be null or undefined, making their types wrong by default.

After enabling strictNullChecks, the only types that can be null or undefined are the ones explicitly marked as so. This means you wouldn’t be able to initialize a variable without a value. For example:

What do you get from all this extra work? Now that your types are more specific, TypeScript will be able to analyze your code flow and find potential bugs:

Sometimes you have to opt out of this feature. Maybe some external types are wrong, but you have no way to fix them or you want to gradually introduce this flag. You can always disable strictNullChecks by using the ! symbol.
Any property access that has the !symbol will be ignored in this check:

Only if you enable strictNullChecks will you be able to enable the next flag.

strictPropertyInitialization

With strictPropertyInitialization, TypeScript will throw an error unless all class properties are initialized in the constructor or by a property initializer. This is used to help prevent unintentional access to undefined props in your code:

Sometimes you can’t initialize in the class creation. For example, maybe you use an external service to fetch data. In these cases, you can ignore strictPropertyInitialization by using the ! symbol just like you used it for strictNullChecks:

strictFunctionTypes

In TypeScript, argument types are bivariant (both covariant and contravariant), which is unsound (although you can now fix this in TypeScript 2.6 with --strictFunctionTypes or --strict).

Let’s look at a simple example to understand what it means:

In this example, we call forEach with another function that gets an HTMLElement even though querySelectorAll returns a list of Element (which HTMLElement extends). You can see that we access the element’s offsetHeight, a property that doesn’t exist on Element but only on HTMLElement.

Without strictFunctionTypes, this code will compile, but its types are wrong and can lead to potential bugs. If we turnstrictFunctionTypes on, we will get an error:

Type ‘Element’ is missing the following properties from type ‘HTMLElement’ …

This basically means that we can’t decide that element is HTMLElement instead of Element because then we can use properties that only HTMLElement has inside our function, which is wrong.

strictBindCallApply

With the strictBindCallApply flag, the bind, call, and apply methods are strongly typed. Without this flag, you can mistakenly use these three functions with arguments that don’t match the function they are used on:

noImplicitThis

This acts the same way as noImplicitAny, but for the this type. If you have a function that uses this, but the compiler can’t infer what the type of this is from the code, you will have an error. This can help catch bugs in compile time instead of in runtime. For example:

In this example, we get a runtime error:

Uncaught TypeError: Cannot read property ‘x’ of undefined.

We get an error because we called isEqual with the wrong this parameter (we lost the this binding when we assigned the function to another variable in line 11).

If we had the noImplicitThis flag on, instead of a runtime error, we would get a compile-time error on line 6: ‘this’ implicitly has type ‘any’. We can fix this error by writing the this type explicitly, which will force us to fix the bug:

Conclusion

I hope you now understand strict mode and that you should use it in your code because the benefits are greater than the price — at least for new projects that don’t require migration.

For existing projects, consider adding one strict flag at a time, making the migration more gradual.

--

--