The TypeScript compiler is throwing TS2367: This condition will always return true because it’s detected a logical impossibility in your code, specifically within a conditional check. This isn’t a runtime error, but a compile-time warning indicating that a branch of your code will never be reached, suggesting a potential logic flaw or dead code.
Here are the common causes and how to fix them:
1. Type Narrowing After Assignment
You’ve narrowed a type within a block, and then later in the same scope, you check the original condition, which is now guaranteed to be true due to the earlier narrowing.
Diagnosis: Look for code blocks where a variable’s type is refined (e.g., inside an if block checking typeof or instanceof), and then a subsequent check in the same or an outer scope uses the original un-narrowed type in a condition that’s now redundant.
Example:
function processValue(value: string | number) {
if (typeof value === 'string') {
console.log("It's a string:", value.toUpperCase());
// 'value' is now known to be a string here.
}
// This condition is always true if the above 'if' was entered.
// If the above 'if' was NOT entered, 'value' must be a number,
// making this condition false. The compiler sees the potential
// for the 'if' block to execute, and thus sees this as redundant.
if (typeof value === 'string') {
console.log("This will always run if the first block did.");
}
}
Fix: Restructure your logic. If you need to perform an action based on a type, do it within the narrowed scope. If you need a separate check, ensure it’s distinct from the narrowing condition or that the variable is re-declared or re-assigned.
function processValue(value: string | number) {
if (typeof value === 'string') {
console.log("It's a string:", value.toUpperCase());
} else { // Handle the number case separately
console.log("It's a number:", value.toFixed(2));
}
}
This works because the else block explicitly handles the case where value is not a string, thus avoiding the redundant check.
2. Overly Broad Type Assertions (as any or as Type)
You might be asserting a type that is a supertype of the actual runtime type, leading the compiler to believe a condition related to the supertype will always be met.
Diagnosis: Search for as any or as SomeType followed by conditional checks that appear trivially true given the asserted type.
Example:
function checkData(data: { id: number; name: string }) {
const apiResponse: any = { id: 123, name: "Example" };
// Asserting 'apiResponse' as 'any' allows us to assign it,
// but the compiler loses track of its specific structure.
// If we then check for a property that's guaranteed to exist
// on the *actual* object, but not necessarily on 'any',
// the compiler might flag it depending on context.
// More commonly, this happens when asserting a subtype as a supertype.
// A more direct example:
let unknownValue: unknown = 5;
let numberValue = unknownValue as number; // Explicitly treat it as a number
if (typeof numberValue === 'number') { // This condition is always true because of the assertion
console.log("It's a number.");
}
}
Fix: Avoid unnecessary type assertions, especially as any. Use more specific types or type guards. If you’re asserting unknown to number, the typeof check is indeed redundant.
function checkData(data: { id: number; name: string }) {
const apiResponse: { id: number; name: string } = { id: 123, name: "Example" }; // Use the specific type
// If you truly need to assert from unknown:
let unknownValue: unknown = 5;
if (typeof unknownValue === 'number') { // Use a type guard *before* asserting or casting
let numberValue = unknownValue as number; // Now the assertion is safe and the typeof check is meaningful *before* it
console.log("It's a number:", numberValue);
}
}
The fix is to either use the correct, specific type from the start or use type guards (typeof, instanceof, custom type predicates) to safely narrow down unknown types before asserting, making the assertion itself potentially redundant but the preceding guard meaningful.
3. Literal Types and Union Types Mismatch
You might be checking a variable against a literal type, but the variable’s type is a union that includes that literal type, making the check always true if the variable has that specific literal type.
Diagnosis: Look for if (variable === 'literal') where variable has a type like 'literal' | 'anotherLiteral' | TypeB.
Example:
type Status = 'pending' | 'completed' | 'failed';
function handleStatus(currentStatus: Status) {
// This condition will always return true if currentStatus is 'completed'.
// The compiler is being helpful here, indicating that if the code
// proceeds, and currentStatus *is* 'completed', this condition is met.
// The problem arises if you expect this to be a *discriminating* check
// and there are other branches.
if (currentStatus === 'completed') {
console.log("Task is done!");
}
// A more direct TS2367 example:
// If 'currentStatus' is already known to be 'completed' from a previous
// narrowing, this check would be redundant.
// However, the compiler often flags it when it can deduce that
// *at least one* value in the union will satisfy the condition.
// A common scenario is checking a property of a discriminated union.
interface SuccessResponse { type: 'success'; data: string; }
interface ErrorResponse { type: 'error'; message: string; }
type ApiResponse = SuccessResponse | ErrorResponse;
function processResponse(response: ApiResponse) {
if (response.type === 'success') {
// 'response' is narrowed to SuccessResponse here.
console.log("Success:", response.data);
}
// This next check is what can trigger TS2367 if the compiler
// cannot guarantee 'response.type' is NOT 'success' in this scope.
// If you have `if (response.type === 'success') { ... } else if (response.type === 'error') { ... }`,
// the compiler might infer that the `else if` is always true if the first `if` fails.
// The most common TS2367 trigger here is when a union type isn't fully exhausted.
// Consider this scenario:
let outcome: 'win' | 'lose' | 'draw' = 'win';
if (outcome === 'win') {
console.log("We won!");
}
// The compiler sees that 'outcome' *could* be 'win'. If it IS 'win',
// the condition below is true. This is flagged as always true
// because the compiler can't prove it's *not* always true in *some* execution path.
if (outcome === 'win') { // TS2367
console.log("This path is always possible if outcome is 'win'");
}
}
}
Fix: If you have a discriminated union and are exhausting all cases, ensure your if/else if/else structure correctly handles each literal type. If you’re checking a single literal type within a union, and you expect it to be true, then the compiler is just confirming your logic. The "fix" is often to remove the redundant check or restructure your if/else to be exhaustive.
type Status = 'pending' | 'completed' | 'failed';
function handleStatus(currentStatus: Status) {
if (currentStatus === 'completed') {
console.log("Task is done!");
} else if (currentStatus === 'pending') {
console.log("Task is still pending.");
} else { // currentStatus === 'failed'
console.log("Task failed.");
}
}
This works by explicitly handling each possible value of the Status union. The compiler can then see that no single condition is always true because other branches exist.
4. Boolean Literal Types
You might be checking a variable against true or false when its type is a boolean literal type, or a union that includes true or false.
Diagnosis: Look for if (myBoolean === true) or if (myBoolean === false) where myBoolean has a type like true, false, true | false, or a union that guarantees one of these.
Example:
function checkFlag(flag: true) {
if (flag === true) { // TS2367: This condition will always return true
console.log("The flag is definitely true.");
}
}
function checkOptionalFlag(flag: true | undefined) {
if (flag === true) { // TS2367 if flag is known to be true
console.log("Flag is true.");
}
// If flag could be undefined, the above is not TS2367.
// The error occurs when the compiler can prove `flag` *must* be `true`.
}
Fix: If the type guarantees the boolean value, the check is redundant. Remove it.
function checkFlag(flag: true) {
// No check needed, 'flag' is guaranteed to be true by its type.
console.log("The flag is definitely true.");
}
This works because the type signature flag: true directly enforces that the flag parameter can only ever be the boolean value true.
5. Complex Logical Expressions
Sometimes, deeply nested or complex logical expressions can fool the compiler into thinking a condition is always true, even if there are subtle cases where it might not be. This is less common but can happen.
Diagnosis: Examine the logical operators (&&, ||, !) in the condition. If the condition involves multiple variables and operators, try to simplify it or break it down.
Example:
function complexCondition(a: boolean, b: boolean) {
// Assume some prior logic has determined that if 'a' is true, 'b' must also be true.
// This is hard to represent in a simple snippet without more context.
// But imagine a scenario where 'a' implies 'b'.
// If we then check:
if (a && b) { // If 'a' implies 'b', and 'a' is true, then 'b' is true, so this is always true.
console.log("Both are true (or 'a' implies 'b').");
}
// A more direct TS2367 trigger:
let x: boolean = true;
let y: boolean = true;
if (x || y) { // If x and y are *both* true, this is always true.
console.log("At least one is true");
}
// This specific case `if (x || y)` where x and y are *statically known* to be true
// will be flagged. If they are variables whose values are not known at compile time,
// it's not flagged.
}
Fix: Simplify the logic. If the condition is indeed always true due to the nature of the variables or preceding logic, remove the redundant check. If it’s a complex interaction, break it down into smaller, more manageable conditional statements.
function complexCondition(a: boolean, b: boolean) {
// If we know 'a' implies 'b', and we only care about the case where both are true:
if (a) { // If 'a' is true, we know 'b' is also true.
console.log("Both are true (because 'a' implies 'b').");
}
}
This works by removing the explicitly redundant part of the condition, relying on the established implication.
6. Incorrect Use of Object.keys or Object.values with Empty Objects
If you check the length of Object.keys() or Object.values() on an object that is statically known to be empty, the condition Object.keys(emptyObj).length > 0 will always be false, and Object.keys(emptyObj).length === 0 will always be true. The reverse error (condition always returns false) would occur. However, if the object could be empty or non-empty based on control flow, the compiler might flag an "always true" condition incorrectly if it misinterprets the flow.
Diagnosis: Look for checks on the length of keys/values derived from an object that has been initialized as empty and never modified in a way the compiler can detect.
Example:
interface MyData {
[key: string]: any;
}
function processData(data: MyData) {
const keys = Object.keys(data);
// If 'data' was initialized as {} and never mutated in a way the compiler sees,
// 'keys.length' would be 0, making `keys.length > 0` always false.
// The error TS2367 would be "condition always returns true" if the check was `keys.length === 0`.
if (keys.length === 0) { // TS2367 if 'data' is known to be empty
console.log("No data provided.");
}
}
Fix: Ensure your object initialization and mutation are clear to the compiler. If the object is intended to potentially be empty, the check keys.length === 0 is valid and not redundant. If the compiler flags it as always true, it means it has determined the object cannot have keys in that execution path, making the check unnecessary. Remove the check if the object is guaranteed empty.
interface MyData {
[key: string]: any;
}
function processData(data: MyData) {
const keys = Object.keys(data);
if (keys.length > 0) { // Check if there ARE keys
console.log("Data found.");
// Process data based on keys
} else {
console.log("No data provided.");
}
}
This structure correctly handles both cases without redundancy. The compiler flags keys.length === 0 as always true if it can prove data is empty, meaning the else branch would always be taken.
The next error you’ll likely encounter after fixing these is TS2367: This condition will always return false, which is the inverse problem – the compiler has deduced that a condition can never be met.