B1 · Part 1 · Beginner
Setup & mental model
The layer everybody skips — and it shows in interviews.
09On this page
Two boxes, not one
Beginners think "JavaScript" is one thing. It's two things stacked, and almost every "weird JS behaviour" question traces back to which box actually did the work.
Say it like this → "V8 has no idea what a timer
is. The browser does. The engine runs the language, the runtime provides
the world around it."
The 5 words that describe JS
- Single-threaded — one worker. It can only do one thing at a time. A slow loop freezes your whole page.
- Synchronous by default — line 1, then line 2, then line 3. Waiting doesn't happen unless you hand work to the runtime.
- Dynamically typed — a variable is a box, not a shape. Any value can go in it, and what's in it can change.
-
Weakly typed — if types don't match, JS converts
instead of complaining.
"5" * 2 → 10. - Interpreted + JIT compiled — it starts running instantly, then quietly re-compiles the parts you run a lot into fast machine code.
"Dynamic" and "weak" are often confused, but they're separate questions.
Dynamic asks when a type is checked — JS decides at runtime, not
ahead of time. Weak asks what happens on a mismatch — JS converts
instead of refusing. Python is dynamic but strongly typed:
1 + "1" is an error there. JS does both at once, which is why
it gets blamed for more than its share.
Watch the single thread work
This is the whole model in one demo: one call stack, a queue for promises, and a separate queue for everything the runtime hands back (timers, clicks, I/O). Step through it — the code on the left is really the code running underneath the panel on the right.
Call stack
Microtask queue — promises
Macrotask queue — timers
Console
The punchline: C prints before B, even though
the timer's delay was 0ms. The microtask queue always drains
completely before the event loop looks at a single macrotask.
That's not a quirk of timers — it's the ordering rule every Promise,
every async/await, and every render sits on top of.
Where your code actually runs
Three places you'll type JavaScript, and they behave differently.
-
The console — a REPL. It prints the result of every expression,
which is why a stray
undefinedshows up afterconsole.log(...)— that'slog's own return value being echoed, not a bug. -
A
<script>tag — by default, HTML parsing stops while the script downloads and runs. That's why a plain<script>sitting in<head>delays everything below it from painting. -
Node —
node app.jsruns a file; barenodeopens a REPL. This is where JS gets a filesystem, a process, and no DOM at all.
⚠ file:// is not a server
Double-clicking
index.html works fine — until you add
type="module", at which point every import silently fails.
Modules need a real origin. Run one locally:
npx serve or a "Live Server" extension.
var / let / const
| var 💀 | let | const ⭐ | |
|---|---|---|---|
| Lives inside | the whole function | the nearest { } | the nearest { } |
| Use before declaring | undefined |
💥 error | 💥 error |
| Reassign | yes | yes | no |
Those two rows are both consequences of one thing: before running a
line, JS scans the scope and registers every name in it.
var names get created and pre-filled with
undefined; let and const names
get created but left empty, and touching one early throws. The scan is
called hoisting, and that empty gap has a dramatic name — the
Temporal Dead Zone.
console.log(a); // what happens?
console.log(b); // what happens?
var a = 1;
let b = 2;
Run it above — the first line quietly prints undefined
because var was pre-filled. The second line never gets the
chance: b is still in the Temporal Dead Zone, so the whole
script throws a real ReferenceError right there.
Dry run: the creation phase, then execution, for that snippet
| Step | Line | a | b | What happens |
|---|---|---|---|---|
| 1 | creation phase | undefined | uninitialised — TDZ | the whole scope is scanned before line 1 runs |
| 2 | console.log(a) | undefined | TDZ | prints undefined — a was pre-filled |
| 3 | console.log(b) | undefined | TDZ | throws ReferenceError: Cannot access 'b' before initialization — script stops here |
| — | var a = 1; | — | — | never runs |
| — | let b = 2; | — | — | never runs |
Both lines fail for the same underlying reason — the names already existed before line 1 — but the creation phase left them in different states, so one fails silently and the other stops the whole script.
That is the whole of it you need today. The next two chapters take it
apart. Execution context is
the two phases behind the scan and what the TDZ actually spans;
var, let and const is block scope,
why a var loop and a let loop print
different numbers, and why const does not freeze an
object.
Naming & comments
Identifiers may contain letters, digits, $ and
_. They can't start with a digit and can't be a reserved
word. They're case-sensitive — Name and name
are two different variables.
const userName = "ana"; // camelCase — variables, functions
class UserAccount {} // PascalCase — classes, constructors
const MAX_RETRIES = 3; // UPPER_SNAKE — true fixed constants
const _internal = {}; // leading _ — "private" by convention only
class A { #secret = 1; } // # — actually private (ES2022)
Name for intent, not type. Booleans read as questions
(isActive, hasPermission), functions start with
a verb (getUserById, handleSubmit).
// single line
/* multi
line — these do NOT nest */
/**
* JSDoc — tooling reads this for autocomplete and type hints.
* @param {string} name
*/
function greet(name) { return "Hi " + name; }
Write comments that explain why, not what — the code already says what.
'use strict'
Added in ES5 to fix old design mistakes without breaking the existing web. It doesn't add powers — it removes the silence. Mistakes that used to fail quietly now throw a real error.
'use strict';
x = 5; // no var, no let — what happens?
Run it — strict mode refuses to guess and throws
ReferenceError: x is not defined. Delete the first line
(or run it in a plain script) and the exact same assignment succeeds
silently, quietly creating a global variable named x. That
silent global is the bug strict mode exists to kill.
You rarely type 'use strict' yourself anymore, because
modules and classes are strict automatically.
Script vs module
A genuine fork in how a file is parsed and run, decided before a single line executes.
<script src="a.js"></script> // classic script
<script type="module" src="a.js"></script> // ES module
| Classic script | ES module | |
|---|---|---|
| Strict mode | opt-in | always on |
| Top-level scope | the global object | module-local |
Top-level this |
window |
undefined |
import / export |
not allowed | the whole point |
| Loading | blocking, unless defer |
deferred by default |
| Evaluated | once per tag | once per URL, then cached and shared |
Script vs Module A plain
<script> is loose, blocks the page, and dumps its
variables on window.<script type="module"> is strict, waits for the page,
keeps its variables to itself, and can import. Use modules.
One more thing worth knowing: an imported name is a live view into the module that exported it, not a copy taken once. If that module later changes the value, every importer sees the new one. That, plus imports being static and resolved before any code runs, is exactly what lets a bundler tree-shake unused exports away.
A harder ordering puzzle
Harder than the demo above: an async function, an await, and a promise chain all competing. Watch where the code after await actually goes.
Call stack
Microtask queue
Macrotask queue
Console
Interview answer, one sentence
"JavaScript is single-threaded and runs inside a host, the browser or Node, that supplies the event loop and APIs such as timers and fetch, so the language does one thing at a time while the host handles the waiting."
Before you move on
- Explain the difference between the engine and the runtime, and name two APIs that exist only because the runtime provides them.
- State the five words that describe JS, and explain why "dynamic" and "weak" are answering two different questions, not the same one.
- Walk through why
Cprints beforeBin the event loop demo, even though the timer's delay was0ms. - Predict, and explain, why
console.log(a)printsundefinedwhileconsole.log(b)throws, for the samevar/letsnippet. - List the concrete differences between a classic script and an ES module — strict mode, top-level scope,
this, and loading order.
Opens in the editor: write it, run it, and check it against real tests.
All 35 for this level →Finished reading?
0 of 41 chapters read in this series