Introduction
If you’re using TypeScript, setting strict: true is now considered essential. While strict significantly improves type safety, there are additional compiler options that can further strengthen strictness.
In this article, I introduce both the tsconfig options I regularly use in my TypeScript projects beyond strict, as well as those I intentionally do not use.
List of tsconfig Options I Use
{
"compilerOptions": {
// ...Omitted...
"exactOptionalPropertyTypes": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true
}
}
1. Enable exactOptionalPropertyTypes
exactOptionalPropertyTypes is a compiler option that enforces stricter semantics for optional properties. It is disabled by default.
For example, consider the interface below. When this option is disabled, the age property of the user variable is treated as either “the property does not exist” or as having the type number | undefined.
interface User {
name: string;
age?: number; // Type is number
}
const user: User = {
name: "guest",
age: undefined, // undefined can be assigned
};
When this option is enabled, attempting to assign undefined to the age property will result in a type error.
This makes it possible to clearly distinguish between the state where “the property does not exist” and the state where “the property’s value is undefined”. Enabling this option is therefore highly beneficial when you want to handle these two states explicitly and precisely.
2. Enable noImplicitReturns
noImplicitReturns reports missing return statements as errors. It is disabled by default.
This option helps prevent bugs where a return statement is accidentally omitted in one branch, causing a function that is expected to return a value to instead implicitly return undefined.
I enable this option to avoid such implementation mistakes. This option is especially useful in complex branching logic, such as switch statements.
3. Enable noFallthroughCasesInSwitch
noFallthroughCasesInSwitch reports fallthrough in switch statements—cases where a case block does not end with a break—as an error. It is disabled by default.
While some implementations intentionally rely on fallthrough, in most real-world codebases it is more often the result of a simple mistake. I enable this option to help prevent such implementation errors.
4. Enable noUncheckedIndexedAccess
noUncheckedIndexedAccess treats the result of obj[key] or arr[i] as potentially undefined. It is disabled by default.
In JavaScript, accessing a non-existent index in an array or a key in an object returns undefined. However, by default, TypeScript does not reflect this behavior in its type system.
const arr: string[] = ["a", "b"];
const item = arr[10]; // Type is string, but the actual value is undefined
console.log(item.toUpperCase()); // Crashes at runtime

When this option is enabled, the result of indexed access becomes T | undefined (in the example above, the type of item becomes string | undefined). This makes it explicit that a value may not always be present.
As a result, subsequent code is forced to include guard logic, such as existence checks or default value assignments.

I enable this option to reduce the risk of runtime errors. Personally, I find it especially valuable for preventing missed handling of undefined after accessing non-existent array indices or object keys.
5. Enable noImplicitOverride
noImplicitOverride requires the override keyword when a subclass overrides a method from its parent class. It is disabled by default.
Enabling this option helps prevent unintended overrides in subclasses and catches issues such as unnoticed method name or signature changes in the parent class. I enable it to improve overall development safety.
This option is particularly effective in team-based development and in codebases that rely on inheritance, where it helps prevent unintended breaking changes.
6. Enable noPropertyAccessFromIndexSignature
noPropertyAccessFromIndexSignature is a compiler option that disallows dot-notation property access (e.g., obj.foo) for index signatures (e.g., { [key: string]: T }) and requires bracket notation (e.g., obj["foo"]). It is disabled by default.
This option prevents treating properties whose existence is uncertain as if they were guaranteed to exist. When enabled, attempting to access properties that are not explicitly defined using dot notation results in a type error. This encourages a clear distinction: dot notation for statically defined properties, and bracket notation for dynamically determined properties, which improves overall code readability.
When enabled together with noUncheckedIndexedAccess, this option provides a strong level of type safety.
List of tsconfig Options I Do Not Use
1. Options Covered by strict
When strict is enabled, all of the following options are enabled as well. Because they do not need to be configured explicitly, I do not list them in my tsconfig.
alwaysStrictstrictNullChecksstrictBindCallApplystrictBuiltinIteratorReturnstrictFunctionTypesstrictPropertyInitializationnoImplicitAnynoImplicitThisuseUnknownInCatchVariables
2. noUnusedLocals / noUnusedParameters
noUnusedLocals reports unused variables and import statements as errors. noUnusedParameters reports unused function parameters as errors. Both are disabled by default.
Unused variables and import statements should generally be removed.
Although these options can be enabled in tsconfig, I prefer to enforce them via ESLint.
When enabled in tsconfig, even temporarily defining small variables for experimentation during development can cause builds to fail, which interferes with debugging and other exploratory work. For the sake of development efficiency, I manage these rules with ESLint rather than tsconfig.
Comments