Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more

JavaScript’s odd-looking results usually follow a small set of rules: operators convert values, truthiness is defined rather than intuitive, and declarations differ in when and where they become available. Knowing those rules makes expressions easier to debug—and helps you choose clearer checks in your own code.

Why do JavaScript values change type or meaning?

JavaScript is dynamically typed: a value has a type, but a variable can hold values of different types over time. Operators may also convert values as they work. The result depends on the operator and the types of its operands, not just on how an expression looks. MDN’s guide to JavaScript data types and data structures explains these conversions and the primitive and object types involved.

1. typeof null returns "object"

null is a primitive value, not an object. Yet typeof null evaluates to "object", a long-standing compatibility oddity. Do not use typeof to test for null; write value === null instead.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

2. NaN is a number that is not equal to itself

NaN is a special Number value, often produced when a numeric operation cannot produce a valid number. Thus typeof NaN is "number", but NaN === NaN and NaN == NaN are both false. Test specifically for it with Number.isNaN(value), not an equality comparison. MDN’s equality guide covers this behavior.

3. + can concatenate or add

The plus operator can join strings or add numbers. If primitive conversion produces a string operand, the operation concatenates; otherwise it performs numeric addition. Evaluation proceeds from left to right:

  • "3" + 4 + 5 becomes "34" + 5, then "345".
  • 3 + 4 + "5" becomes 7 + "5", then "75".

When an expression mixes strings and numbers, make the intended conversion explicit or split the operations into named steps. That avoids relying on a reader to infer which operation + will perform.

4. Loose equality converts values

1 == true is true because loose equality converts the Boolean to the number 1. Strict equality does not make that conversion, so 1 === true is false. Loose equality has several rules—not a blanket rule that every value becomes a number—including special handling for nullish values, strings, BigInts, and objects. For predictable comparisons in ordinary code, use === unless you specifically want loose equality’s conversion behavior. MDN’s comparison guide describes the algorithm and its exceptions.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Why do empty values behave unexpectedly in conditions?

5. Empty arrays and objects are truthy

An empty array ([]) and an empty object ({}) are both truthy. JavaScript’s falsy values are false, 0, -0, 0n, "", NaN, null, and undefined; every other value is truthy. So if (items) checks whether items is truthy, not whether an array contains elements. To check for an empty array, use items.length === 0. MDN’s JavaScript language guide explains truthiness.

6. && and || return operands

These logical operators short-circuit based on truthiness, but they return one of their operand values rather than necessarily returning true or false. For example, 0 || "fallback" returns "fallback", while "ready" && 42 returns 42. This is why an expression using && or || can evaluate to a string, number, object, or undefined.

A common pattern is value && value.property, which avoids reading the property when value is falsy. If the intent is specifically to guard against null or undefined, optional chaining—value?.property—usually states that intent more clearly. The distinction matters when value could validly be another falsy value such as 0 or "".

What do JavaScript declarations and hoisting actually do?

7. var is not block scoped

A var binding belongs to its containing function or, outside a function, the global scope. An if block does not contain it:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
if (true) {
  var status = "ready";
}
console.log(status); // "ready"

By contrast, let and const are block scoped. If you need a binding confined to a block, use one of those declarations.

8. “Hoisting” describes different declaration behavior

Hoisting is a shorthand for how declarations are available within their scope; the engine does not literally move source lines. The details depend on the declaration type:

  • var is initialized to undefined before its assignment runs. Reading it earlier therefore gives undefined, not its later assigned value.
  • let and const bindings cannot be accessed before execution reaches their declaration. That interval is the temporal dead zone; an earlier access throws a ReferenceError.
  • Function declarations are available earlier in their scope, so a function can be called before its declaration appears in the source.

These differences are documented in MDN’s grammar and types reference. Declare bindings before using them to make control flow and initialization easier to follow.

Which JavaScript oddity is specific to browsers?

9. document.all is a legacy compatibility exception

In browsers, document.all is a host-defined object with unusual behavior retained for web compatibility. It acts like undefined for selected operations: typeof document.all returns "undefined", it is falsy, and document.all == null is true. Yet it is not actually undefined or null. This exception is specified in ECMA-262’s IsHTMLDDA annex; it is not a rule for ordinary objects, and code should not use it as a normal null check.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

How can objects make conversions stranger?

10. Objects can supply their own primitive conversion

When an operation needs a primitive value from an object, JavaScript may consult the object’s [Symbol.toPrimitive] method, then valueOf or toString as appropriate. Arrays and objects can therefore take part in concatenation or equality in ways that are not obvious from their literal syntax.

For example, in an expression context, ({} + []) evaluates to "[object Object]": the object converts to that string, the empty array converts to an empty string, and + concatenates them. Parentheses matter here. A brace at the start of a statement can be parsed as a block rather than an object literal, so brace-leading examples can produce a different result depending on context. See MDN’s explanation of object-to-primitive conversion.

A practical way to read surprising expressions

When an expression produces an unexpected value, check it in this order:

  1. Identify the operand types and whether the operator converts them.
  2. For +, follow evaluation from left to right and note when an operand becomes a string.
  3. For conditions and logical operators, ask whether each value is truthy and which operand short-circuiting returns.
  4. For comparisons, distinguish strict equality from the conversions used by loose equality.
  5. For a variable access, check its scope, declaration type, and whether execution has reached its initialization.

These questions turn most “weird” results into consequences of a specific rule—and suggest a clearer check or expression when the original code obscures its intent.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.