DEV Community

Cover image for JavaScript: The Language That Gaslights Its Developers
Jean-Luc Martel
Jean-Luc Martel

Posted on

JavaScript: The Language That Gaslights Its Developers

After writing about PHP's greatest hits in the Weird Language Hall of Fame, it felt only fair to turn to JavaScript.

Because if PHP is the language of historical baggage, JavaScript is the language of "this seems impossible, and yet it is fully specified behavior."

JavaScript has a special talent for producing results that look like bugs, feel like bugs, and absolutely are not bugs.

Most of the time, the culprit is type coercion, implicit conversion, or one of the language's many "technically reasonable, emotionally devastating" rules.

So here are a few of my favorite examples.

1. An Empty Array Is Truthy... But Also Equal to false

Let's begin with a classic.

Boolean([])       // true
[] == false       // true
[] === false      // false
Enter fullscreen mode Exit fullscreen mode

This is the kind of thing that makes developers stare at the screen and wonder why they picked computing science instead of accounting like their mom advised.

An empty array is truthy.

So far, so normal:

if ([]) {
  console.log("Yep, truthy");
}
Enter fullscreen mode Exit fullscreen mode

That runs.

But then this also happens:

[] == false   // true
Enter fullscreen mode Exit fullscreen mode

Why?

Because JavaScript doesn't use the same rules for boolean contexts and loose equality.

In a boolean context, arrays are simply objects, and objects are truthy.

But with ==, JavaScript starts converting values. The empty array becomes an empty string, which then becomes the number 0. Meanwhile, false also becomes 0.

So JavaScript ends up comparing:

0 == 0
Enter fullscreen mode Exit fullscreen mode

Which is, unfortunately, true.

And if you really want to ruin your afternoon:

[] == ![]   // true
Enter fullscreen mode Exit fullscreen mode

Because ![] becomes false, and now you're back in the previous example.

The lesson: truthiness and loose equality are not the same thing. If you're comparing values in JavaScript, === should be your default.

2. typeof null Is "object"

This one has survived for decades.

typeof null         // "object"
typeof undefined    // "undefined"
typeof []           // "object"
typeof NaN          // "number"
Enter fullscreen mode Exit fullscreen mode

Let's focus on the weirdest part:

typeof null   // "object"
Enter fullscreen mode Exit fullscreen mode

Except null is not an object.

This is a long-standing historical bug in JavaScript that became untouchable because too much code depended on it.

So now it just lives there, like haunted wallpaper.

And since JavaScript enjoys layering confusion on top of confusion:

  • null is not an object, but typeof null says it is.
  • Arrays are objects, so typeof [] is also "object".
  • NaN means "Not a Number," but typeof NaN is "number".

You can almost hear the language whispering, "If you can't handle me on my worst days, you don't deserve me."

The lesson: typeof is useful, but not always precise. If you need to distinguish arrays, nulls, and objects, use more specific checks like Array.isArray() and explicit value === null.

3. map(parseInt) Produces Nonsense

This is one of the best examples in the entire language because every piece of it looks reasonable.

["10", "10", "10"].map(parseInt)
Enter fullscreen mode Exit fullscreen mode

What would you expect?

[10, 10, 10]
Enter fullscreen mode Exit fullscreen mode

What do you actually get?

[10, NaN, 2]
Enter fullscreen mode Exit fullscreen mode

Naturally.

Here's what's happening.

map() doesn't just pass each array element to the callback. It passes three arguments:

  1. the current value
  2. the current index
  3. the whole array

So this:

["10", "10", "10"].map(parseInt)
Enter fullscreen mode Exit fullscreen mode

effectively becomes:

parseInt("10", 0)  // 10
parseInt("10", 1)  // NaN
parseInt("10", 2)  // 2
Enter fullscreen mode Exit fullscreen mode

And why is that a problem?

Because parseInt() takes a second argument: the radix.

So JavaScript interprets the array index as the number base.

  • "10" in base 0 becomes 10
  • "10" in base 1 is invalid, so NaN
  • "10" in base 2 becomes 2

This is one of those perfect JavaScript moments where two completely legitimate APIs collide and create chaos.

The fix is simple:

["10", "10", "10"].map(Number)
Enter fullscreen mode Exit fullscreen mode

or:

["10", "10", "10"].map(x => parseInt(x, 10))
Enter fullscreen mode Exit fullscreen mode

The lesson: Don't hand a function to map() unless you know exactly what arguments it expects.

4. sort() Sorts Numbers Like Strings

This one has caused real bugs in real applications.

[1, 2, 10, 20, 3].sort()
Enter fullscreen mode Exit fullscreen mode

What you probably expect:

[1, 2, 3, 10, 20]
Enter fullscreen mode Exit fullscreen mode

What JavaScript gives you:

[1, 10, 2, 20, 3]
Enter fullscreen mode Exit fullscreen mode

Because by default, sort() compares elements as strings.

So JavaScript is effectively sorting these:

  • "1"
  • "2"
  • "10"
  • "20"
  • "3"

And in string comparison, "10" comes before "2".

This makes perfect sense if you're sorting words.

It makes much less sense if you're sorting numbers in a programming language and expected numeric order to be the default.

The fix:

[1, 2, 10, 20, 3].sort((a, b) => a - b)
Enter fullscreen mode Exit fullscreen mode

Now you get what you wanted:

[1, 2, 3, 10, 20]
Enter fullscreen mode Exit fullscreen mode

The lesson: JavaScript's default behaviors are often optimized for generality, not for your expectations.

So, Is JavaScript Bad?

Not really.

Like PHP, JavaScript is often mocked for behavior that is actually consistent with its own rules.

The problem is that many of those rules are invisible until you trip over them.

An empty array can be truthy while also loosely equal to false.

typeof null can lie to your face.

A perfectly innocent-looking map(parseInt) can produce results that seem like a prank.

And sort() can rearrange numbers alphabetically, because apparently that was a reasonable default choice at some point in human history.

But that's also what makes JavaScript fascinating.

Its weirdness isn't random. It's the product of a language that was created to validate forms in web pages and became something people use to build microservices and large-scale data streaming pipelines, and evolved while the entire internet was standing on top of it.

Which is kind of impressive.

This is part of a series on programming languages that occasionally make developers question the nature of reality.

PHP already had its turn. Python, C#, and Perl are waiting patiently in the wings.

And Perl, as always, remains less a programming language than a dare.

Top comments (0)