TLDR
Strict mode in TypeScript turns on 8 safety checks at once with one line: "strict": true in your tsconfig.json. It catches null errors, missing types, uninitialized class properties, and more before your code ever runs. Always enable it on new projects. On older projects, turn on the 8 flags one at a time and fix errors as you go.
What is Strict Mode in TypeScript?
By default, TypeScript is pretty relaxed. It lets you skip types in some places. It does not check for null on every value. It allows some patterns that can crash at runtime.
Strict mode fixes all of that. It is a single switch that turns on a group of type-checking rules all at once.
Think of it like turning on every warning light in a car instead of just the engine light. You see more problems, but that means you fix them before they cause a crash.
How to Enable Strict Mode
Enabling strict mode takes one line in your tsconfig.json.
{
"compilerOptions": {
"strict": true
}
}
That is it. This one setting turns on 8 different checks at the same time.
You can also turn it on temporarily from the command line:
tsc --strict
What Does strict: true Actually Turn On?
When you set "strict": true, TypeScript enables these 8 flags automatically:
| Flag | What It Checks |
|---|---|
strictNullChecks |
Stops you from using null or undefined where a real value is expected |
noImplicitAny |
Forces you to add types when TypeScript cannot figure them out |
strictFunctionTypes |
Checks that function parameters match the expected types correctly |
strictBindCallApply |
Makes .bind(), .call(), and .apply() type-safe |
strictPropertyInitialization |
Makes sure class properties are set in the constructor |
noImplicitThis |
Stops you from using this when its type is unclear |
useUnknownInCatchVariables |
Makes the error variable in catch blocks unknown instead of any
|
alwaysStrict |
Adds "use strict" to every compiled JavaScript file |
You do not need to set any of these one by one. "strict": true handles all of them.
Note: TypeScript may add new flags to the strict group in future versions. When you upgrade TypeScript, those new flags turn on automatically if you have
"strict": true. This is a good thing. It means your code gets safer with every upgrade.
Why Each Flag Matters (With Code Examples)
1. strictNullChecks — Stop Null Crashes
This is the most important flag. Without it, TypeScript lets you use null and undefined anywhere. This causes runtime crashes.
Without strict:
function getLength(text: string): number {
return text.length; // No error here
}
getLength(null); // Compiles fine. Crashes at runtime.
With strict:
function getLength(text: string): number {
return text.length;
}
getLength(null);
// Error: Argument of type 'null' is not assignable
// to parameter of type 'string'.
Now TypeScript forces you to handle the null case before using the value:
function getLength(text: string | null): number {
if (text === null) {
return 0;
}
return text.length; // TypeScript knows text is a string here
}
2. noImplicitAny — No Hidden any Types
Without this flag, TypeScript quietly gives some values the type any when it cannot figure out the type. any turns off type checking for that value. It defeats the whole point of using TypeScript.
Without strict:
function processItems(items) { // items is silently 'any'
items.forEach(item => {
console.log(item.name); // No type checking at all
});
}
With strict:
function processItems(items) {
// Error: Parameter 'items' implicitly has an 'any' type.
}
Now you must add a type:
function processItems(items: string[]): void {
items.forEach(item => {
console.log(item); // TypeScript checks this correctly
});
}
3. strictPropertyInitialization — Class Properties Must Be Set
Without this flag, you can declare a class property and never set it. Then it is undefined at runtime and causes bugs.
Without strict:
class User {
name: string; // Never set in constructor
greet() {
console.log(this.name.toUpperCase()); // Crashes at runtime
}
}
With strict:
class User {
name: string;
// Error: Property 'name' has no initializer and is not
// definitely assigned in the constructor.
}
Now you must either set it in the constructor or mark it as optional:
// Option 1: Set it in the constructor
class User {
name: string;
constructor(name: string) {
this.name = name;
}
}
// Option 2: Mark it optional with a default
class User {
name: string = "Guest";
}
// Option 3: Mark it as possibly undefined
class User {
name?: string;
}
4. useUnknownInCatchVariables — Safe Error Handling
In older TypeScript, the error variable inside a catch block had the type any. That meant you could call any method on it without checking. This could crash.
Without strict:
try {
doSomething();
} catch (error) {
console.log(error.message); // error is 'any', no check done
}
With strict:
try {
doSomething();
} catch (error) {
console.log(error.message);
// Error: 'error' is of type 'unknown'.
}
Now you must check what kind of error it is before using it:
try {
doSomething();
} catch (error) {
if (error instanceof Error) {
console.log(error.message); // Safe to use
} else {
console.log("Unknown error occurred");
}
}
5. strictBindCallApply — Type-Safe Function Methods
This flag makes .call(), .bind(), and .apply() check that you pass the right argument types.
Without strict:
function greet(name: string): string {
return `Hello, ${name}`;
}
greet.call(undefined, 42); // Wrong type, but no error
With strict:
function greet(name: string): string {
return `Hello, ${name}`;
}
greet.call(undefined, 42);
// Error: Argument of type 'number' is not assignable
// to parameter of type 'string'.
6. noImplicitThis — Clear this Typing
Without this flag, using this inside a regular function is allowed even when TypeScript does not know what this points to.
Without strict:
function printName() {
console.log(this.name); // 'this' is implicitly 'any'
}
With strict:
function printName() {
console.log(this.name);
// Error: 'this' implicitly has type 'any' because it does
// not have a type annotation.
}
Fix it by adding an explicit this type:
function printName(this: { name: string }) {
console.log(this.name); // TypeScript knows the shape of 'this'
}
Two Extra Flags Worth Adding
These two flags are NOT included in strict: true. But they are worth turning on for even safer code.
noUncheckedIndexedAccess
When you access an array item by index, TypeScript normally assumes the item exists. This flag makes it return T | undefined instead.
const names: string[] = ["Alice", "Bob"];
// Without noUncheckedIndexedAccess
const first: string = names[0]; // TypeScript thinks this is always a string
// With noUncheckedIndexedAccess
const first: string | undefined = names[0]; // Safer, might be undefined
if (first !== undefined) {
console.log(first.toUpperCase()); // Now safe to use
}
noImplicitOverride
This flag makes you add the override keyword when you override a method in a subclass. It prevents silent bugs when the parent class changes.
class Animal {
makeSound() {
console.log("...");
}
}
// Without noImplicitOverride: no error even if parent renames the method
class Dog extends Animal {
makeSound() { // Silently breaks if parent renames makeSound()
console.log("Woof");
}
}
// With noImplicitOverride: you must write override
class Dog extends Animal {
override makeSound() { // Clear intent, compiler verifies parent has this
console.log("Woof");
}
}
Add them to your tsconfig.json alongside strict:
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true
}
}
The Full Recommended Strict Config
Here is the complete set of strict options we recommend for all new TypeScript projects:
{
"compilerOptions": {
"target": "ES2022",
"module": "CommonJS",
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true
}
}
Here is what the extra flags do:
| Flag | What It Does |
|---|---|
noUnusedLocals |
Errors on variables that are declared but never used |
noUnusedParameters |
Errors on function parameters that are never used |
noImplicitReturns |
Errors when a function does not return a value on all paths |
What the Same Code Looks Like Without and With Strict Mode
Here is a real example. This function has two silent bugs without strict mode.
Without strict mode (no errors shown):
function getUserCity(user) {
return user.address.city;
}
const city = getUserCity(null);
console.log(city.toUpperCase());
This code compiles with no errors. But it will crash at runtime because:
-
usercould benull -
user.addresscould beundefined -
citycould beundefined
With strict mode (all bugs caught at compile time):
// Error on line 1: Parameter 'user' implicitly has an 'any' type.
function getUserCity(user) {
return user.address.city;
}
TypeScript stops you right away. You now write a safe version:
type Address = {
city: string;
};
type User = {
address: Address | null;
};
function getUserCity(user: User | null): string {
if (user === null || user.address === null) {
return "Unknown";
}
return user.address.city;
}
const city = getUserCity(null);
console.log(city.toUpperCase()); // Always safe
How to Enable Strict Mode on an Existing Project
If you are adding TypeScript to an older project, turning on strict: true all at once can show hundreds of errors. That is overwhelming.
Do it step by step instead.
Step 1: Keep strict off. Turn on flags one at a time.
{
"compilerOptions": {
"strict": false,
"noImplicitAny": true
}
}
Step 2: Fix all the errors from that flag. Commit your changes.
Step 3: Add the next flag.
{
"compilerOptions": {
"strict": false,
"noImplicitAny": true,
"strictNullChecks": true
}
}
Step 4: Keep going until all 8 flags are on. Then replace them all with "strict": true.
Here is a good order to follow when migrating:
| Step | Flag to Enable | Why This Order |
|---|---|---|
| 1 | noImplicitAny |
Easiest to fix, forces you to add types |
| 2 | strictNullChecks |
Most impactful, stops null crashes |
| 3 | strictPropertyInitialization |
Fixes class property bugs |
| 4 | strictFunctionTypes |
Catches function type mismatches |
| 5 | strictBindCallApply |
Usually few errors, easy to fix |
| 6 | noImplicitThis |
Quick fixes with this annotations |
| 7 | useUnknownInCatchVariables |
Makes catch blocks safer |
| 8 | alwaysStrict |
Adds "use strict" to JS output |
Does Strict Mode Change the JavaScript Output?
No. Strict mode is a compile-time feature only.
All the checks happen when you run tsc. The compiled JavaScript output is identical whether strict mode is on or off.
The only exception is alwaysStrict. That flag adds "use strict" at the top of every compiled JavaScript file. But that is a JavaScript directive, not a TypeScript type change.
Common Errors You Will See After Enabling Strict Mode
Here are the errors developers hit most often after turning on strict mode.
"Object is possibly null"
const el = document.getElementById("app");
el.innerHTML = "Hello";
// Error: 'el' is possibly 'null'.
// Fix:
const el = document.getElementById("app");
if (el !== null) {
el.innerHTML = "Hello";
}
"Parameter implicitly has an 'any' type"
function double(n) { // Error
return n * 2;
}
// Fix:
function double(n: number): number {
return n * 2;
}
"Property has no initializer"
class Cart {
items: string[]; // Error
}
// Fix:
class Cart {
items: string[] = [];
}
"Not all code paths return a value"
function grade(score: number) {
if (score >= 90) {
return "A";
}
// Error: Function lacks ending return statement
}
// Fix:
function grade(score: number): string {
if (score >= 90) {
return "A";
}
return "B";
}
FAQ
Q: Does strict mode make TypeScript harder to use?
At first, yes. You will see more errors. But each error points to a real bug. Over time, your code becomes more reliable and easier to maintain.
Q: Can I turn off just one strict flag?
Yes. You can override any individual flag even with strict: true. For example, if you want everything except alwaysStrict, set it to false after enabling strict:
{
"compilerOptions": {
"strict": true,
"alwaysStrict": false
}
}
Q: Should I use strict mode on a team project?
Yes. Always. Agree on it at the start of the project so the whole team works with the same rules.
Q: Is strict: true the default?
No. TypeScript defaults to strict: false. You have to opt in. This is why you should always set it manually.
Q: Does strict mode slow down compilation?
A tiny bit. More checks take more time. But the difference is not noticeable on most projects.
Q: What is the difference between strict and strictNullChecks?
strictNullChecks is one of the 8 checks inside strict: true. Setting strict: true includes strictNullChecks plus 7 other checks. Always use strict: true instead of setting individual flags one by one on new projects.
What You Learned
-
"strict": trueintsconfig.jsonturns on 8 safety checks at once -
strictNullChecksstops null crashes before they happen at runtime -
noImplicitAnyforces you to add proper types everywhere -
strictPropertyInitializationcatches unset class properties -
useUnknownInCatchVariablesmakes error handling safer - Add
noUncheckedIndexedAccessandnoImplicitOverridefor even more safety - On new projects, always start with
strict: true - On existing projects, enable each flag one at a time and fix errors before adding the next
Sources: TypeScript TSConfig Reference | TypeScript Official Docs — Strict Mode

Top comments (0)