What Is Strict Mode In TypeScript, Why And When You Should Use It?

This post is part of series and book about TypeScript. It will guide you from scratch to writing full TypeScript applications on Back End and Front End. The series is available as PDF eBook for free to everyone.

There are many options and parameters that can be used in the TypeScript configuration. One of the important "groups" of options is strict options. These options provide us the ability to write the safest code. How? Because TypeScript was developed primarily as superset of JavaScript, by default it allows us to write code that is not super strict in types and other aspects. It was made like this because TypeScript should be easily used by JavaScript developers. JavaScript doesn't have static types and is more flexible in some aspects of programming. It has many features to write code faster and easier (at first). However, the other side of this is that you probably will have more places where your code is unsafe. It means that the count of cases where your program will fall is big. TypeScript partially solves this problem.

What Is Strict Mode?

You may probably have heard about JavaScript strict mode and TypeScript strict mode. The point is that they are different kinds of things.

TypeScript strict mode is a bunch of TypeScript compiler parameters (that can be also be represented by one "main" parameter) that make TypeScript compiler tsc force you to follow specific rules of writing code in TypeScript. All these rules are directly related to types and managing types in the TypeScript code. For example, one of the rules forces you to always write a type of parameter in every function.

TypeScript "strict mode" could be enabled in the TypeScript configuration file by one OR a few parameters. It's a group of parameters that can be enabled individually or can be enabled all at once by one parameter.

The "main strict mode" parameter is called strict. It is a part of compilerOptions in tsconfig.json. If you set this parameter to true it will automatically set specific several parameters of configuration to true. Another important nuance is that if you enable strict parameter instead of individual strict checks parameters, the TypeScript compiler will automatically check your code with all new strict checks parameters that TypeScript may have in the next versions.

Strict checks parameters are:

  • noImplicitAny
  • strictNullChecks
  • strictFunctionTypes
  • strictBindCallApply
  • strictPropertyInitialization
  • noImplicitThis
  • alwaysStrict

I will introduce you to each of these parameters in next posts of the series. Let's focus on strict mode itself.

Should I Use Strict Mode?

What happens when you set this parameter to true? Well, it depends on is your project new (just created) or not. If you don't have any code in your project except configuration files or files with boilerplate code that was generated by tools like create-react-app then tsc should compile the project without errors. But if your project is not new, have a huge amount of files and code and you didn't follow specific rules of strict mode then you probably will get a list of errors provided by tsc after you attempted to compile the project.

And this is an answer to the question "Should I use strict mode in TypeScript or not?". If you just start a new project I highly recommend using strict mode in TypeScript. It will help you to avoid errors, typos, and mistakes in your code in the future. Strict mode constricts you in ways of writing your code. On other hand, you won't choose the way that will bring you to make a mistake. Sometimes a simple, verbose, and "boring" code is better than "cool" one-line code because it won't be failed.

Why strict mode in TypeScript can be enabled by several parameters? These parameters can be used in an existing project. You probably won't be able to set parameter strict to true in an existing project. But you can set some strict mode parameters to gradually rewrite your codebase following strict mode rules. For example, you can set the parameter noImplicitAny to true and rewrite your code to one that is following this rule (you should write a type of parameters in every function). You probably won't have time to rewrite all projects but you can improve specific parts of your code. Next time when you will have time to improve code quality you can set one more strict mode parameter (that suits your by time, number of changes in code, etc).

In Practice

Let's look at the example. Here is the simple console app that asks a user to type its name and then if the user exists in a database (which is basically JSON file with an array of users) prints greetings in a terminal. The source of an example is available here. Just create a new TypeScript project and create a few files with the code presented below.

git clone https://github.com/kowalevski/ts-node-sample/tree/strict-mode-example

The app has three files or modules with functions. The entry point of the app is file src/main.ts:

import { createQuestioner } from "./createQuestioner";
import { greeting } from "./greeting";
import usersDB from './users.json';

async function main() {
  try {
    const questioner = createQuestioner();
    const username = await questioner.ask("Type your name: ");

    const foundUser = usersDB.find((user) => user.username === username);

    greeting(foundUser.username);

    questioner.finishUp();
  } catch (e) {
    console.error(e);
    process.exit();
  }
}

main();

We are not interested in the module createQuestioner. Let's perceive it as a third-party library with some API. What we really need to focus on is file src/greeting.ts:

export function greeting(names) {
  console.log(`Hello, ${names.map((name) => name.toLowerCase()).join(', ')}!`);
}

Pretty simple code, isn't it? We didn't specify the type of parameter names but we expect that there will be an array of strings.

Okay, let's run our small application. Did you see any issues in the code? Because strict mode is disabled in the TypeScript configuration of this app we shouldn't see any errors in the editor.

Run the app by the following command:

tsc && node dist/main.js

Or

npm run dev

After typing a name (it can be whatever name you want) you should see that there are errors in our code. But they were identified only after running Node, i.e. in runtime. So, the TypeScript compiler didn't find any problems in the code.

Now, let's enable strict mode and see what happens. After enabling strict parameter in tsconfig.json you should see errors in your editor (if it supports TypeScript):

And if you run TypeScript compilation you will see that TypeScript highlight the error:

src/greeting.ts:1:26 - error TS7006: Parameter 'names' implicitly has an 'any' type.

1 export function greeting(names) {
                           ~~~~~

src/greeting.ts:2:36 - error TS7006: Parameter 'name' implicitly has an 'any' type.

2   console.log(`Hello, ${names.map((name) => name.toLowerCase()).join(', ')}!`);
                                     ~~~~
Found 2 errors

Basically, the problem is that we didn't specify the type of parameter names in a function greeting. It means that the function can be called with wrong arguments in module main. Specify the type in src/greeting.ts. It should be an array of string values:

export function greeting(names: string[]) {
  console.log(`Hello, ${names.map((name) => name.toLowerCase()).join(', ')}!`);
}

Great! Let's see that we have in a file main.ts. In an editor, you probably may see that there is an error. Let's run TypeScript and see what's wrong here.

src/main.ts:12:14 - error TS2345: Argument of type '{ username: string; age: number; } | undefined' is not assignable to parameter of type 'string[]'.
  Type 'undefined' is not assignable to type 'string[]'.

12     greeting(foundUser.username);
                ~~~~~~~~~~~~~~~

Found 1 error

Okay. So, the problem is that we put a value of variable foundUser as itself (it is an object or undefined) but a function greeting receives an array of string values. It seems like a small typo because all we need to do is write argument foundUser.username as bracketed. Also, because variable foundUser could be undefined we need to check that is it false or not:

async function main() {
  try {
    const questioner = createQuestioner();
    const username = await questioner.ask("Type your name: ");

    const foundUser = usersDB.find((user) => user.username === username);

    if (!foundUser) {
        console.log('User is not found');
        questioner.finishUp();
        return;
    }

    greeting([foundUser.username]);

    questioner.finishUp();
  } catch (e) {
    console.error(e);
    process.exit();
  }
}

Now, let's run the app again:

npm run dev

It should work correctly.

Conclusions

So now you know that even code that compiles by tsc successfully can have issues and incorrect behavior when this code is running. The goal of TypeScript is to prevent times when the application works with errors or doesn't work at all before the code of the application executes. TypeScript analysis your code and based on types that were specified by you or by type inference and highlight part of the code with potential risk. And the best way it works is in strict mode. Using strict mode you will catch more potential errors and typos in your codebase before running the program.

Do you like the material? Please, subscribe to my email newsletter to stay up to date.

29