Truthiness narrowing

Narrows a value by testing it for truthiness, removing null, undefined and the other falsy types.

Signature
if (value) { /* value: NonNullable<T> minus falsy literals */ }
  • Added in TypeScript 2.0

Parameters

ParameterTypeDescription
$valueTThe tested expression. Positions that coerce to boolean all narrow: if, while, the ternary condition, && and ||, and the operand of !.

Return value

booleanNothing directly. In the true branch the compiler removes null, undefined and any falsy literal types; the false branch keeps only the types a falsy value could have had.

Errors

Nothing. Truthiness testing never throws, which is why it silently hides bugs rather than reporting them.

Changelog

  • PHP 2.0Control flow based type analysis and non-null / non-undefined type guards introduced under --strictNullChecks; a truthiness check removes null and undefined from a union.
  • PHP 4.4A truthiness check stored in a const is tracked, so `const ok = a && b; if (ok) { ... }` narrows both a and b.

Anything that coerces to boolean narrows: if, while, the ternary condition, &&, || and !. In the true branch TypeScript removes every type whose values are all falsy — null, undefined, and literal types like 0, "", false and 0n.

When to reach for it

  • Guarding an object or array reference before dereferencing it, where no legitimate value is falsy.
  • As the leading half of a compound guard: if (strs && typeof strs === "object"), where the truthiness test is what removes null before the typeof check that would otherwise let it through.

What trips people up

Empty string and zero are falsy. A guard written to mean "was this provided?" also rejects "" and 0, which are usually valid inputs. if (!name) return "stranger" throws away a deliberately blank name; count ? ... : "none" reports zero items as missing. Because the compiler agrees the branch is unreachable for those values, you get no warning at all — this is the most common source of quiet bugs in narrowing code.

When you mean "present", write value !== undefined, value != null, or value ?? fallback. Save truthiness for values where falsy really is invalid.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Bailing out early when an object reference may be null before reading its fields
  • Removing null before a typeof "object" check that would otherwise still include it
  • Auditing an existing codebase for `if (count)` guards that mishandle zero

Frequently asked questions

Why is my zero being treated as missing?

Because 0 is falsy, so `if (count)` and `count || fallback` both skip it. Use `count !== undefined`, `count != null`, or the `??` operator, which only substitutes for null and undefined.

Which values are falsy in JavaScript?

false, 0, -0, 0n, "", null, undefined and NaN. Everything else, including empty arrays and empty objects, is truthy.

Does a truthiness check remove null and undefined from the type?

Yes, under strictNullChecks the true branch drops both, plus any all-falsy literal types like 0 or "". That is precisely why it over-narrows for optional numbers and strings.

More examples

Truthiness as the null-remover in a compound guard

Prints [ 'a', 'b' ], [ 'solo' ], [], [ '' ]. Note the last line: the empty string still reaches the string branch, because the truthiness test only guards the object branch. Wrapping the whole body in `if (strs)` instead would have silently dropped it.

Example · typescript
function printAll(strs: string | string[] | null): string[] {
  const out: string[] = [];
  if (strs && typeof strs === "object") {
    // strs: string[] — the leading `strs &&` is what removed null,
    // because typeof null === "object" would have kept it.
    for (const s of strs) out.push(s);
  } else if (typeof strs === "string") {
    out.push(strs);
  }
  return out;
}

console.log(printAll(["a", "b"]));
console.log(printAll("solo"));
console.log(printAll(null));
console.log(printAll(""));

?? and ?. do not use truthiness

Prints "|| 3 ?? 0 label " then "|| 5 ?? 5 label GO" then "|| 3 ?? 3 label (none)". The first line is the whole lesson: `||` treats 0 as absent, `??` does not, and `?.` short-circuits only on null and undefined — an empty string still gets .toUpperCase() called on it.

Example · typescript
type Settings = { retries?: number; label?: string };

function summarise(s: Settings): string {
  const retries = s.retries || 3;   // 0 becomes 3 — wrong
  const safe = s.retries ?? 3;      // only undefined/null becomes 3
  const upper = s.label?.toUpperCase() ?? "(none)";
  return `|| ${retries}  ?? ${safe}  label ${upper}`;
}

console.log(summarise({ retries: 0, label: "" }));
console.log(summarise({ retries: 5, label: "go" }));
console.log(summarise({}));

Discussion

  • Be the first to comment on this lesson.