Skip to main content

JavaScript Quirks

Welcome to the weird part

By this point, you have learned the normal, useful, respectable parts of JavaScript.

Now it is time to meet the raccoons living behind the language.

JavaScript has a few behaviors that surprise almost everyone at first. Some are historical, some come from type coercion, and some are just the natural result of a language growing up in public since the 1990s.

The good news: these quirks are learnable.

The better news: once you know them, you stop being surprised and start being the person who says, "ah, yes, classic JavaScript."

Quirk 1: == tries to be helpful and becomes unhelpful

JavaScript has two equality operators:

  • === for strict equality
  • == for loose equality

Strict equality compares both value and type.

5 === 5;     // true
5 === "5"; // false

Loose equality converts values before comparing them.

5 == "5";        // true
0 == false; // true
"" == false; // true
null == undefined; // true

This is the source of many "why would it do that?" moments.

It helps to think of == as JavaScript saying:

"These values are different, but I can change them until they become the same."

That is not always the kind of confidence you want in a program.

Use strict equality by default, especially in conditionals and validation code.

Quirk 2: truthy and falsy are everywhere

In JavaScript, conditions do not require actual booleans. Many values are treated as truthy or falsy.

if ("wolf") {
console.log("This runs");
}

if (0) {
console.log("This does not run");
}

Falsy values include:

  • false
  • 0
  • ""
  • null
  • undefined
  • NaN

Everything else is truthy.

That means this works:

const username = "Ada";

if (username) {
console.log("User exists");
}

But it also means this can be a bug:

const itemCount = 0;

if (itemCount) {
console.log("Show item count");
}

That block does not run, because 0 is falsy.

If you want to check a specific value, be specific:

if (itemCount === 0) {
console.log("Cart is empty");
}

The full idea is covered in the data types guide and shows up constantly in conditionals.

Quirk 3: numbers are not always great at decimal math

JavaScript uses floating-point numbers for regular numeric values.

That means some decimal calculations look... unsettling.

0.1 + 0.2; // 0.30000000000000004

No, you did not break math.

No, your laptop is not haunted.

This is a normal floating-point limitation, and JavaScript is not the only language with it.

The important lesson is: do not rely on exact decimal equality for values like money.

0.1 + 0.2 === 0.3; // false

Use integer cents or another safer approach when precision matters. This comes up in the data types guide and the numbers guide.

Quirk 4: typeof null is "object"

This is one of JavaScript's most famous historical oddities:

typeof null; // "object"

null is not actually an object in the way beginners usually mean "object."

It is a long-standing language bug that became too famous to fix without breaking older code.

So if you need to check for null, do it directly:

const value = null;

if (value === null) {
console.log("The value is intentionally empty");
}

This is one reason the data types guide treats null and objects as different ideas, even though typeof gets weird here.

Quirk 5: arrays are objects, but not all objects are arrays

This can be confusing at first:

typeof []; // "object"
typeof {}; // "object"

Arrays are a special kind of object, which is why they share some object-like behavior.

But this check is not enough:

const value = [];

if (typeof value === "object") {
console.log("Technically true, but not very useful");
}

If you want to know whether something is an array, use:

Array.isArray(value); // true

This becomes much easier once you have both the arrays and objects mental models in place.

Quirk 6: const does not make objects immutable

This one gets a lot of beginners.

const prevents reassignment of the variable name. It does not freeze the contents of an array or object.

const user = { name: "Ada" };

user.name = "Grace";

console.log(user.name); // "Grace"

This fails:

const user = { name: "Ada" };

// user = { name: "Grace" }; // TypeError

But mutating the existing object still works.

This is the difference between reassignment and mutation from the variables and values guide.

Quirk 7: missing values come in two flavors

JavaScript has both undefined and null.

That can feel like the language looked at "no value" and said, "what if there were two?"

In practice:

  • undefined often means a value was not set
  • null often means a value was intentionally set to "no value"
let name;
console.log(name); // undefined

const selectedUser = null;
console.log(selectedUser); // null

The distinction matters when reading APIs, function results, and object properties. The data types guide covers the mental model in more detail.

Quirk 8: automatic semicolon insertion exists

JavaScript can insert semicolons for you in many cases. This is called automatic semicolon insertion.

That sounds convenient, and often it is.

It is also the sort of convenience that occasionally rearranges your furniture while you sleep.

For example, this code:

return
{
success: true
};

does not return the object.

JavaScript inserts a semicolon after return, so the function returns undefined.

Write it like this instead:

return {
success: true
};

This is one reason the introduction guide recommends explicit semicolons for beginners.

Quirk 9: scope can surprise you if you use var

Modern JavaScript uses let and const for good reasons.

var has function scope, not block scope:

if (true) {
var mystery = "I escaped";
}

console.log(mystery); // "I escaped"

That is legal JavaScript.

It is also a wonderful way to create bugs that feel like ghost stories.

This is why the guides recommend const and let, and why scope and closures spends time on block scope.

A few more classics

The JavaScript internet has been collecting cursed examples for years. A lot of famous ones were popularized by wtfjs, which is basically a museum of "please explain this immediately."

Here are a few classics worth knowing.

NaN is not equal to itself

NaN === NaN; // false

Yes, really.

NaN means "not a number," and JavaScript follows floating-point rules here. If you specifically want to check this case, use Number.isNaN():

Number.isNaN(NaN); // true

This pairs nicely with the earlier reminder that numbers have some special cases.

Object.is() disagrees with === in a few edge cases

Object.is(NaN, NaN); // true
NaN === NaN; // false

Object.is(-0, 0); // false
-0 === 0; // true

Most of the time === is what you want.

But if you ever see Object.is(), this is why it exists: it handles a few value-identity edge cases differently.

The famous baNaNa

"b" + "a" + +"a" + "a"; // "baNaNa"

The trick is +"a", which tries to turn "a" into a number and gets NaN.

So this becomes:

"b" + "a" + NaN + "a"; // "baNaNa"

It is silly, but it is also a good reminder that the + operator does both number addition and string concatenation depending on the values involved. That behavior starts in the variables and values guide and shows up all over JavaScript.

null compared to 0 is weird in different ways

null == 0;  // false
null > 0; // false
null >= 0; // true

This feels illegal, but it is the result of different comparison rules being used for equality vs relational operators.

This is a strong argument for keeping comparisons simple and explicit, especially in conditionals.

Math.min() and Math.max() have dramatic default moods

Math.min(); // Infinity
Math.max(); // -Infinity

These functions are designed to compare lists of numbers.

If there is no list:

  • the minimum starts at Infinity
  • the maximum starts at -Infinity

It looks ridiculous when you first see it, but it actually makes the reduction logic work.

None of this means JavaScript is bad

JavaScript is unusual, not useless.

In fact, part of becoming comfortable with JavaScript is learning which parts are ordinary and which parts come with historical seasoning.

The quirks stop feeling random once you know the rules behind them:

  • Use === instead of ==
  • Be careful with truthy and falsy values
  • Do not expect perfect decimal math
  • Check arrays with Array.isArray()
  • Remember that const prevents reassignment, not mutation
  • Prefer let and const over var

Final advice

When JavaScript does something surprising, do not panic.

Reduce the example. Log the values. Check the types. Ask whether coercion, mutation, or scope is involved.

Usually the language is not being random.

Usually it is being extremely consistent with a rule you did not know existed yet.

Which, admittedly, is a very JavaScript way to be weird.