How strict is Typescript's strict mode?
Tags: beginners, typescript, javascript, bestpractices
Published: 2019-06-01 19:19:47

---
title: How strict is Typescript's strict mode?
published: true
description: Answer: stricter than Typestrict
tags: #beginner, #typescript, #javascript, #bestpractices
cover_image: https://images.unsplash.com/photo-1508726096737-5ac7ca26345f?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1300&q=80
---

I started out writing code in Javascript without proper typing. When I switched to Typescript, I migrated my code without turning the strict mode on because I knew that the typing refactor was going to be too much to handle, and I should focus on passing the unit tests first.

Even without the strict mode, it was already a shift of paradigm because you have to specifically define most of the things unlike Javascript. I thought I've already strict enough with my types back then. But how strict is strict mode?

According to the [docs](https://www.typescriptlang.org/docs/handbook/compiler-options.html), when Typescript strict mode is set to on, it will validate your code using the strict type rules under the 'strict' family to all files in the project. The rules are:

- `noImplicitAny`
- `noImplicitThis`
- `strictNullChecks`
- `strictPropertyInitialization`
- `strictBindCallApply`
- `strictFunctionTypes`

These are some of the lessons I learned when I bumped into these rules.

## 1. `noImplicitAny`
This rule disallows variables or function arguments to have an implicit `any` type. Consider this example:

```javascript
// Javascript/Typescript non-strict mode
function extractIds (list) {
  return list.map(member => member.id)
}
```

Looking at the code, `list` can be _anything_. Sure, from the `.map` you would think that it is an array of members, and the `member` has property called `id` but there isn't anything that specifically defines that. This is why it is an error in `strict` mode.

```typescript
// Typescript strict mode
function extractIds (list) {
  //              ❌ ^^^^
  //                 Parameter 'list' implicitly has an 'any' type. ts(7006)
  return list.map(member => member.id)
  //           ❌ ^^^^^^
  //              Parameter 'member' implicitly has an 'any' type. ts(7006)
}
```
 

A fixed version would be:

```typescript
// Typescript strict mode
interface Member {
  id: number
  name: string
}

function extractIds (list: Member[]) {
  return list.map(member => member.id)
}
```

Another common code that you might see in the wild:

```typescript
// Typescript strict mode
function onChangeCheckbox (e) {
  //                    ❌ ^
  //                       Parameter 'e' implicitly has an 'any' type. ts(7006)
  e.preventDefault()
  const value = e.target.checked
  validateCheckbox(value)
}
```

You can use some of the global types defined by the Typescript itself for, in this case, [browser-specific types](https://github.com/microsoft/TypeScript/blob/master/lib/lib.dom.d.ts):

```typescript
// Typescript strict mode
interface ChangeCheckboxEvent extends MouseEvent {
  target: HTMLInputElement
}

function onChangeCheckbox (e: ChangeCheckboxEvent) {
  e.preventDefault()
  const value = e.target.checked
  validateCheckbox(value)
}
```

Do note that it would also throw an error if you import libraries that has no type definitions, because that would imply that the imported library has an `any` type.

```typescript
// Typescript strict mode
import { Vector } from 'sylvester'
//                  ❌ ^^^^^^^^^^^
//                     Could not find a declaration file for module 'sylvester'.
//                     '/foo/node_modules/sylvester/lib/node-sylvester/index.js' implicitly has an 'any' type.
//                     Try `npm install @types/sylvester` if it exists
//                     or add a new declaration (.d.ts) file containing `declare module 'sylvester';` ts(7016)
```

It can be a bottleneck in your project since you might end up having to write that type definitions yourself if there isn't any, but having _all_ types defined should've been the right thing to do anyway, at least in strict mode.

## 2. `noImplicitThis`
This rule disallows `this` context to be implicitly defined. Consider this example:

```javascript
// Javascript/Typescript non-strict mode
function uppercaseLabel () {
  return this.label.toUpperCase()
}

const config = {
  label: 'foo-config',
  uppercaseLabel
}

config.uppercaseLabel()
// FOO-CONFIG
```

For someone who's been writing Javascript, it is known that `this` refers to the `config` object, so `this.label` would just be retrieving `config.label`, which is why this code works. However, referring to `this` on a function can be ambiguous.

```typescript
// Typescript strict mode
function uppercaseLabel () {
  return this.label.toUpperCase()
  //  ❌ ^^^^
  //     'this' implicitly has type 'any' because it does not have a type annotation. ts(2683)
}
```

If we run `uppercaseLabel` alone, it would throw an error because `this` context is not on `config` anymore, hence the error because `label` is undefined.

One way to fix it is to avoid using `this` on a function without a context:

```typescript
// Typescript strict mode
const config = {
  label: 'foo-config',
  uppercaseLabel () {
    return this.label.toUpperCase()
  }
}
```

Typescript won't even complain on this because all types are inferred properly. Or, even better, write the interface, so that all types are now defined instead of inferred.

```typescript
// Typescript strict mode
interface MyConfig {
  label: string
  uppercaseLabel: (params: void) => string
}

const config: MyConfig = {
  label: 'foo-config',
  uppercaseLabel () {
    return this.label.toUpperCase()
  }
}
```

## 3. `strictNullChecks`
This rule validates the possibility of values returning null or undefined. Consider this example:

```javascript
// Javascript/Typescript non-strict mode
function getArticleMetaById (articles: Article[], id: string) {
  const article = articles.find(article => article.id === id)
  return article.meta
}
```

Now, of course I would have checked the code first in the browser if it works (and it did). However, in strict mode, Typescript would remind me that there are chances that [`.find` would return undefined](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find) when none of the ids in the responses would match the given id.

```typescript
// Typescript strict mode
function getArticleMetaById (articles: Article[], id: string) {
  const article = articles.find(article => article.id === id)
  return article.meta
  //  ❌ ^^^^^^^
  //     Object is possibly 'undefined'. ts(2532)
}
```

This would actually broaden my code specification so that now I have to actually handle error cases as well, which should've been done in the first place.

```typescript
// Typescript strict mode
function getArticleMetaById (articles: Article[], id: string) {
  const article = articles.find(article => article.id === id)
  if (typeof article === 'undefined') {
    throw new Error(`Could not find an article with id: ${id}.`)
  }

  return article.meta
}
```

## 4. `strictPropertyInitialization`
This rule validates properties in a class to be initialized either inside a constructor function or already defined before constructed. Consider this example:

```javascript
// Javascript
class Student {
  constructor (grade, lessons) {
    this.grade = grade
    this.lessons = lessons.filter(lesson => lesson.grade <= grade)
  }

  setRedoLessons (lessons) {
    this.redoLessons = lessons
  }
}
```

With Typescript, all class instance properties can be defined properly.
```typescript
// Typescript non-strict mode
interface Lesson {
  title: string
  grade: number
}

class Student {
  private grade: number
  private lessons: Lesson[]
  private redoLessons: Lesson[]
  private greetingType: string
  constructor (grade: number, lessons: Lesson[]) {
    this.grade = grade
    this.lessons = lessons.filter(lesson => lesson.grade <= grade)
  }

  setRedoLessons (lessons: Lesson[]) {
    this.redoLessons = lessons
  }
}
```

However, at this point you couldn't tell whether there is a property that isn't defined either in the constructor function or in some method. I don't know if you noticed but in the previous code I sneaked in a property that meets such criteria.

```typescript
// Typescript non-strict mode
interface Lesson {
  title: string
  grade: number
}

class Student {
  private grade: number
  private lessons: Lesson[]
  private redoLessons: Lesson[]
  private greetingType: string // 👀 This is undefined, not used and there's no error!
  constructor (grade: number, lessons: Lesson[]) {
    this.grade = grade
    this.lessons = lessons.filter(lesson => lesson.grade <= grade)
  }

  setRedoLessons (lessons: Lesson[]) {
    this.redoLessons = lessons
  }
}
```

In strict mode, it actually throws errors on all undefined properties not defined in the constructor.

```typescript
// Typescript strict mode
interface Lesson {
  title: string
  grade: number
}

class Student {
  private grade: number
  private lessons: Lesson[]
  private redoLessons: Lesson[]
  //   ❌ ^^^^^^^^^^^
  //      Property 'redoLessons' has no initializer and is not definitely assigned in the constructor. ts(2564)
  private greetingType: string
  //   ❌ ^^^^^^^^^^^^
  //      Property 'greetingType' has no initializer and is not definitely assigned in the constructor. ts(2564)
  constructor (grade: number, lessons: Lesson[]) {
    this.grade = grade
    this.lessons = lessons.filter(lesson => lesson.grade <= grade)
  }

  setRedoLessons (lessons: Lesson[]) {
    this.redoLessons = lessons
  }
}
```

This helps you review the code and see whether the properties are indeed being used in places other than the constructor. If it is, you can put an `!` on it and simply remove those who aren't.

```typescript
// Typescript strict mode
interface Lesson {
  title: string
  grade: number
}

class Student {
  private grade: number
  private lessons: Lesson[]
  private redoLessons!: Lesson[]
  constructor (grade: number, lessons: Lesson[]) {
    this.grade = grade
    this.lessons = lessons.filter(lesson => lesson.grade <= grade)
  }

  setRedoLessons (lessons: Lesson[]) {
    this.redoLessons = lessons
  }
}
```

However, I would recommend to either set it to a default value if it's not defined in the constructor as a good practice, otherwise it would be forever `undefined` until it is set (unless that is intentional).

## 5. `strictBindCallApply`
This rule validates the usage of `bind`, `call` or `apply` as defined in the function. Consider this example:

```typescript
// Typescript without strict mode
function sum (num1: number, num2: number) {
  return num1 + num2
}

sum.apply(null, [1, 2])
// 3
```

Maybe a mistake is made, thinking that the `sum` can take in more than two arguments. When the code is run, there is no error thrown on Typescript _and_ in your environment (browser, perhaps). 


```typescript
// Typescript non-strict mode
function sum (num1: number, num2: number) {
  return num1 + num2
}

sum.apply(null, [1, 2, 3])
// Still 3...?
```

The only way to know if it's a mistake is when the code is tested manually or in a unit test. In strict mode, you can spot this even before that:


```typescript
// Typescript strict mode
function sum (num1: number, num2: number) {
  return num1 + num2
}

sum.apply(null, [1, 2, 3])
//           ❌ ^^^^^^^^^
//              Argument of type '[number, number, number]' is not assignable to parameter of type '[number, number]'.
//                Types of property 'length' are incompatible.
//                  Type '3' is not assignable to type '2'. ts(2345)
```

Then it might be a good time to rethink of the `sum` function design.

```typescript
// Typescript strict mode
function sum (...args: number[]) {
  return args.reduce<number>((total, num) => total + num, 0)
}

sum.apply(null, [1, 2, 3])
// 6
```

## 6. `strictFunctionTypes`
Unfortunately, I have yet to find the use case of this rules in my code so far, so I can't comment much on it. You can always check out the release notes for [`strictFunctionTypes`](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-6.html#strict-function-types) on the docs. If anyone has a use case to share, let me know!

---

If you want to take Typescript restrictions to a different level, I recommend using [`tslint`](https://palantir.github.io/tslint/), though I would say some of the rules are based on preferences, but there are a lot of useful ones. Or, avoid bikeshedding by choosing a standard such as [`gslint`](https://github.com/google/gts) or [`tslint-config-standard`](https://github.com/blakeembrey/tslint-config-standard).

I hope you find this article useful! Thanks for reading.

---
Cover image by by [Mark Duffel](https://unsplash.com/@2mduffel?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on [Unsplash](https://unsplash.com/search/photos/rules?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText).