A complete reference · Beginner → Advanced

The JavaScript Atlas

Every concept that takes you from your first console.log to writing async, prototype-savvy, production-grade code — explained, demonstrated, and ordered.

Foundations Intermediate Advanced Ecosystem
1

Foundations

Level 1

The bedrock. These are the ideas every line of JavaScript rests on — how values are stored, how decisions are made, and how code is grouped into reusable pieces. Master these and everything later becomes pattern recognition.

Orientation

What JavaScript Is

JavaScript is a high-level, interpreted programming language that runs in web browsers, on servers (via Node.js), and in countless embedded environments. It was created in 1995 to make web pages interactive and has grown into one of the most widely used languages in the world.

It is dynamically typed (you don't declare the type of a variable), single-threaded (it runs one operation at a time), and multi-paradigm (you can write it in procedural, object-oriented, or functional styles).

Your code is read and executed by an engine — V8 in Chrome and Node, SpiderMonkey in Firefox, JavaScriptCore in Safari. The engine parses your text, compiles it to fast machine instructions just-in-time, and runs it.

Running your first code

// Open any browser, press F12, click "Console", and type:
console.log("Hello, world!");
// In Node.js, save as app.js and run:  node app.js
console.log is your most-used tool for years to come. It prints values so you can see what your program is doing.
↑ Back to top
Storing values

Variables & Declarations

A variable is a named container for a value. JavaScript has three keywords to declare one: let, const, and the older var.

let score = 10;        // can be reassigned
score = 20;             // fine

const name = "Ada";    // cannot be reassigned
// name = "Grace";      // TypeError!

var old = "legacy";     // avoid in modern code

Which to use?

  • Default to const. It signals the binding won't change, which makes code easier to reason about.
  • Use let only when you genuinely need to reassign (counters, accumulators, swapping).
  • Avoid var. It has confusing scope rules (function-scoped, hoisted) that cause bugs.
const on an object or array means the binding is fixed, not the contents — you can still push to a const array or change its properties.
↑ Back to top
The values themselves

Data Types

JavaScript has seven primitive types plus objects. Primitives are immutable single values; everything else is an object.

The primitives

  • String — text: "hi", 'yo', `hey`
  • Number — all numbers, integer or decimal: 42, 3.14, -0, Infinity, NaN
  • BigInt — integers beyond safe number range: 9007199254740993n
  • Booleantrue / false
  • undefined — a variable that has been declared but not assigned
  • null — an intentional "no value"
  • Symbol — a unique, immutable identifier (advanced)

The reference type

Object — collections of key/value pairs. Arrays, functions, dates, and almost everything else are specialized objects.

typeof "hello"     // "string"
typeof 42          // "number"
typeof true        // "boolean"
typeof undefined   // "undefined"
typeof null        // "object"  ← a famous historical bug
typeof {}          // "object"
typeof []          // "object"  (use Array.isArray to check)
typeof function(){} // "function"
Watch out: typeof null returns "object" — a bug kept for backward compatibility. To check for null, use value === null.
↑ Back to top
Doing things to values

Operators & Type Coercion

Operators combine and compare values. The categories you'll use constantly:

// Arithmetic
5 + 2   // 7
5 % 2   // 1  (remainder / modulo)
5 ** 2  // 25 (exponent)

// Comparison
5 > 2   // true
5 === "5"  // false (strict: type AND value)
5 == "5"   // true  (loose: coerces first)

// Logical
true && false  // false (AND)
true || false  // true  (OR)
!true          // false (NOT)

Type coercion

JavaScript automatically converts types in many operations. This is powerful but surprising:

"5" + 3     // "53"  (number becomes string)
"5" - 3     // 2     (string becomes number)
true + true  // 2     (booleans become numbers)
[] + []      // ""    (arrays become strings)
Golden rule: Always use === and !== (strict equality). Avoid == unless you have a specific reason. This single habit prevents a whole class of bugs.

Falsy values

Eight values are "falsy" — they behave like false in conditions: false, 0, -0, 0n, "", null, undefined, and NaN. Everything else is "truthy".

↑ Back to top
Making decisions

Control Flow

Control flow lets your program choose different paths and repeat work.

Conditionals

if (temp > 30) {
  console.log("Hot");
} else if (temp > 15) {
  console.log("Mild");
} else {
  console.log("Cold");
}

// Ternary — a compact if/else that returns a value
const label = temp > 30 ? "Hot" : "OK";

// Switch — clean multi-branch on one value
switch (day) {
  case "Sat":
  case "Sun":
    console.log("Weekend");
    break;
  default:
    console.log("Weekday");
}
Don't forget break in switch cases — without it, execution "falls through" to the next case (occasionally useful, usually a bug).
↑ Back to top
Repeating work

Loops

Loops execute a block repeatedly. JavaScript gives you several, each suited to a situation.

// Classic for — when you need an index/counter
for (let i = 0; i < 5; i++) {
  console.log(i);  // 0 1 2 3 4
}

// while — repeat until a condition is false
let n = 3;
while (n > 0) { console.log(n); n--; }

// do...while — runs the body at least once
do { console.log("runs once minimum"); } while (false);

// for...of — iterate VALUES of arrays/strings/iterables
for (const fruit of ["apple", "pear"]) {
  console.log(fruit);
}

// for...in — iterate KEYS of an object (use sparingly)
for (const key in {a:1, b:2}) {
  console.log(key);  // "a", "b"
}

Loop control: break exits the loop entirely; continue skips to the next iteration.

Rule of thumb: use for...of for arrays, for...in for object keys, and the classic for when you need precise index control. For transforming arrays, prefer .map()/.filter() (covered later).
↑ Back to top
Reusable blocks

Functions

A function is a reusable block of code that takes inputs (parameters), does work, and optionally returns a value. They are the fundamental unit of organization in JavaScript.

// Function declaration — hoisted, can call before definition
function add(a, b) {
  return a + b;
}

// Function expression — assigned to a variable
const multiply = function(a, b) {
  return a * b;
};

// Arrow function — concise, modern (more later)
const square = x => x * x;

add(2, 3);      // 5
square(4);       // 16

Parameters & arguments

function greet(name = "friend") {  // default parameter
  return `Hello, ${name}!`;
}
greet();          // "Hello, friend!"
greet("Sam");     // "Hello, Sam!"

// Rest parameter — collect many args into an array
function sum(...nums) {
  return nums.reduce((a, b) => a + b, 0);
}
sum(1, 2, 3, 4);  // 10
A function without a return statement returns undefined. The return keyword also immediately exits the function.
↑ Back to top
Where names live

Scope & Hoisting

Scope determines where a variable is accessible. JavaScript has global scope, function scope, and block scope.

const g = "global";   // visible everywhere

function outer() {
  const f = "function";  // visible in outer()
  if (true) {
    const b = "block";    // visible only in this {}
    console.log(g, f, b);  // all three work
  }
  // console.log(b);     // ReferenceError
}

let and const are block-scoped (confined to the nearest {}). The old var is function-scoped, ignoring blocks — a common source of bugs.

Hoisting

JavaScript "hoists" declarations to the top of their scope before running. Function declarations are fully hoisted; var is hoisted but undefined; let/const are hoisted into a "temporal dead zone" and error if used early.

sayHi();  // ✅ works — declaration hoisted
function sayHi() { console.log("hi"); }

// console.log(x); // ❌ ReferenceError (temporal dead zone)
let x = 5;
↑ Back to top
Working with text

Strings

Strings are sequences of characters. They're immutable — methods return new strings rather than changing the original.

const s = "JavaScript";
s.length              // 10
s.toUpperCase()       // "JAVASCRIPT"
s.slice(0, 4)        // "Java"
s.includes("Script") // true
s.indexOf("S")       // 4
s.replace("Java", "Type") // "TypeScript"
s.split("")          // ["J","a","v",...]
"  hi  ".trim()      // "hi"

Template literals

Backticks let you embed expressions and write multi-line strings — far better than + concatenation.

const name = "Sam", age = 30;
const msg = `${name} is ${age} years old.
This spans multiple lines.`;
↑ Back to top
Calculation

Numbers & Math

All JavaScript numbers are 64-bit floating point (there's no separate integer type, aside from BigInt). The Math object provides calculation helpers.

Math.round(4.6)    // 5
Math.floor(4.9)    // 4
Math.ceil(4.1)     // 5
Math.max(1, 9, 3)   // 9
Math.random()       // 0 ≤ x < 1
Math.sqrt(144)     // 12

(3.14159).toFixed(2)  // "3.14" (string!)
parseInt("42px")     // 42
parseFloat("3.5kg")  // 3.5
Number("123")        // 123
Floating-point gotcha: 0.1 + 0.2 === 0.3 is false (it's 0.30000000000000004). This affects all languages using IEEE 754. For money, work in integer cents or use a decimal library.
// NaN = "Not a Number", the result of invalid math
0 / 0             // NaN
Number.isNaN(0/0) // true — the safe way to check
↑ Back to top
Ordered lists

Arrays (Basics)

An array is an ordered, zero-indexed list of values. Arrays can hold any mix of types and grow or shrink dynamically.

const fruits = ["apple", "banana", "cherry"];
fruits[0]        // "apple"  (first)
fruits[fruits.length - 1]  // "cherry" (last)
fruits.length    // 3

// Adding & removing
fruits.push("date");     // add to END → length 4
fruits.pop();           // remove from END
fruits.unshift("kiwi");  // add to START
fruits.shift();         // remove from START

// Useful checks
fruits.includes("banana") // true
fruits.indexOf("cherry")  // 2
Array.isArray(fruits)     // true
Arrays are objects under the hood, so a const array can still be mutated — const only prevents reassigning the whole array.
↑ Back to top
Key/value structures

Objects (Basics)

An object stores data as key/value pairs (properties). It's the workhorse structure for modeling real-world things.

const person = {
  name: "Ada",
  age: 36,
  isAdmin: true,
  greet() {                  // a method
    return `Hi, I'm ${this.name}`;
  }
};

// Two ways to access properties
person.name          // "Ada"  (dot notation)
person["age"]       // 36     (bracket — for dynamic keys)
person.greet()       // "Hi, I'm Ada"

// Add, change, delete
person.email = "ada@x.com";
person.age = 37;
delete person.isAdmin;
Use dot notation by default. Use bracket notation when the key is stored in a variable or isn't a valid identifier: person[fieldName].
↑ Back to top
Touching the page

DOM Basics

In the browser, the DOM (Document Object Model) is a live, tree-shaped representation of the HTML. JavaScript reads and changes it to make pages interactive.

// Selecting elements
const title = document.querySelector("h1");
const all = document.querySelectorAll(".item");
const byId = document.getElementById("menu");

// Reading & changing content
title.textContent = "New heading";
title.innerHTML = "<em>styled</em>";
title.style.color = "crimson";
title.classList.add("active");
title.setAttribute("data-id", "42");

// Responding to a click
title.addEventListener("click", () => {
  console.log("Heading clicked!");
});
innerHTML with untrusted input opens you to XSS attacks. Prefer textContent for plain text; sanitize anything dynamic you inject as HTML.
↑ Back to top
Finding problems

Debugging Basics

Every developer spends much of their time debugging. Build these habits early.

Console methods

console.log(value);       // general output
console.error("broke");   // red error output
console.warn("careful");  // yellow warning
console.table(arrayOfObjects); // neat grid
console.dir(domElement);   // inspect object structure
console.time("t"); /* ... */ console.timeEnd("t"); // timing

The debugger

The debugger; statement pauses execution where it sits (when DevTools is open), letting you inspect variables step by step. Browser DevTools also let you set breakpoints by clicking line numbers in the Sources panel.

Read your errors. The message and stack trace usually tell you the exact file, line, and nature of the problem. "x is not a function", "Cannot read properties of undefined" — each has a predictable cause you'll learn to recognize.
↑ Back to top
2

Intermediate

Level 2

Now you write JavaScript that's expressive and idiomatic. These topics — array methods, closures, the modern ES6+ toolkit, async basics — are where JavaScript starts to feel powerful instead of just functional.

The functional toolkit

Array Iteration Methods

These methods replace most manual loops with clear, expressive, chainable operations. They're among the most important things to master in JavaScript.

Transforming & filtering

const nums = [1, 2, 3, 4, 5];

// map — transform every element, returns a NEW array
nums.map(n => n * 2);        // [2, 4, 6, 8, 10]

// filter — keep elements that pass a test
nums.filter(n => n % 2 === 0); // [2, 4]

// reduce — boil an array down to one value
nums.reduce((sum, n) => sum + n, 0); // 15

Searching & testing

nums.find(n => n > 3);     // 4 (first match)
nums.findIndex(n => n > 3);// 3
nums.some(n => n > 4);     // true  (any pass?)
nums.every(n => n > 0);    // true  (all pass?)
nums.forEach(n => console.log(n)); // side effects only

Chaining — the real power

const result = [1,2,3,4,5,6]
  .filter(n => n % 2 === 0)  // [2, 4, 6]
  .map(n => n * 10)          // [20, 40, 60]
  .reduce((a, b) => a + b);   // 120
map vs forEach: use map when you want a new array back; use forEach only for side effects (logging, DOM updates) where the return value doesn't matter.
↑ Back to top
Two families of methods

Mutating vs Non-Mutating

Some array methods change the original array (mutating); others return a new one (non-mutating). Knowing which is which prevents subtle bugs.

// MUTATING — change the original
arr.push(), arr.pop(), arr.shift(), arr.unshift()
arr.splice(start, count, ...items)  // remove/insert
arr.sort(), arr.reverse(), arr.fill()

// NON-MUTATING — return a new array, leave original alone
arr.slice(start, end)   // extract a portion
arr.concat(other)       // join arrays
arr.map(), arr.filter(), [...arr]  // copy

sort needs care

[10, 1, 5].sort();  // [1, 10, 5] — sorts as STRINGS!

// Provide a comparator for numbers:
[10, 1, 5].sort((a, b) => a - b);  // [1, 5, 10]
sort() and reverse() mutate in place. If you need the original intact, copy first: [...arr].sort(). Modern engines also offer non-mutating toSorted() and toReversed().
↑ Back to top
Unpacking values

Destructuring

Destructuring extracts values from arrays and objects into variables with concise syntax. You'll use it constantly.

// Array destructuring — by position
const [first, second] = ["a", "b", "c"];
// first = "a", second = "b"

const [head, ...tail] = [1, 2, 3, 4];
// head = 1, tail = [2, 3, 4]

// Object destructuring — by key
const { name, age } = person;

// Rename + default values
const { name: userName, role = "guest" } = person;

// In function parameters — very common
function draw({ x, y, color = "black" }) {
  /* ... */
}
draw({ x: 10, y: 20 });

// Swap variables with no temp
let a = 1, b = 2;
[a, b] = [b, a];
↑ Back to top
The ... operator

Spread & Rest

The three-dot ... operator does two opposite jobs depending on context: spread expands; rest collects.

Spread — expand into elements

// Copy & merge arrays
const combined = [...arr1, ...arr2];
const copy = [...original];   // shallow copy

// Copy & merge objects (later keys win)
const merged = { ...defaults, ...overrides };
const updated = { ...user, age: 31 };

// Pass array as individual arguments
Math.max(...[3, 7, 2]);  // 7

Rest — collect into one

function log(first, ...others) {
  // others is an array of remaining args
}
const { id, ...rest } = bigObject; // pull off id, keep the rest
Shallow copy: spread copies only one level deep. Nested objects/arrays are still shared by reference. For deep copies use structuredClone(obj).
↑ Back to top
Working with objects

Object Methods & Iteration

The Object constructor provides static methods for inspecting and transforming objects.

const user = { name: "Ada", age: 36, role: "dev" };

Object.keys(user)    // ["name", "age", "role"]
Object.values(user)  // ["Ada", 36, "dev"]
Object.entries(user) // [["name","Ada"], ["age",36], ...]

// Iterate an object cleanly
for (const [key, value] of Object.entries(user)) {
  console.log(`${key}: ${value}`);
}

// Build an object from entries (reverse of above)
Object.fromEntries([["a", 1], ["b", 2]]); // {a:1, b:2}

// Merge / copy
Object.assign({}, target, source);
// freeze to make read-only
Object.freeze(config);

Shorthand & computed keys

const name = "Sam", age = 22;
const obj = { name, age };  // {name:"Sam", age:22}

const key = "score";
const data = { [key]: 100 };  // {score: 100}
↑ Back to top
Concise & lexical this

Arrow Functions

Arrow functions offer shorter syntax and — crucially — do not bind their own this. They inherit it from the surrounding scope.

// Full → concise progression
const add = function(a, b) { return a + b; };
const add2 = (a, b) => { return a + b; };
const add3 = (a, b) => a + b;      // implicit return
const double = n => n * 2;       // one param, no parens
const rand = () => Math.random(); // no params

// Returning an object literal needs parens
const make = id => ({ id, active: true });

The this difference

const timer = {
  seconds: 0,
  start() {
    setInterval(() => {
      this.seconds++;  // ✅ "this" is the timer object
    }, 1000);
  }
};
Don't use arrow functions as object methods if they need this to refer to the object, or as constructors (they can't be used with new). Their lack of own this is a feature in callbacks but a trap in methods.
↑ Back to top
Functions remembering scope

Closures

A closure is a function that "remembers" the variables from the scope where it was created, even after that scope has finished. This is one of JavaScript's most powerful and most-asked-about concepts.

function makeCounter() {
  let count = 0;        // private variable
  return function() {
    count++;             // closes over `count`
    return count;
  };
}

const counter = makeCounter();
counter();  // 1
counter();  // 2  — remembers count between calls

const other = makeCounter();
other();    // 1  — independent closure

Why it matters

  • Data privacycount can't be accessed or tampered with from outside.
  • Factories — generate customized functions.
  • Callbacks & event handlers rely on closures to access surrounding state.
// A configurable function factory
function multiplier(factor) {
  return n => n * factor;  // closes over `factor`
}
const triple = multiplier(3);
triple(10);  // 30
↑ Back to top
Context binding

The this Keyword

this refers to the object a function is operating on — but what it points to depends on how the function is called, not where it's defined. This trips up many learners.

// 1. Method call → this is the object before the dot
const obj = { name: "Ada", hi() { return this.name; } };
obj.hi();  // "Ada" — this === obj

// 2. Plain call → undefined (strict) or global object
const fn = obj.hi;
fn();      // undefined — lost its context!

// 3. Arrow → inherits this from enclosing scope
// 4. Constructor (new) → the new instance

Controlling this explicitly

function greet(greeting) { return greeting + " " + this.name; }
const p = { name: "Sam" };

greet.call(p, "Hi");      // "Hi Sam" — call now, args listed
greet.apply(p, ["Hey"]);   // "Hey Sam" — call now, args array
const bound = greet.bind(p); // returns a new bound function
bound("Yo");              // "Yo Sam"
call/apply/bind all set this manually. call and apply invoke immediately (differing only in how args are passed); bind returns a new function for later.
↑ Back to top
The modern toolkit

ES6+ Essentials

ES6 (2015) and yearly updates since transformed JavaScript. Beyond let/const, arrows, destructuring, and template literals (covered already), these features are everyday tools.

// Optional chaining — safe deep access
user?.address?.city   // undefined instead of throwing

// Nullish coalescing — default only for null/undefined
const port = config.port ?? 3000;
// (?? differs from || : 0 and "" are kept, not replaced)

// Logical assignment
x ??= 10;   // assign if x is null/undefined
y ||= 5;    // assign if y is falsy

// Array/string includes
[1,2,3].includes(2);  // true

// Numeric separators for readability
const billion = 1_000_000_000;
?? vs ||: Use ?? when 0, "", or false are valid values you want to keep. Use || when any falsy value should trigger the default.
↑ Back to top
Organizing code in files

Modules

Modules split code across files with explicit import/export. Each module has its own scope — no more polluting the global namespace.

// ── math.js ──
export const PI = 3.14159;
export function add(a, b) { return a + b; }
export default function multiply(a, b) { return a * b; }

// ── app.js ──
import multiply, { PI, add } from "./math.js";
import * as math from "./math.js";  // namespace
import { add as sum } from "./math.js"; // rename
  • Named exports — any number per file, imported by exact name in braces.
  • Default export — one per file, imported with any name, no braces.
In the browser, load a module with <script type="module" src="app.js">. Modules are deferred and run in strict mode automatically. Node.js supports them via .mjs files or "type":"module" in package.json.
↑ Back to top
Dealing with failure

Error Handling

Robust programs anticipate failure. The try/catch/finally construct catches errors so your program can recover instead of crashing.

try {
  const data = JSON.parse(userInput);  // might throw
  process(data);
} catch (err) {
  console.error("Failed:", err.message);
  // handle / log / show friendly message
} finally {
  // always runs — cleanup, close resources
  spinner.hide();
}

Throwing your own errors

function withdraw(amount, balance) {
  if (amount > balance) {
    throw new Error("Insufficient funds");
  }
  return balance - amount;
}

// Custom error types
class ValidationError extends Error {
  constructor(msg, field) {
    super(msg);
    this.name = "ValidationError";
    this.field = field;
  }
}
Don't swallow errors with an empty catch {}. At minimum log them. Catch only what you can meaningfully handle; let truly unexpected errors surface during development.
↑ Back to top
Interactive pages

DOM Events & Manipulation

Beyond basic selection, real apps create, modify, and respond to elements dynamically.

Events

button.addEventListener("click", (event) => {
  event.preventDefault();    // stop default behavior
  event.stopPropagation();  // stop bubbling up
  console.log(event.target);  // element that fired it
});

// Common events: click, input, change, submit,
// keydown, mouseover, focus, blur, scroll, load

Event delegation

Instead of attaching listeners to many children, attach one to the parent and inspect event.target. Efficient and works for elements added later.

list.addEventListener("click", (e) => {
  if (e.target.matches("li")) {
    console.log("Clicked:", e.target.textContent);
  }
});

Creating & inserting elements

const li = document.createElement("li");
li.textContent = "New item";
list.append(li);          // add as last child
li.remove();             // delete it
↑ Back to top
Data interchange

JSON

JSON (JavaScript Object Notation) is the universal format for sending structured data between systems. It looks like JavaScript objects but is plain text following strict rules.

// Object → JSON string (for sending/storing)
const user = { name: "Ada", roles: ["admin"], active: true };
const json = JSON.stringify(user);
// '{"name":"Ada","roles":["admin"],"active":true}'

// Pretty-print with indentation
JSON.stringify(user, null, 2);

// JSON string → object (for receiving/reading)
const obj = JSON.parse(json);
obj.name;  // "Ada"

JSON rules

  • Keys must be in double quotes
  • Values: string, number, boolean, null, array, object only
  • No functions, no undefined, no comments, no trailing commas
JSON.parse throws on malformed input — always wrap it in try/catch when parsing untrusted data. JSON.stringify silently drops functions and undefined values.
↑ Back to top
Scheduling work

Timers & Async Basics

JavaScript can schedule code to run later. This is your first taste of asynchronous programming — code that doesn't run top-to-bottom.

// Run once after a delay (milliseconds)
const id = setTimeout(() => {
  console.log("2 seconds later");
}, 2000);
clearTimeout(id);  // cancel before it fires

// Run repeatedly
const tick = setInterval(() => {
  console.log("every second");
}, 1000);
clearInterval(tick);  // stop it

Why "async"?

JavaScript is single-threaded, but timers, network requests, and user events don't block. They're handed off and their callbacks run later when the main thread is free. Understanding this leads directly to the event loop and Promises.

console.log("A");
setTimeout(() => console.log("B"), 0);
console.log("C");
// Prints: A, C, B  — even with 0ms delay!
↑ Back to top
Pattern matching

Regular Expressions

Regular expressions (regex) describe text patterns for searching, validating, and replacing. Dense but immensely powerful.

const pattern = /\d+/g;   // one or more digits, global

"abc123def456".match(/\d+/g);  // ["123", "456"]
/^\d+$/.test("12345");          // true (all digits?)
"hello world".replace(/o/g, "0"); // "hell0 w0rld"

Common building blocks

  • \d digit, \w word char, \s whitespace, . any char
  • * 0+, + 1+, ? 0 or 1, {2,4} range
  • ^ start, $ end, [abc] set, (...) group, | or
  • Flags: g global, i case-insensitive, m multiline
// A simple email-ish check (real validation is harder!)
const email = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
email.test("a@b.com");  // true
Regex is a deep topic. Tools like regex101.com let you build and test patterns interactively with live explanations — invaluable while learning.
↑ Back to top
Working with time

Dates & Time

The built-in Date object handles dates and times — though it's notoriously awkward, so many projects reach for libraries.

const now = new Date();           // current moment
const specific = new Date("2025-06-14");
const fromParts = new Date(2025, 5, 14); // month is 0-indexed!

now.getFullYear();   // 2026
now.getMonth();      // 0–11 (Jan = 0)
now.getDate();       // day of month 1–31
now.getDay();        // day of week 0–6 (Sun = 0)
now.getTime();       // ms since Jan 1 1970 (epoch)

now.toISOString();   // "2026-06-14T..." standard format
now.toLocaleDateString(); // locale-aware
Month is zero-indexed (January is 0, December is 11) — an endless source of bugs. For serious date work, consider date-fns, Day.js, or the newer built-in Temporal API as it lands.
↑ Back to top
3

Advanced

Level 3

The concepts that separate competent from expert. Asynchronous flow, the engine's inner workings, prototypes, the functional and object-oriented depths, and the performance know-how that production code demands.

Taming the async

Promises

A Promise represents a value that will be available later — the result of an async operation. It's an object in one of three states: pending, fulfilled, or rejected.

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    const ok = Math.random() > 0.5;
    ok ? resolve("Success!") : reject(new Error("Failed"));
  }, 1000);
});

promise
  .then(result => console.log(result))
  .catch(err => console.error(err))
  .finally(() => console.log("done"));

Combining promises

// Wait for ALL (fails if any fails)
const [a, b] = await Promise.all([fetchA(), fetchB()]);

// All settle, success or failure
await Promise.allSettled([p1, p2]);

// First to finish wins
await Promise.race([slow(), timeout()]);

// First to SUCCEED
await Promise.any([mirror1(), mirror2()]);
Promises chain: each .then() returns a new promise, so you can sequence async steps without deeply nested callbacks ("callback hell").
↑ Back to top
Synchronous-looking async

async / await

async/await is syntactic sugar over promises that lets asynchronous code read like ordinary sequential code. It's the modern default.

async function loadUser(id) {
  try {
    const res = await fetch(`/users/${id}`);
    if (!res.ok) throw new Error(res.status);
    const user = await res.json();
    return user;
  } catch (err) {
    console.error("Load failed:", err);
    throw err;  // re-throw or return a fallback
  }
}

// Calling it (also async context)
const user = await loadUser(42);
  • Any function marked async returns a promise automatically.
  • await pauses the function until the promise settles, then resumes with its value.
  • Use ordinary try/catch for errors — much cleaner than .catch().
Don't serialize independent awaits. await a(); await b(); runs them one after another. If they're independent, run in parallel: await Promise.all([a(), b()]).
↑ Back to top
How async really works

The Event Loop

JavaScript is single-threaded yet handles concurrency. The event loop is the mechanism that makes this possible — essential knowledge for an expert.

The pieces

  • Call stack — where functions execute, one at a time.
  • Web APIs / host — timers, fetch, DOM events run outside the engine.
  • Task queue (macrotasks) — callbacks from setTimeout, events, I/O.
  • Microtask queue — promise callbacks, queueMicrotask, with higher priority.

The loop: run all stack work → drain all microtasks → take one macrotask → repeat. Microtasks always finish before the next macrotask.

console.log("1");                          // sync
setTimeout(() => console.log("2"), 0);  // macrotask
Promise.resolve().then(() => console.log("3")); // microtask
console.log("4");                          // sync

// Output: 1, 4, 3, 2
// sync first, then microtask (3), then macrotask (2)
This ordering explains countless "why did this run before that?" mysteries. The key takeaway: promise callbacks jump ahead of setTimeout, every time.
↑ Back to top
Talking to servers

Fetch & HTTP

The fetch API makes HTTP requests to load data from and send data to servers. It returns a promise and is the modern replacement for XMLHttpRequest.

// GET request
const res = await fetch("https://api.example.com/users");
const data = await res.json();

// POST with a JSON body
const res2 = await fetch("/api/users", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ name: "Ada" })
});

Handling responses properly

async function getData(url) {
  const res = await fetch(url);
  // fetch only rejects on network failure —
  // you must check status yourself!
  if (!res.ok) {
    throw new Error(`HTTP ${res.status}`);
  }
  return res.json();
}
fetch doesn't reject on 404 or 500. A response arrived, so the promise fulfills. Always check res.ok or res.status before treating the response as success.
↑ Back to top
JS's object model

Prototypes & Inheritance

JavaScript inheritance is prototypal: objects link to other objects (their prototype) and inherit properties through that chain. Classes are syntax sugar over this.

const animal = {
  speak() { return `${this.name} makes a sound`; }
};

// Create an object whose prototype is `animal`
const dog = Object.create(animal);
dog.name = "Rex";
dog.speak();  // "Rex makes a sound" — found via prototype

The prototype chain

When you access a property, the engine looks on the object itself, then its prototype, then its prototype, up to Object.prototype and finally null. This chain is how methods like .toString() are available everywhere.

Object.getPrototypeOf(dog) === animal;  // true
dog.hasOwnProperty("name");  // true — own, not inherited
"speak" in dog;             // true — own OR inherited
Every function has a .prototype object; every instance created with new links to it. This is the machinery classes hide.
↑ Back to top
OOP syntax

Classes

Classes provide clean syntax for creating objects and inheritance, built on top of prototypes.

class Animal {
  constructor(name) {
    this.name = name;        // instance property
  }
  speak() {                    // shared via prototype
    return `${this.name} speaks`;
  }
  static create(name) {        // called on class itself
    return new Animal(name);
  }
}

class Dog extends Animal {
  constructor(name, breed) {
    super(name);             // call parent constructor
    this.breed = breed;
  }
  speak() {                    // override
    return `${this.name} barks`;
  }
}

const rex = new Dog("Rex", "Lab");
rex.speak();  // "Rex barks"

Modern class features

class Counter {
  #count = 0;           // private field (# prefix)
  static label = "counter"; // static field

  get value() { return this.#count; } // getter
  increment() { this.#count++; }
}
#private fields are truly inaccessible outside the class — real encapsulation, unlike the old underscore convention.
↑ Back to top
Pure & composable

Functional Programming

Functional programming (FP) emphasizes pure functions, immutability, and composition. JavaScript supports it well, and FP ideas make code more predictable and testable.

Pure functions

A pure function returns the same output for the same input and causes no side effects (no mutation, no I/O, no globals).

// Pure ✅
const add = (a, b) => a + b;

// Impure ❌ (mutates external state)
let total = 0;
const addToTotal = n => { total += n; };

Immutability

// Don't mutate — create new values
const updated = { ...user, age: 31 };
const withItem = [...list, newItem];
const withoutFirst = list.slice(1);

Composition

const compose = (...fns) => x =>
  fns.reduceRight((acc, fn) => fn(acc), x);

const shout = compose(
  s => s + "!",
  s => s.toUpperCase()
);
shout("hi");  // "HI!"
Higher-order functions (functions that take or return functions) are FP's backbone — map, filter, reduce, and your own factories all qualify.
↑ Back to top
Closure patterns

Currying & Memoization

Several powerful patterns build directly on closures.

Currying

Transform a multi-argument function into a chain of single-argument functions — useful for specialization and composition.

const curry = fn => a => b => c => fn(a, b, c);

const volume = curry((l, w, h) => l * w * h);
volume(2)(3)(4);  // 24

// Partial application — fix some args
const greet = greeting => name => `${greeting}, ${name}`;
const sayHi = greet("Hi");
sayHi("Ada");  // "Hi, Ada"

Memoization

Cache a function's results so repeated calls with the same input are instant.

function memoize(fn) {
  const cache = new Map();
  return (...args) => {
    const key = JSON.stringify(args);
    if (cache.has(key)) return cache.get(key);
    const result = fn(...args);
    cache.set(key, result);
    return result;
  };
}
const fastFib = memoize(fib);
↑ Back to top
Custom iteration

Iterators & Generators

Iterators define how an object is looped over. Generators are special functions that produce values lazily, pausing and resuming.

Generators

function* range(start, end) {
  for (let i = start; i <= end; i++) {
    yield i;   // pause and emit a value
  }
}

for (const n of range(1, 4)) console.log(n); // 1 2 3 4
[...range(1, 3)];  // [1, 2, 3]

// Infinite sequences — lazy, only computes when asked
function* ids() {
  let i = 1;
  while (true) yield i++;
}
const gen = ids();
gen.next().value;  // 1
gen.next().value;  // 2

Custom iterables

const collection = {
  items: ["a", "b", "c"],
  [Symbol.iterator]() {
    let i = 0;
    return {
      next: () => i < this.items.length
        ? { value: this.items[i++], done: false }
        : { value: undefined, done: true }
    };
  }
};
[...collection];  // ["a", "b", "c"]
↑ Back to top
Better collections

Map, Set, WeakMap, WeakSet

Beyond objects and arrays, ES6 added specialized collection types with distinct advantages.

Map — keyed collection

const map = new Map();
map.set("name", "Ada");
map.set(obj, "any value as key!"); // keys can be ANY type
map.get("name");   // "Ada"
map.has("name");   // true
map.size;          // 2
for (const [k, v] of map) { /* iterates in insertion order */ }

Map vs Object: Maps allow any key type, preserve insertion order, have a .size, and iterate easily — better for dynamic key/value collections.

Set — unique values

const set = new Set([1, 2, 2, 3, 3]);
set.size;           // 3 — duplicates removed
set.add(4);
set.has(2);          // true

// Deduplicate an array instantly
const unique = [...new Set(arr)];

WeakMap / WeakSet

Hold object keys "weakly" — if no other reference exists, they can be garbage collected. Used for private data and caches without memory leaks. Not iterable and keys must be objects.

↑ Back to top
Unique identifiers

Symbols

A Symbol is a unique, immutable primitive often used as a non-colliding object key or to define special behaviors.

const id = Symbol("id");     // the string is just a label
Symbol("id") === Symbol("id"); // false — always unique

const user = { [id]: 123, name: "Ada" };
user[id];  // 123 — won't clash with any "id" string key

Well-known symbols

Symbols also let you hook into language behaviors:

const obj = {
  [Symbol.iterator]() { /* makes obj iterable */ },
  [Symbol.toPrimitive](hint) { /* custom coercion */ }
};

// Global symbol registry (shared across realms)
const shared = Symbol.for("app.id");
Symbol.for("app.id") === shared;  // true
Symbol keys are excluded from for...in, Object.keys, and JSON — making them useful for "hidden" metadata.
↑ Back to top
Metaprogramming

Proxy & Reflect

Proxy wraps an object to intercept and customize fundamental operations (get, set, delete, etc.). Reflect provides the default versions of those operations.

const target = { name: "Ada", age: 36 };

const proxy = new Proxy(target, {
  get(obj, prop) {
    console.log(`reading ${prop}`);
    return Reflect.get(obj, prop);
  },
  set(obj, prop, value) {
    if (prop === "age" && typeof value !== "number") {
      throw new TypeError("age must be a number");
    }
    return Reflect.set(obj, prop, value);
  }
});

proxy.name;        // logs "reading name" → "Ada"
proxy.age = "old";  // throws TypeError
Proxies power reactive frameworks (Vue 3's reactivity, MobX), validation layers, and observable objects. They're advanced but conceptually elegant: intercept any operation and decide what happens.
↑ Back to top
ESM vs CommonJS

Module Systems Deep-Dive

JavaScript has two major module systems. Understanding both matters when working across browser and Node.js code.

ES Modules (ESM) — the standard

import { thing } from "./mod.js";
export const x = 1;
// Static, hoisted, async-loaded, tree-shakeable
// Works in browsers and modern Node

CommonJS (CJS) — Node's legacy

const thing = require("./mod");
module.exports = { x: 1 };
// Synchronous, dynamic, Node-only origin

Dynamic imports

Load modules on demand — for code splitting and lazy loading.

async function loadChart() {
  const { Chart } = await import("./chart.js");
  return new Chart();
}
// Only fetches chart.js when actually needed
Tree-shaking — because ESM imports are static, bundlers can detect and eliminate unused exports, shrinking your final bundle. CJS's dynamic nature makes this harder.
↑ Back to top
Managing resources

Memory & Garbage Collection

JavaScript manages memory automatically via garbage collection (GC). The engine periodically frees memory occupied by values that are no longer reachable. Understanding this prevents leaks.

How reachability works

A value is "reachable" if it can be accessed from a root (global object, current call stack, etc.) through a chain of references. Once nothing references it, it becomes eligible for collection.

Common leak sources

  • Forgotten timers/listenerssetInterval never cleared, listeners never removed, keep their closures alive.
  • Detached DOM nodes — JS variables holding removed elements.
  • Growing caches — Maps/objects that only ever add entries.
  • Closures capturing large objects unnecessarily.
// Leak: listener keeps `bigData` alive forever
element.addEventListener("click", () => use(bigData));

// Fix: remove when done
const handler = () => use(bigData);
element.addEventListener("click", handler);
// later:
element.removeEventListener("click", handler);
Use WeakMap/WeakSet for caches keyed by objects so entries vanish when the objects do. Browser DevTools' Memory panel finds leaks via heap snapshots.
↑ Back to top
Debounce, throttle & more

Performance Patterns

Smooth, responsive apps require controlling how often expensive work runs.

Debounce — wait for a pause

Run a function only after activity stops. Ideal for search-as-you-type and resize handlers.

function debounce(fn, delay) {
  let timer;
  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => fn(...args), delay);
  };
}
input.addEventListener("input",
  debounce(search, 300));

Throttle — limit the rate

Run at most once per interval. Ideal for scroll and mousemove handlers.

function throttle(fn, limit) {
  let waiting = false;
  return (...args) => {
    if (!waiting) {
      fn(...args);
      waiting = true;
      setTimeout(() => waiting = false, limit);
    }
  };
}

Other techniques

  • requestAnimationFrame for visual updates synced to the display refresh.
  • Batch DOM reads/writes to avoid layout thrashing.
  • Virtualize long lists — render only visible rows.
  • Lazy-load images and modules.
↑ Back to top
Beyond the DOM

Browser Web APIs

Browsers expose many APIs beyond the DOM for storage, background work, and device features.

Storage

// localStorage — persists across sessions, ~5–10MB, strings only
localStorage.setItem("theme", "dark");
localStorage.getItem("theme");   // "dark"
localStorage.removeItem("theme");

// sessionStorage — same API, cleared when tab closes
// Store objects via JSON
localStorage.setItem("user", JSON.stringify(user));

IndexedDB

A full in-browser database for large, structured data and offline apps (asynchronous, more complex — often used via wrappers like idb).

Web Workers

Run heavy computation on a background thread so the UI stays responsive.

// main.js
const worker = new Worker("worker.js");
worker.postMessage({ numbers: bigArray });
worker.onmessage = e => console.log(e.data);

// worker.js
onmessage = e => {
  const result = heavyCompute(e.data.numbers);
  postMessage(result);
};
Service Workers are a special worker enabling offline caching, background sync, and push notifications — the foundation of Progressive Web Apps.
↑ Back to top
Reusable solutions

Design Patterns

Design patterns are proven templates for common problems. A few that appear constantly in JavaScript:

Module pattern

const counter = (() => {
  let count = 0;              // private
  return {
    increment() { return ++count; },
    get() { return count; }
  };
})();  // IIFE creates one instance with privacy

Observer (pub/sub)

class Emitter {
  #listeners = {};
  on(event, fn) {
    (this.#listeners[event] ??= []).push(fn);
  }
  emit(event, data) {
    (this.#listeners[event] ?? []).forEach(fn => fn(data));
  }
}

Factory & Singleton

// Factory — function that builds objects
const createUser = (name, role) => ({ name, role, created: Date.now() });

// Singleton — one shared instance
const Config = (() => {
  let instance;
  return { get() { return instance ??= { loaded: true }; } };
})();
Patterns are vocabulary, not rules. Reach for them when they clarify a real problem — not to force structure where simple code would do.
↑ Back to top
4

Ecosystem & Beyond

Level 4

JavaScript rarely runs alone. This final level surveys the surrounding world — the language layer on top (TypeScript), the tools that build and test your code, the runtimes and frameworks, and the security knowledge every professional needs.

JavaScript with types

TypeScript

TypeScript is a superset of JavaScript that adds static types, checked before your code runs. It catches whole categories of bugs at write-time and powers excellent editor autocomplete.

// Annotate variables, params, and returns
function greet(name: string): string {
  return `Hi, ${name}`;
}

// Interfaces & types describe object shapes
interface User {
  id: number;
  name: string;
  email?: string;   // optional
  readonly role: string;
}

// Generics — reusable, type-safe abstractions
function first<T>(arr: T[]): T | undefined {
  return arr[0];
}

// Union & literal types
type Status = "idle" | "loading" | "error";
TypeScript compiles to plain JavaScript — types are erased at build time. It's now the default for most large codebases and well worth learning once your JS fundamentals are solid.
↑ Back to top
From source to ship

Build Tools & Bundlers

Modern apps use build tools to bundle modules, transpile new syntax for older browsers, and optimize output.

  • Vite — the current default: instant dev server (native ESM), fast production builds via Rollup. Great DX.
  • esbuild — extremely fast bundler/transpiler written in Go; powers many other tools.
  • Webpack — the long-dominant, highly configurable bundler; still common in established projects.
  • Rollup — excellent for libraries; pioneered tree-shaking.
  • Babel — transpiles modern/experimental JS to widely supported versions.

What they do

  • Bundling — combine many modules into few optimized files.
  • Transpiling — convert TS/JSX/new syntax to compatible JS.
  • Minification — strip whitespace and shorten names.
  • Tree-shaking — remove unused code.
  • Code splitting — load parts of the app on demand.
Start a modern project with npm create vite@latest. You rarely configure bundlers by hand anymore — sensible defaults cover most needs.
↑ Back to top
npm and dependencies

Package Management

npm (Node Package Manager) installs and manages the libraries your project depends on, drawing from a registry of millions of packages.

# Initialize a project
npm init -y

# Install dependencies
npm install lodash          # runtime dependency
npm install -D vitest       # dev-only dependency
npm install                 # install everything in package.json

# Run scripts defined in package.json
npm run build
npm test

Key files

  • package.json — project metadata, dependencies, and scripts.
  • package-lock.json — exact resolved versions for reproducible installs.
  • node_modules/ — the installed packages (never commit this).

Semantic versioning (^1.2.3): MAJOR.MINOR.PATCH. ^ allows minor/patch updates; ~ allows patch only.

Alternatives like pnpm (disk-efficient) and yarn offer the same role with different trade-offs. npx runs a package without installing it globally.
↑ Back to top
Confidence in code

Testing

Automated tests verify your code works and keep it working as you change it. They're essential for any non-trivial project.

Test types

  • Unit — test one function/module in isolation.
  • Integration — test pieces working together.
  • End-to-end (E2E) — test full user flows in a real browser.
// A unit test with Vitest / Jest
import { describe, it, expect } from "vitest";
import { add } from "./math.js";

describe("add", () => {
  it("sums two numbers", () => {
    expect(add(2, 3)).toBe(5);
  });
  it("handles negatives", () => {
    expect(add(-1, 1)).toBe(0);
  });
});

The toolkit

  • Vitest / Jest — test runners and assertion libraries.
  • Testing Library — test UI the way users interact with it.
  • Playwright / Cypress — E2E browser automation.
Aim to test behavior, not implementation. Good tests give you the confidence to refactor fearlessly.
↑ Back to top
JavaScript on the server

Node.js

Node.js runs JavaScript outside the browser, powered by Chrome's V8 engine. It's used for servers, APIs, command-line tools, and build tooling.

// A minimal HTTP server (no framework)
import { createServer } from "node:http";

createServer((req, res) => {
  res.writeHead(200, { "Content-Type": "application/json" });
  res.end(JSON.stringify({ message: "Hello from Node" }));
}).listen(3000);

The ecosystem

  • Built-in modulesfs (files), path, http, crypto, os, and more.
  • Express / Fastify / Hono — web frameworks for building APIs.
  • Event-driven, non-blocking I/O — handles many connections efficiently on one thread.
Newer server runtimes — Deno (secure-by-default, built-in TS) and Bun (all-in-one, very fast) — are worth knowing about, though Node remains the default.
↑ Back to top
Building UIs at scale

Frontend Frameworks

Frameworks manage UI complexity — state, rendering, and components — so you don't hand-write DOM updates. The major players:

  • React — the most widely used. Component-based, uses a virtual DOM, vast ecosystem. Pairs with Next.js for full-stack apps.
  • Vue — approachable, gentle learning curve, excellent docs. Nuxt is its full-stack meta-framework.
  • Angular — full, opinionated framework with TypeScript and tooling built in; common in enterprises.
  • Svelte — compiles components to minimal vanilla JS at build time; no runtime virtual DOM. SvelteKit for full apps.
  • Solid — fine-grained reactivity with React-like syntax and exceptional performance.
// A React component — the dominant mental model
function Counter() {
  const [count, setCount] = useState(0);
  return (
    <button onClick={() => setCount(count + 1)}>
      Clicked {count} times
    </button>
  );
}
Learn vanilla JS first. Frameworks come and go; the language fundamentals underneath them are permanent and transfer everywhere.
↑ Back to top
Sharing app data

State Management

As apps grow, sharing and updating data across many components becomes hard. State management tools solve this.

  • Component state — local data (React's useState, Vue's ref). The starting point.
  • Context / provide-inject — pass data down a tree without prop-drilling.
  • Stores — centralized, shared state: Redux/Redux Toolkit, Zustand, Jotai (React); Pinia (Vue); Svelte stores.
  • Server state — TanStack Query, SWR: cache, sync, and revalidate data from APIs.

Core idea

Most modern state tools follow one principle: state is a single source of truth, updated through controlled actions, and the UI reactively re-renders when it changes. This predictable one-way flow makes complex apps debuggable.

Don't reach for heavy state libraries too early. Local state plus a little context handles many apps. Add a store only when genuinely shared, cross-cutting state appears.
↑ Back to top
Protecting users

Web Security

Every JavaScript developer must understand common vulnerabilities and their defenses. Security is not optional.

XSS — Cross-Site Scripting

Attackers inject malicious scripts via unsanitized user input. Defense: never insert untrusted data as HTML; use textContent, sanitize with libraries like DOMPurify, and set a Content Security Policy.

// ❌ Dangerous
el.innerHTML = userInput;
// ✅ Safe
el.textContent = userInput;

CSRF — Cross-Site Request Forgery

Tricks a logged-in user's browser into making unwanted requests. Defense: anti-CSRF tokens, SameSite cookies.

CORS — Cross-Origin Resource Sharing

A browser security model controlling which origins may call your API. Servers grant access via response headers; it's a protection, not an obstacle to route around.

General principles

  • Validate and sanitize all input — on the server, always.
  • Never trust the client; never put secrets/API keys in front-end code.
  • Use HTTPS everywhere; store tokens carefully (httpOnly cookies over localStorage for sensitive auth).
  • Keep dependencies updated — run npm audit.
Most breaches exploit basic, well-known issues. Following the fundamentals above prevents the large majority of real-world attacks.
↑ Back to top
The frontier

PWAs, WebAssembly & What's Next

A few advanced platform capabilities round out an expert's awareness.

Progressive Web Apps (PWAs)

Web apps that behave like native ones: installable, offline-capable (via service workers), with push notifications. Built from a manifest file plus a service worker caching strategy.

WebAssembly (Wasm)

A binary format letting languages like Rust, C++, and Go run in the browser at near-native speed. JavaScript and Wasm interoperate — use Wasm for compute-heavy tasks (image/video processing, games, simulations) and JS for everything else.

// Loading a Wasm module from JS
const wasm = await WebAssembly.instantiateStreaming(
  fetch("module.wasm")
);
wasm.instance.exports.compute(42);

Keeping current

  • TC39 proposals shape the language yearly — watch stage-3/4 features (Temporal, decorators, pattern matching).
  • MDN Web Docs is the authoritative reference for every API.
  • Read source code of libraries you use — the fastest way to deepen mastery.
You never "finish" learning JavaScript — and that's the appeal. Solid fundamentals plus curiosity carry you through every new tool the ecosystem invents.
↑ Back to top