JavaScript doesn’t wake up trying to hurt you. '5' + 3 becomes '53' (string karaoke), but '5' - 3 becomes 2 (quiet math)—same literals, flipped operator, whole new storyline—and then it looks at you like you’re being dramatic.
It’s not evil—just earnest in the worst way. It’ll coerce and guess until something runs. Sometimes that’s your app doing fine. Sometimes it’s a polite little crime against logic. Either way, you’re gonna feel something.
These are the bits that make you stare at the screen, exhale through your nose, and mutter okay, fine, we’re doing this.
#== — chaos dressed up as helpfulness
Ask == if two values are “the same” and it hears: could we bully these into agreeing?
'' == 0; // true (empty string becomes 0)
'0' == false; // true (classic)
[] == ''; // true (array becomes empty string… somehow)
null == undefined; // true (they made peace)
null === undefined; // false (strict equality wasn’t invited to the treaty)
=== is the friend who says what they mean. == is the one who “resolves” the fight by changing the subject until nobody remembers what we were arguing about.
What I’d do: Default to ===. Loving JS doesn’t mean you owe loose equality endless patience.
#When + stops doing math
The moment + sees a string, the vibes shift. Mixed types quit being arithmetic and become collage:
[] + []; // "" — two arrays stringify empty, concatenate to nothing
[] + {}; // "[object Object]" — haunting, poetic, useless
{} + []; // 0 in some scripts — `{}` might be a block, not an object literal…
// Parsing is fun. Snacks optional. Regrets included.
This is half of why “can you just concatenate this?” balloons into forty minutes on Stack Overflow.
Honestly? JS isn’t sabotaging you—it’s trying to stitch strings together. Tell it when you meant numbers (Number(), templates, explicit conversion). Whispering intentions to the linter helps too.
#typeof null — wrong on purpose, stuck forever
typeof null; // "object"
null isn’t secretly an object. This is old baggage: the string "object" was a mistake that shipped, and fixing it properly would’ve broken assumptions across the actual internet. So we carry it.
Rough rule: value === null when you care. Don’t outsource your sense of emptiness to typeof.
#The NaN thing
“Not-a-Number” sounds like it shouldn’t live in Number Land. Then:
typeof NaN; // "number"
Of course.
And identity is worse—NaN refuses to match itself:
NaN === NaN; // false
So you can’t use === or == to ask “are you NaN?” You want Number.isNaN(value). (Older isNaN() coerces first and can confuse you.)
NaN isn’t really one sentinel value—it’s a bucket for “that didn’t work out”. Two different flops can both be NaN without being “the same flop.” Messy but honest.
TL;DR: The name exaggerates; typeof fibs a little; equality fibs outright. Number.isNaN is the adult in the room.
#parseInt does what it says—not what you wished
parseInt('08'); // 8 on modern engines; older eras had octal drama
parseInt(0.0000008); // 8 — stringify → `"8e-7"`… first digit narrative wins
If you handed parseInt a number expecting it to “just parse,” poke the second example again once you’ve had coffee.
parseInt wants a string. Feeding it a number starts a weird game of telephone.
#[].map(parseInt) — the blind date nobody asked for
['1', '2', '3'].map(parseInt);
// [1, NaN, NaN]
map calls your callback with (element, index, array). parseInt takes (string, radix). So the index becomes your radix—not what anyone meant.
Fix it with n => parseInt(n, 10) or Number. Naming a wrapper function also saves future-you from three-argument accidents.
#this: who’s calling?
In normal functions, this follows how the function got invoked. Arrow functions freeze this from where they were born. Sprinkle those across classes and DOM handlers and you’ll run the whole emotional rainbow.
Usually it’s not that you’re “bad at JS”—it’s the call site. Bind it, wrap it, decide on arrows deliberately. Tiny bit of discipline saves afternoons.
#Floats — 0.1 + 0.2 is not petty, it’s physics
0.1 + 0.2; // 0.30000000000000004
0.1 + 0.2 === 0.3; // false
IEEE 754 doubles can’t nail every decimal in binary—not a JS unique sin; it’s floating point in general.
For money? Integers (cents) or a decimals library. For “are these close?” epsilon (Math.abs(a - b) < ε). For inner peace—accept tiny slop unless you picked the wrong comparison.
#ASI — when the parser fills in your blanks
Engines insert semicolons where they guess a statement ends. Usually harmless. Occasionally your newline sends the wrong vibe.
Classic gotcha:
function broken() {
return
{ answered: true };
}
broken(); // undefined — parsing sees `return;` then `{ answered: true }` as label soup, not your object literal
Fix: same line return { answered: true } or wrap the expression in parentheses if you insist on poetic line breaks.
Your brain likes blank lines between “return” and the value; the grammar often doesn’t. Pick one habit and stick to it—you’ll debug fewer phantom undefineds.
#var, hoisting, and why your seniors sound tired about it
Before let / const, var was how we held state. Hoisting pulls the declaration upward and initializes with undefined, so reading a var above its assignment line usually won’t explode—you just see undefined until the assignment runs:
console.log(hi); // undefined (not a TDZ error like let)
var hi = "hello";
let/const are block-scoped and put you in a temporal dead zone early—mess with them before initialization and you deserve that ReferenceError.
These days const by default, let when you reassign, var basically when payroll says you have to patch something ancient.
Prefer let/const so the order you read matches the order stuff actually runs.
#Sparse arrays — holes dressed as empty seats
You can accidentally build arrays with gaps:
const sparse = [1, , 3]; // hole at index 1
sparse.length; // still 3 — spooky
Stuff like map and forEach skips those holes entirely; other methods behave differently. Silent gaps chew up transforms quietly.
Unless you meant holes… don’t punch them. Array.from, flat, explicit undefined/null—anything beats phantom indices.
#If you forget everything else…
Strict equality (=== / !==) sidesteps ninety percent of accidental coercion dramas—unless == is literally your tool.
Stick with let and const. Blocks beat surprise hoisting.
When types matter, say so (Number(), String(), templates) instead of daring the interpreter to psychoanalyze your intent.
Reach for Number.isNaN, Number.isFinite, Array.isArray when the edge cases show up—they’re clearer than crafty typeof games.
Mind odd return line breaks, float compares, NaN, null, sparse arrays—mundane habits stop weirdness staying theoretical.
Nothing here means you’re “bad” at programming. The language accumulated decades of compatibility; you inherited the personality.
#Why bother loving it?
Because JavaScript genuinely is everywhere—tabs, terminals, tooling, whatever someone emailed you named final_final_v2_REALLY.js.
It’s messy because millions of humans needed last Tuesday’s scripts to stay green. The quirks aren’t malice—they’re souvenirs from shipping in public.
Grab the linter, lean on strict bits, sprinkle types if that calms your brain, and when something absurd happens overnight in prod—sometimes it isn’t stupidity… it’s just a very forgiving runtime doing its social butterfly thing again.
Mind the ==. See you out there.
Written by
Manish Singh
At
Thu Jan 30 2025