The Bug Was in My Mental Model, Not in JavaScript

A postfix increment on a string value quietly breaks a mental model most developers carry around without ever testing it, and the fix is to go read the spec instead of blaming the language.

September 5, 20262 min read

I used to think x++ was just shorthand for x = x + 1. That held up fine for years, until I tried it on a value that wasn't already a number.

JavaScript
let x = "5"; console.log(x++); // what comes back here? console.log(x); // and what does x hold now?

My guess: print "5" untouched, then quietly bump x up to 6 behind the scenes. That's what "after" in postfix should mean.

JavaScript
console.log(x++); // 5 (a number, not "5") console.log(x); // 6

The value that came back was already a number, not the string I expected. Something changed the type before the printing even happened.

What Actually Happens

x++ can't add 1 to a string. So before anything else runs, it converts the string to a number, then increments. The "hand back the value untouched" step was never really untouched.

A three-step flow diagram showing the string "5" converted to the number 5, then incremented to 6, with the returned value being the number 5, not the string ExpandA three-step flow diagram showing the string "5" converted to the number 5, then incremented to 6, with the returned value being the number 5, not the string

Translated into plain code, postfix ++ behaves closer to this:

JavaScript
function postfixIncrement(x) { const oldValue = Number(x); // convert first x = oldValue + 1; // then increment return oldValue; // hand back the pre-increment number }

That first line is the part my x + 1 model had no room for. The conversion happens before the increment, not after it.

Check the Spec, Not the Assumption

It's tempting to call this inconsistent design. The better question is simpler: does this match what the spec says should happen?

It does. Which means the surprise was never a bug in JavaScript. It was a bug in an assumption I'd never actually tested.

Most developers never open that spec. MDN is a fine summary to check first, but it's still just a summary written by people, not the actual rule. When something you're reading disagrees with something you're seeing, the spec is the one that gets the final word.

A bug is really just the gap between what you expected and what the code actually does. An architect doesn't put up a building and hope the roof holds. They work from a model already checked against the rules that govern it, and code deserves the same standard.

That's the habit worth building here: when something surprises you, check it against the rule instead of blaming the language. It's the same instinct behind checking a function's real behavior instead of trusting its name or tracing exactly what a closure keeps alive. A surprise isn't the language misbehaving, it's a sign your model needs an update.

Reference