Skip to content

On `tsconfig` and why being "strict" is a pain, but necessary

A while back, I wrote about the experience of moving from raw JavaScript to TypeScript.

3 min read
TypeScript
tsconfig
strict
noUncheckedIndexedAccess
Toolchain

A while back, I wrote about the experience of moving from raw JavaScript to TypeScript. Usually, once a migration is done, a team inherits a tsconfig.json that just "works" and a vague mandate from the lead to "make it as serious as possible" or "set it to strict mode."

The issue is that being strict isn't just a single toggle. It’s a collection of checks that, once turned on, can suddenly make your head spin. There are also extra flags that aren't actually part of the strict: true package, but they can be much more painful if they're suddenly activated.

I often feel that turning off strict mode just to "finish the migration quickly" is a huge mistake. Six months later, no one will remember which holes in the code were left there on purpose and which ones were actually bugs.

When we talk about strict: true, certain things hit you immediately. The one that most often gets in the way is usually strictNullChecks. Suddenly, a property you assumed would always be in your API response might actually be null or undefined. Then there's noImplicitAny. The moment you leave a function parameter untyped, TypeScript protests. Personally, I think this is good—it forces our code to be more defined through a precise typing system.

However, there are other flags that have their downsides too. If you don't use them, you lose out, but if you turn them on too early, you might burn out.

Take noUncheckedIndexedAccess, for example. If this is on, every time you access an array by its index, TypeScript forces you to assume the result might be undefined. If you're building a list in React, code that used to be a simple items[i].id suddenly turns entirely red. It’s very accurate, but honestly, it’s incredibly annoying when you're chasing a deadline. I usually wait to turn this on for critical domain logic—like money or permissions—rather than experimental UI parts.

Then there's exactOptionalPropertyTypes. This is great for catching bugs when we accidentally pass undefined through an object spread operator. In the React world, where we do a lot of prop spreading, this flag can get very noisy.

One thing that often confuses me—and maybe you too—is the discrepancy between what the editor says and what the terminal says. Sometimes my editor shows red errors, but when I run it through CI or tsc --noEmit, everything is green.

As it turns out, the problem usually isn't the code itself, but how the toolchain reads our configuration. There are often three different readers in a single project that don't agree with each other. When that conflict happens, I tend to trust the CI results more.

At the end of the day, you don't have to solve everything with the strictest configuration overnight. There are times when you just need to blast ahead coding to move fast, but there are also times when you have to be disciplined so the code doesn't turn into a mess later on.

Catch you on the next one.