Skip to content
TypeScript ts narrowing 3 min read

Truthiness Narrowing

JavaScript coerces values to booleans in if conditions, &&, ||, and ternaries, and TypeScript uses these coercions to narrow types. A truthy check can remove null, undefined, 0, "", NaN, and false from a union — which is powerful but also the source of classic bugs when a valid 0 or empty string is treated as “missing.” This page covers what counts as falsy, how &&/|| narrow, the optional-chaining and nullish-coalescing operators, and how to avoid the zero/empty-string trap.

The falsy values

A truthiness check narrows out every falsy value at once. There are exactly seven falsy values in JavaScript, and TypeScript knows each one.

ValueFalsy?
falseyes
0, -0, 0nyes
"" (empty string)yes
nullyes
undefinedyes
NaNyes
everything elseno (truthy)
function greet(name: string | null | undefined): string {
  if (name) {
    // name: string (and non-empty at runtime)
    return `Hello, ${name}`;
  }
  return "Hello, stranger";
}

Narrowing with && and ||

Logical operators narrow within the expression itself. In a && b, the right side only evaluates when a is truthy, so a is narrowed there. In a || b, the right side runs when a is falsy.

function firstChar(text: string | undefined): string {
  // text is narrowed to string before .charAt runs
  return (text && text.charAt(0)) || "?";
}

This guard-and-default style is concise, but for nullable-only checks the optional chaining and nullish coalescing operators express intent more precisely.

Optional chaining and nullish coalescing

?. short-circuits to undefined when the left side is null or undefined, and ?? supplies a fallback only for null/undefined — not for other falsy values. Together they avoid the truthiness trap.

interface Config {
  timeout?: number;
}

function getTimeout(config: Config): number {
  // ?? keeps a valid 0; || would wrongly replace it
  return config.timeout ?? 5000;
}

If timeout is 0, ?? returns 0, whereas config.timeout || 5000 would incorrectly return 5000 because 0 is falsy.

Reach for ?? instead of || whenever 0, "", or false are legitimate values. || narrows on truthiness, ?? narrows only on nullishness.

The zero and empty-string trap

The most common truthiness bug is using a value’s truthiness as a proxy for “is it present?” when 0 or "" are valid inputs. Check for null/undefined explicitly instead.

function render(count: number | undefined): string {
  // ❌ Bug: a real count of 0 is treated as missing
  if (count) {
    return `${count} items`;
  }
  return "no data";

  // ✅ Fix: check for undefined directly
  // if (count !== undefined) return `${count} items`;
}

The first version reports “no data” for count === 0. Using count !== undefined (or count == null inverted) narrows out only the nullish case while keeping 0.

Truthiness on objects and arrays

Objects and arrays are always truthy — even an empty array [] or empty object {}. A truthiness check therefore only narrows out null/undefined for object-typed values, never emptiness.

function hasItems(list: string[] | null): boolean {
  if (list) {
    // list: string[] — but could still be []
    return list.length > 0;
  }
  return false;
}

To detect an empty array you must inspect .length; truthiness alone will not do it.

Best Practices

  • Use truthiness checks to strip null/undefined from non-numeric, non-string unions.
  • Prefer ?? over || whenever 0, "", or false are valid values.
  • Use ?. to safely access members of possibly-nullish objects.
  • Check !== undefined or == null explicitly when a falsy value is meaningful data.
  • Remember every object and array is truthy; test .length or Object.keys for emptiness.
  • Keep guard-and-default &&/|| chains short and readable, or refactor to explicit checks.
Last updated June 29, 2026
Was this helpful?