Ten Days in 1995: The Language and Its Scars
JavaScript was designed in about ten days and can never break old pages. Almost every strange thing about it follows from those two facts — which turns a pile of trivia into one explanation.
In 1995, Netscape needed a scripting language for its browser, quickly, and it had to look vaguely like Java for marketing reasons. Brendan Eich produced a working prototype in roughly ten days.
Then something unusual happened: the web kept every page ever published. A browser that broke a site from 1998 is a broken browser, so the language could only ever be added to, never corrected. Thirty years of the world's most-used programming language, built on a ten-day prototype that can never be revised.
This is not trivia. It's the single most useful fact for learning JavaScript, because it converts a long list of arbitrary weirdness into one explanation, and it tells you what to do: learn the modern language, and learn the old traps well enough to recognise them.
Every strange corner of JavaScript is a promise the web made and has never broken: old pages must keep working.
Values, and the split that explains everything
JavaScript has two categories of value, and half of all beginner confusion lives on the line between them.
Primitives — strings, numbers, booleans, null, undefined, and the rarer symbol and bigint — are copied by value. Assign one variable to another and you get an independent copy; changing one leaves the other untouched.
Objects — which includes arrays and functions, since both are objects — are handled by reference. A variable doesn't hold the object; it holds the address of it. Assign it to another variable and both names now point at the same thing, so a change through one is visible through the other. Pass an object to a function and the function can modify your object.
This is not a quirk to memorise; it's the mechanism behind an enormous class of real bugs — the array you thought you'd copied, the state that changed somewhere you never touched, the "why did the original change too?" that costs an afternoon. Every later article that says "don't mutate, make a copy" is downstream of this paragraph.
One more thing to know early: null and undefined both mean "nothing", and they mean different nothings. undefined is what the language gives you when a value was never supplied — an uninitialised variable, a missing property, a function that returns nothing. null is what a programmer assigns to say "deliberately empty." When you see undefined at runtime, it usually means something didn't arrive; when you see null, someone meant it.
Dynamic typing and the coercion trap
Types belong to values, not to variables. A variable can hold a string now and a number later, and nothing complains — which is convenient for ten lines and a liability for ten thousand. That liability is exactly what TypeScript exists to remove, and it gets its own article later.
The part that bites everyone is coercion: when an operation gets a type it didn't expect, the language converts rather than complains. Adding a number to a string produces a string. Comparing a string to a number with == converts one of them first. This produces the famous list of absurd-looking results that circulates as evidence that the language is broken — most of which are one rule applied consistently in a situation nobody sane would write.
Two habits neutralise nearly all of it:
Always use ===. The triple equals compares without converting. The double equals converts first, by a table of rules nobody remembers correctly. There is no situation where you need == badly enough to be worth the ambiguity.
Know the falsy values. In a condition, everything is treated as true except exactly seven things: false, 0, -0, "", null, undefined, and NaN. Two consequences bite in real code: an empty array and an empty object are both truthy, and a perfectly valid 0 is falsy — which is why if (count) silently skips zero and quietly breaks your logic.
While you're here: NaN means "not a number" and is itself of type number, it's the only value not equal to itself, and it usually means an arithmetic operation received something that wasn't a number. Seeing it in an interface is always a bug two steps upstream.
Variables: const by default
Three ways to declare a variable exist, and only two should be in your code.
let for a value that will change. const for one that won't. var for never — it ignores block boundaries and behaves in ways that exist only for compatibility.
The most-misunderstood point: const doesn't make a value immutable. It stops the name from being reassigned. A const array can still have items pushed into it; a const object can have its properties changed. What you can't do is point that name at a different array. Which follows directly from the reference rule above — the constant is the address, not the contents.
Default to const, switch to let when you actually need to reassign. It's a small habit that makes code readable: a let becomes a signal that says "this changes, pay attention."
Scope, and why functions are the good part
A block — anything in curly braces — is a scope. Variables declared with let and const exist inside it and not outside.
Functions are the piece of this language that has aged best, and they behave in ways worth stating explicitly: a function can be stored in a variable, passed to another function, and returned from one. That's not an advanced technique, it's the everyday texture of JavaScript, and the next article is built on it.
There's one behaviour worth meeting now because it looks like magic later: a function remembers the scope it was created in, even after that scope has finished executing. That's a closure, and it's how a click handler still knows about a variable from the function that created it three seconds ago.
this, briefly
this in JavaScript is a genuine design scar: its value is determined by how a function is called, not where it was defined, so the same function can see a different this depending on the call site.
Modern advice is short. Arrow functions don't have their own this — they use the surrounding one, which is almost always what you want. In the code you'll write in the next few years, this mostly appears in classes and in older code you're maintaining. Recognise it, don't build on it, and don't feel obliged to master its four binding rules before you can be productive.
Errors are information
An error interrupts execution and travels up until something catches it. try/catch is that something.
The rule that separates painful debugging from bearable debugging: never catch an error and do nothing with it. An empty catch block converts a loud, locatable failure into a silent wrong result — the most expensive kind. If you can't handle it, let it through. If you can, handle it and say something.
And read the messages properly. "Cannot read properties of undefined (reading 'name')" is not noise: it says something you expected to be an object was undefined, and the fix is upstream of where it exploded. Half of learning to debug is learning that the error message was already the answer.
How to actually learn this
JavaScript is the point in this roadmap where reading stops working. HTML and CSS give instant visual feedback; a language gives you nothing until you've built something with it.
Three things from the How to Learn sphere apply with unusual force here.
Retrieval, not recognition. Reading a tutorial where each concept is explained just before it's used produces the smoothest possible feeling and the weakest possible retention — it trains recognising code, not writing it. Close the tab and rewrite the example from scratch. The difference between "I understood that" and "I can produce that" is the entire subject.
Interleave. Twenty array exercises in a row trains applying a method, never choosing one, and real code never tells you which method it needs. Mix problem types deliberately, and make yourself name the tool before reaching for it.
Use the console as a feedback loop. Open it, type an expression, see the answer in one second. That's about as tight a loop as deliberate practice can get — but only if you predict the result before pressing Enter. Predicting first turns each line into a test of your model; skipping the prediction turns it into a slot machine.
In practice
Predict, then run. Before executing anything in the console, say what it will print. Every surprise is a free lesson about a wrong model.
Type it out. No copy-paste while learning. The friction is the point — it forces you to process what each token is for.
Write === for a month without thinking about it, and learn the seven falsy values by heart. Those two facts prevent more bugs per unit of effort than anything else in the language.
When something is undefined, walk backwards. Don't add a guard at the crash site; find where the value should have come from. A guard hides the bug and keeps it.
Explain a closure out loud to someone, or to a wall. It's the concept most people believe they understand until the words come out.
Check yourself
Close the article and answer in your own words:
- Why does JavaScript have so many odd corners, and what does that tell you about how to learn it?
- What's the difference between how primitives and objects are assigned, and what bug class comes from it?
- When do you see
undefinedversusnull? - Why
===always, and which seven values are falsy? - What exactly does
constprevent, and what does it not prevent? - What is a closure, in one sentence?
- Why is an empty
catchblock worse than nocatchat all?
In short
- The language was built in ten days and can never break old pages: that one fact explains most of its strangeness and tells you to learn the modern subset plus the traps.
- Primitives copy by value, objects by reference — the source of "why did the original change too?" and of every later rule about not mutating.
undefinedmeans nothing arrived;nullmeans someone meant it.- Types belong to values, and coercion converts instead of complaining: always
===, and know that0and""are falsy while[]and empty objects are truthy. constfreezes the name, not the contents; default to it and letletsignal that something changes.- Functions are values, and a closure is a function remembering the scope it was born in.
thisdepends on the call site; arrow functions borrow the surrounding one, which is usually what you want.- Never swallow an error, and read the message — it usually names the fix. Learn by predicting, typing and interleaving rather than by reading.