
No other programming language occupies the position JavaScript does. It is the only language every major web browser runs natively — Chrome, Firefox, Safari, Edge — which means every interactive website you have ever used runs JavaScript whether you knew it or not. It also runs on servers via Node.js, powers mobile apps via React Native, desktop applications via Electron, and increasingly, embedded and edge computing environments. Learning it well is not a bet on “the frontend language” — it is a bet on the single most deployed programming runtime on the planet.
This post starts from zero: installing the modern JavaScript toolchain, understanding what actually happens when JavaScript code runs, and writing a real first program — a task tracker — that this entire series will return to and rebuild across every post, exactly the way this blog’s Python series rebuilt a unit converter twenty times over. By the end of this series, that simple console script will be a tested, typed, API-backed, browser-rendered application.
The Mental Model: What JavaScript Actually Is
Three things about JavaScript shape everything else in this series.
JavaScript is dynamically typed, like Python covered elsewhere on this blog — a variable’s type is not fixed at declaration, and the same name can hold a number, then a string, then an object, with nothing in the language stopping you. This makes JavaScript fast to write and a genuine source of bugs if you are not deliberate — Post #2 covers exactly how this plays out.
JavaScript is single-threaded, with an event loop. Unlike languages that run multiple things genuinely simultaneously by default, JavaScript executes one thing at a time on a single main thread — but it handles waiting for slow operations (network requests, timers, file reads) without freezing everything else, through a mechanism called the event loop. This single fact explains nearly everything distinctive about how asynchronous JavaScript works, covered fully in Post #9.
JavaScript is standardized as ECMAScript, and it moves fast. The language specification — ECMAScript, often abbreviated ES — gets a new annual version. ES6 (2015, also called ES2015) was the most significant single update in the language’s history, introducing let/const, arrow functions, classes, and much of what “modern JavaScript” means. Every browser and Node.js version referenced in this series supports ES2026, the current specification as of this writing.
Installing Node.js
Node.js is the JavaScript runtime that lets you run JavaScript outside a browser — on your own machine, on a server, anywhere. Even code destined to run entirely in a browser is typically developed, tested, and built using Node.js tooling first.
macOS
# Using Homebrew (recommended)
brew install node@22
# Verify
node --version
# v22.x.x
Windows
Download the installer directly from nodejs.org — select the LTS (Long-Term Support) version, currently the Node.js 22.x line as of 2026, rather than the “Current” release, which prioritizes newer features over stability.
node --version
# v22.x.x
Linux
# Using NodeSource's setup script (Ubuntu/Debian)
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt-get install -y nodejs
node --version
Why LTS matters: Node.js alternates between “Current” releases (newest features, less stable) and “LTS” releases (Long-Term Support, prioritizing stability for production use). Unless you have a specific reason to need a bleeding-edge feature, LTS is the correct default — exactly the same reasoning this blog’s Python series applied to choosing stable release versions over the newest experimental ones.
The Modern Toolchain: npm and bun
Every Node.js installation ships with npm (Node Package Manager) automatically — the original, universal package manager every JavaScript tutorial, library, and existing codebase assumes you have.
npm --version
npm: The Universal Standard
# Create a new project
mkdir task-tracker && cd task-tracker
npm init -y
# This creates package.json — the JavaScript equivalent of Python's pyproject.toml
# Install a package
npm install lodash
# Run a script defined in package.json
npm run start
package.json is the project’s manifest — dependencies, scripts, metadata — functionally equivalent to pyproject.toml in this blog’s Python series. npm install downloads packages into a node_modules folder, and generates (or updates) package-lock.json, which pins exact dependency versions — directly analogous to uv.lock.
bun: The Modern, Fast Alternative
bun is a newer, considerably faster JavaScript runtime and toolchain — written in Zig, positioned similarly to how uv reshaped Python tooling covered elsewhere on this blog: one fast, coherent tool replacing several slower, separate ones (npm for packages, a separate bundler, a separate test runner).
# Install bun
curl -fsSL https://bun.sh/install | bash
# Verify
bun --version
# Create a project
bun init
# Install packages — dramatically faster than npm for large dependency trees
bun install lodash
# Run a script
bun run start
# Run a file directly, no separate build step
bun index.js
The practical guidance for this series: npm remains the universal baseline every JavaScript developer needs to understand, because it is what the overwhelming majority of existing tutorials, packages, and company codebases use — this series teaches it as the default. bun is worth knowing about and reaching for on new personal projects where its speed genuinely matters; commands in this series will primarily use npm for maximum compatibility with what you will encounter elsewhere, noting the bun equivalent where it differs meaningfully.
Running JavaScript: Three Ways
1. The Browser Console
Open any browser, press F12 (or right-click → Inspect), and select the Console tab:
> 2 + 2
4
> const name = "Alex";
> console.log(`Hello, ${name}`);
Hello, Alex
This is JavaScript running directly inside the browser’s own JavaScript engine — the same engine (V8, in Chrome and Node.js; different engines in other browsers) that executes every website’s interactive behavior. Useful for quick experiments; not how you build anything real.
2. The Node.js REPL
$ node
Welcome to Node.js v22.x.x.
> 2 + 2
4
> const name = "Alex";
> console.log(`Hello, ${name}`);
Hello, Alex
> .exit
Functionally identical in purpose to Python’s REPL covered elsewhere on this blog — immediate, interactive, and not for building anything you intend to keep.
3. Running a Script File
# Create a file
echo 'console.log("Hello from a file");' > hello.js
# Run it
node hello.js
# Hello from a file
This is how real JavaScript programs run outside a browser — write code in a .js file, execute the whole file with node.
Your First Real Program: A Task Tracker
Skip a single console.log. Here is a program that does something you would actually want, using only JavaScript fundamentals available right now:
// taskTracker.js
const tasks = [];
function addTask(description) {
tasks.push({ description, completed: false });
console.log(`Added: "${description}"`);
}
function completeTask(index) {
if (index < 0 || index >= tasks.length) {
console.log("Invalid task number.");
return;
}
tasks[index].completed = true;
console.log(`Completed: "${tasks[index].description}"`);
}
function listTasks() {
console.log("\n=== Your Tasks ===");
if (tasks.length === 0) {
console.log("No tasks yet!");
return;
}
tasks.forEach((task, index) => {
const status = task.completed ? "✓" : " ";
console.log(`[${status}] ${index + 1}. ${task.description}`);
});
}
// Using the task tracker
addTask("Learn JavaScript fundamentals");
addTask("Build a real project");
addTask("Understand async/await");
completeTask(0);
listTasks();
Run it:
node taskTracker.js
Added: "Learn JavaScript fundamentals"
Added: "Build a real project"
Added: "Understand async/await"
Completed: "Learn JavaScript fundamentals"
=== Your Tasks ===
[✓] 1. Learn JavaScript fundamentals
[ ] 2. Build a real project
[ ] 3. Understand async/await
What Just Happened, Line by Line
const tasks = []; — declares a constant named tasks, holding an empty array. const means the variable binding cannot be reassigned to point at a different array — it does not mean the array’s contents are frozen, which is precisely why tasks.push(...) below is allowed to work. Post #2 covers this distinction in depth, since it is one of the most common early points of confusion in JavaScript.
function addTask(description) { ... } — a function declaration, taking one parameter. Function fundamentals get full treatment in Post #3; for now, read it as “a named, reusable block of code,” exactly the same concept this blog’s Python series introduced identically.
tasks.push({ description, completed: false }) — push() adds an item to the end of an array. The object being pushed, { description, completed: false }, uses a shorthand: { description } is equivalent to { description: description } when the property name matches an existing variable name exactly — a genuinely convenient piece of modern JavaScript syntax covered fully in Post #4.
`Added: "${description}"` — a template literal, JavaScript’s equivalent of Python’s f-strings, using backticks instead of quotes and ${} instead of curly braces for embedded expressions. This is the modern, idiomatic way to build strings with embedded values — never use string concatenation with + for this in new code.
tasks.forEach((task, index) => { ... }) — forEach runs the given function once for every item in the array, passing the current item and its index. The (task, index) => { ... } syntax is an arrow function — a modern, compact way to write a function, covered fully in Post #3.
task.completed ? "✓" : " " — the ternary operator: a compact if/else that produces a value rather than running a block of statements. Read as “if task.completed is true, use "✓", otherwise use " ".”
Understanding ECMAScript Versions
JavaScript’s evolution is tracked through ECMAScript version numbers, and understanding roughly where things landed helps make sense of code you encounter that uses older or newer patterns:
| Version | Year | Key additions |
|---|---|---|
| ES5 | 2009 | The baseline “old JavaScript” most legacy tutorials still reference |
| ES6 / ES2015 | 2015 | let/const, arrow functions, classes, template literals, Promises, modules |
| ES2017 | 2017 | async/await |
| ES2020 | 2020 | Optional chaining (?.), nullish coalescing (??) |
| ES2022 | 2022 | Class private fields, top-level await |
| ES2026 | 2026 | Current specification — this series’ target throughout |
Every code example in this series uses ES2020+ syntax as the baseline, since it is supported by every current browser and Node.js version without any special configuration. If you encounter older tutorials using var instead of let/const, or .then() chains instead of async/await, you are looking at pre-2015 or pre-2017 patterns respectively — both still technically valid, both covered in this series specifically so you can recognize and modernize them, not because they are the recommended way to write new code.
Common Mistakes and Gotchas
⚠️ Mistake 1: Forgetting semicolons and relying on Automatic Semicolon Insertion (ASI) JavaScript does not strictly require semicolons at the end of every statement — a feature called Automatic Semicolon Insertion fills them in automatically in most cases. This “usually works” behavior occasionally produces genuinely confusing bugs:
function getValue() {
return
{
value: 42
};
}
console.log(getValue()); // undefined — NOT the object you might expect!
ASI inserts a semicolon immediately after return on its own line, silently turning this into return; followed by unreachable code. This series uses explicit semicolons throughout specifically to avoid this entire category of surprise — a widely followed convention, not a strict requirement of the language.
⚠️ Mistake 2: Confusing the browser console with a real development environment The browser console and Node REPL are for quick experiments only — nothing typed there is saved, and neither reflects how a real, multi-file JavaScript project is actually built and run.
⚠️ Mistake 3: Mixing npm and bun package management in the same project
Each tool creates its own lockfile format (package-lock.json for npm, bun.lockb for bun) and expects to manage node_modules itself. Pick one per project and stay consistent — mixing them produces confusing, inconsistent dependency states.
⚠️ Mistake 4: Not specifying the Node.js version a project expects
Add an "engines" field to package.json (covered further in later posts) so anyone working on the project — including future you — knows which Node version it was built and tested against, avoiding “works on my machine” version mismatches.
Performance Note
JavaScript, as run by V8 (Chrome’s and Node.js’s engine) and similarly optimized engines in other browsers, is Just-In-Time (JIT) compiled — code is compiled to machine code at runtime, with “hot” code paths (functions called repeatedly) getting increasingly optimized the more they run. This is meaningfully different from Python’s more straightforwardly interpreted execution model covered elsewhere on this blog, and it is a large part of why JavaScript, despite being dynamically typed like Python, generally executes computational code significantly faster. Post #15 covers V8’s optimization behavior and genuine performance profiling in depth; for now, know that JavaScript’s performance characteristics are not simply “interpreted language, therefore slow” — the JIT compilation story is considerably more sophisticated than that.
Quick Reference
# Node.js and npm
node --version
npm --version
npm init -y # create package.json
npm install package-name # install a dependency
npm run script-name # run a script defined in package.json
# bun (modern alternative)
bun --version
bun init
bun install package-name
bun run script-name
bun file.js # run directly, no separate step
# Running JavaScript
node script.js # run a file
node # start the REPL
// Template literals — the modern way to build strings
const name = "Alex";
console.log(`Hello, ${name}`);
// Arrow function
const double = (x) => x * 2;
// Ternary operator
const status = isDone ? "complete" : "pending";
Exercises
Exercise 1 — Direct application
Add a removeTask(index) function to the task tracker that removes a task from the array entirely (not just marks it complete). Hint: arrays have a .splice() method — look up its signature.
Exercise 2 — Slight variation
Add a listIncompleteTasks() function that only prints tasks where completed is false, reusing the same formatting style as listTasks().
Exercise 3 — Real-world combination
Write a completely separate script that asks nothing from the user yet (input handling arrives in a later post) but defines an array of at least five numbers, and prints their sum and average using a forEach loop and template literals for the output.
Exercise 4 — Open-ended challenge
Run the task tracker with a deliberately introduced ASI bug — a return statement followed by an object literal on the next line, exactly like this post’s example — and confirm you see the same undefined result. Understanding this failure mode now will save real debugging time later.
FAQ
Q: Should I learn TypeScript instead of JavaScript directly? A: This series teaches JavaScript first, deliberately — TypeScript (covered in Post #16) is a superset of JavaScript, and understanding the underlying language thoroughly makes TypeScript’s added type system dramatically easier to learn than attempting both simultaneously.
Q: Is Node.js required if I only want to write browser JavaScript? A: In practice, yes — even purely browser-targeted JavaScript is virtually always developed using Node.js-based tooling (package managers, bundlers, testing tools) today, covered throughout this series and directly in Post #19.
Q: Why does JavaScript have so many different package managers (npm, yarn, pnpm, bun)? A: Each emerged to solve real limitations of its predecessors at the time — npm’s early performance issues motivated yarn and pnpm; bun’s ground-up rewrite in a faster language motivated its speed advantage. npm remains the universal baseline every tool aims for compatibility with, which is why this series teaches it first.
Q: What’s the actual difference between JavaScript and ECMAScript? A: ECMAScript is the formal language specification; JavaScript is the most common implementation of that specification (there are others, like the JScript used historically in old Internet Explorer). In virtually all everyday conversation, including throughout this series, the terms are used interchangeably.
Summary and Next Steps
You now have Node.js installed, understand npm as the universal package manager (and bun as the modern, fast alternative), know the three ways to run JavaScript, and have a working task tracker using arrays, functions, template literals, and arrow functions — with a clear understanding of what each piece actually does, not just that it works.
Your next step: Complete Exercises 1 and 2 before moving to the next post. The task tracker will return in Post #3, where its functions get refactored using more of JavaScript’s function syntax options, and again in Post #4, where its task objects get proper destructuring and spread operator treatment.
Code tested with Node.js 22 LTS. Last updated: July 2026.



