JavaScript Ambiguity
A Language That Smiles While Lying to You

Introduction
JavaScript is the only language that will look you in the eye, say "yes, that's valid," and then silently ruin your production build. Every other language would throw an error or refuse to compile - but JavaScript? It coerces types, lies about typeof results, and changes this binding based on invisible rules
The typeof Operator's Big Lies
console.log(typeof [])
// Returns "object"
console.log(typeof {})
// Returns "object"
console.log(typeof Nan)
// Despite being "Not-A-Number"
console.log(typeof null)
// Returns "object"
JavaScript's typeof operator lies blatantly about arrays. An empty array [] is not an object, but typeof [] returns "object" because arrays inherit from Object.prototype. This historical bug from JavaScript's early days trips up developers constantly, same like typeof {}.
Even worse, typeof null returns "object" despite null being a primitive value. This dates back to JavaScript's original implementation where null had an object type tag that was never fixed for backward compatibility.
Array or Object
The real way to check arrays is Array.isArray([]), which returns true.
function isArrayOrObject(value) {
if (value === null || typeof value !== 'object') {
return false;
}
return Array.isArray(value) || Object.prototype.toString.call(value) === '[object Object]';
}
The real way to check arrays is Array.isArray([]), which returns true.
String Conversion Gotchas
JavaScript loves implicit toString() calls during concatenation.
42 + "0" // 420
[] + {} // "[object Object]"
null + "" // null
Number becomes "420" because + coerces the number to string first. But becomes [] + {} becomes "[object Object]" because array toString() joins elements (empty so nothing) and object falls back to default toString.
null + "" is "null", undefined + "" is "undefined".
The Deadly a++a Expression
let a = "1";
console.log(a+ +a);
console.log(('b' + 'a' + +'a'+'a').toLowerCase());
// guess the output
Truthy and Falsy Ambush List
textif([]) console.log("truthy!"); // This runs
!![] === true // true
!!0 === false // true
Double negation !! value coerces anything to proper boolean: !![] is true, !!0 is false.
Only 6 falsy values exist:
false, 0, "", null, undefined, NaN.
Everything else is truthy, including [] (empty array), {} (empty object), "false" (string), and Infinity.
this Keyword: The Shape-Shifter
Regular functions get dynamic this based on the call site. Arrow functions capture lexical this from the surrounding scope.
textfunction Regular() {
setTimeout(function() {
console.log(this); // window/global
}, 0);
}
const Arrow = () => {
setTimeout(() => {
console.log(this); // Arrow's lexical this
}, 0);
};
Regular.call({x:1}); // logs window
Arrow.call({x:1}); // logs {x:1} if called in object method
Function Definitions Compared
function declarations hoist completely (name + body movable to top)
function expressions hoist only declaration (body undefined until assignment)
Arrow functions are always expressions, no hoisting, no own this/arguments
textgreet(); // Works - fully hoisted
function greet() { console.log("Hi"); }
console.log(fn); // undefined
var fn = function() {};
sayHi(); // ReferenceError - TDZ
const sayHi = () => console.log("Hi");
Bonus Production Killers
textNaN !== NaN // true - use Number.isNaN()
+[] === 0 // true
+![] === 1 // true
{} + [] === "[object Object]" // true
[] + {} === "" // true
JSON.stringify({a: undefined}) // "{}" - drops undefined properties
The Escape Plan
JavaScript doesn't break your code with errors - it transforms it into something subtly wrong that passes tests until production. Use TypeScript for safety, ESLint with strict rules, and always test edge cases. Your future self will thank you.





