Skip to content

The JavaScript Truthy/Falsy Trap That Broke a "Simple" Empty Check

- - -

I came across some code recently that looked like the most boring, unassailable line of JavaScript imaginable:

javascript
if (!getProperty('/myJob/myProcedureParameter')) {
    return false;
}
return true;

The intent was obvious: "if this parameter is empty, bail out." It reads like plain English. It compiled. It even worked — most of the time. And then, on some runs, it just... didn't. The condition would pass when the parameter clearly had a value, or fail when the parameter was clearly empty, with no pattern anyone could immediately spot.

If you've written enough JavaScript, you already know where this is going. If you haven't, buckle up — this one bites people at every experience level, because the bug isn't in what the code does. It's in what ! actually means.

The setup: strings pretending to be booleans

The parameter in question came from a pipeline orchestration tool's getProperty() call — the kind of API that reads a value out of a config store and hands it back to your script. Like a lot of these APIs, it always returns a string. Not undefined, not null, not a boolean — a string. Even when the property is "empty," you get back '', the empty string.

That distinction is exactly where things go sideways, because JavaScript's ! operator doesn't know anything about your intent. It only knows about truthy and falsy coercion, and those rules were not designed with "is this empty" in mind — they were designed decades ago, and they're stricter (and weirder) than most people remember day-to-day.

Here's the cheat sheet, using exactly the kind of string values you'd get back from a "read a config value" API:

javascript
!''          // true   -> empty string IS falsy
!'0'         // false  -> the string "0" is TRUTHY
!'false'     // false  -> the string "false" is TRUTHY
!' '         // false  -> a single space is TRUTHY
!undefined   // true
!null        // true

Read that middle block again. '0' and 'false' — two values a reasonable human would look at and think "that's basically nothing" or "that's basically off" — are both truthy. JavaScript doesn't care that the string looks like zero or looks like false. It only cares that it's a non-empty string, and non-empty strings are always truthy, full stop.

So if your parameter's value ever legitimately happens to be the string "0" (a count, a flag, a stringified boolean from some upstream system), !param evaluates to false — meaning your "is this empty" check reports "not empty," even though to a human eye that value screams "nothing to see here."

Why it looked so random

This is the part that makes the bug genuinely nasty to track down: it doesn't fail consistently. It fails depending on what the actual runtime value happens to be that day.

  • Parameter is a normal non-empty string like "my-build-42"? !param correctly evaluates to false. Condition works as intended.
  • Parameter is a genuinely empty string ""? !param correctly evaluates to true. Condition works as intended.
  • Parameter happens to be "0" or "false" because some upstream step stringified a falsy value? !param evaluates to false — condition silently treats it as "not empty," when the author's mental model says it should be treated as empty-ish.

Three different flavors of "this could be considered empty" — a true empty string, null/undefined (property doesn't exist at all), and a string that merely represents nothingness — and JavaScript's implicit coercion collapses them into two buckets that don't line up with any of those categories. You get a condition that passes code review, passes the first few manual tests, and then quietly does the wrong thing the first time a real-world value doesn't match the tester's assumptions.

The fix: stop asking ! to make a decision it can't make

The fix here isn't clever. It's the opposite of clever, which is exactly the point:

javascript
var myParam = getProperty('/myJob/myProcedureParameter');
if (myParam == '') {
    return false;
}
return true;

Instead of asking JavaScript's coercion rules to guess what "empty" means, you just... say what you mean. myParam == '' matches exactly one thing: the actual empty string. Not "0". Not "false". Not a stray whitespace character (' ' is not '' — worth calling out explicitly, since it's an easy one to get wrong even with the explicit comparison).

If the property might not exist at all — as opposed to existing but being blank — you'll also want to guard for that separately, since getProperty()-style APIs can return null/undefined for "this path doesn't exist" versus '' for "this path exists and is blank":

javascript
var myParam = getProperty('/myJob/myProcedureParameter');
if (myParam == null || myParam == '') {
    return false;
}
return true;

Quick note on that first check: myParam == null is one of the rare, actually-useful cases for loose equality (==) in modern JS. == null matches both null and undefined in a single comparison, because of how JS's abstract equality algorithm treats those two values as mutually interchangeable (and only interchangeable with each other — null == 0 and undefined == '' are both false). It's a deliberate, idiomatic use of ==, not an accident — if that surprises you, it's worth remembering, because it comes up constantly in real-world null-checking code.

The takeaway

None of this is exotic JavaScript trivia. It's the same handful of truthy/falsy rules everyone technically "knows" — and yet this exact class of bug keeps showing up in real codebases, because !param reads like "check if param is empty," and reading like something is not the same as meaning it.

A short checklist for next time you're tempted to write if (!value) as an emptiness check:

  • Know what type you're actually holding. If a value comes from an API, a config store, or a template substitution, assume it's a string until proven otherwise — and strings have their own truthy/falsy quirks that don't match your intuition about "empty."
  • '0', 'false', and ' ' are all truthy. If any of those are plausible values in your system, implicit coercion will not do what you want.
  • Distinguish "doesn't exist" from "exists but blank." null/undefined and '' are different failure modes with different root causes; conflating them with a single !value check throws away information you might need for debugging later.
  • When a condition behaves inconsistently across runs, log the raw value before you touch it with any operator. console.log(typeof value, JSON.stringify(value)) costs you nothing and turns "why does this randomly fail" into "oh, obviously" in about ten seconds.

Explicit comparisons are a few more characters to type. They're also the difference between a condition that means what you think it means, and one that means what JavaScript's 1995-era coercion rules think it means. Those are not always the same sentence.

About · Privacy