The Complete JavaScript Guide
Basic to Advanced — every core language concept explained in detail, with a live, editable code example under each one.
JavaScript, Basic to Advanced
A hands-on JavaScript reference covering every core concept from your first variable to async programming and the event loop — with a live editor under every example so you can run, break, and rewrite the code right here.
What Is JavaScript?
JavaScript is the programming language of the web. Every button click, form validation, animation, and live update you see in a browser is driven by it — and thanks to Node.js, the same language now runs servers, command-line tools, and even mobile apps. It's dynamically typed (you don't declare a variable's type up front), interpreted (no separate compile step), and single-threaded with a clever concurrency model called the event loop that lets it stay responsive while waiting on slow things like network requests.
Where does JavaScript run?
Three places you'll use constantly: inside a <script> tag in an HTML page, directly in your browser's DevTools console (right-click → Inspect → Console), or on a server via Node.js by running node file.js in a terminal. This document uses a fourth option: a live sandbox, so you never have to leave the page.
Your first line of JavaScript
The function you'll use more than any other while learning is console.log() — it prints a value so you can see what your code is doing. Try editing the message below and pressing Run.
Variables
A variable is a named container for a value you want to store and reuse. JavaScript gives you three keywords to create one — let, const, and the older var — and the difference between them is almost entirely about scope, reassignment, and hoisting behavior.
var vs. let vs. const — quick comparison
| Feature | var | let | const |
|---|---|---|---|
| Scope | Function or Global | Block ({ }) | Block ({ }) |
| Reassignment | Allowed | Allowed | Forbidden |
| Redeclaration | Allowed (same scope) | Forbidden (same scope) | Forbidden (same scope) |
| Hoisting behavior | Hoisted, initialized as undefined | Hoisted, but unusable until declared (TDZ) | Hoisted, but unusable until declared (TDZ) |
| Initial value required | No | No | Yes |
let/const variable is hoisted and when its declaration line actually runs. Accessing it during that gap throws a ReferenceError instead of silently returning undefined. See Scope & Hoisting for a runnable example.
let — for values that will change
Use let when you know the variable's value needs to be updated later, like a counter or a running total. It's block-scoped, meaning it only exists inside the { } it was declared in.
const — for values that won't be reassigned
Use const by default for everything else. It prevents the variable itself from being reassigned — though if it holds an object or array, the contents can still be modified; only the reference is locked.
var — the legacy keyword
You'll still see var in older code. It's function-scoped instead of block-scoped (so it "leaks" out of if blocks and loops) and can be redeclared, which causes subtle bugs. Modern JavaScript style guides recommend avoiding it entirely in favor of let and const.
$, or _, can't start with a digit, and are case-sensitive (age and Age are different variables). By convention, JavaScript uses camelCase for variables and functions.
Data Types
Every value in JavaScript is one of two kinds: a primitive (immutable, copied by value) or an object (mutable, copied by reference). Understanding which is which explains a lot of behavior that otherwise looks like magic.
| Type | Category | Example | Notes |
|---|---|---|---|
| String | Primitive | "hello" | Text, wrapped in quotes or backticks |
| Number | Primitive | 42, 3.14 | Both integers and decimals — one type for both |
| BigInt | Primitive | 42n | For integers larger than Number can safely hold |
| Boolean | Primitive | true, false | Logical yes/no |
| undefined | Primitive | undefined | A variable that's been declared but not assigned |
| null | Primitive | null | An intentional "no value", set explicitly |
| Symbol | Primitive | Symbol("id") | A guaranteed-unique value, often used as an object key |
| Object | Reference | { name: "Ava" } | Key-value collections — arrays and functions are objects too |
Checking a type with typeof
The typeof operator tells you what kind of value you're dealing with — essential for debugging and validating input.
Primitives vs. objects: copy by value vs. reference
This is one of the most important mental models in the language. Copying a primitive copies its value; copying an object copies a reference to the same underlying data.
Type conversion
JavaScript will often convert types for you automatically ("coercion"), which is convenient but a common source of bugs. It's usually safer to convert explicitly.
Operators
Operators combine and compare values. Beyond the arithmetic you already know, JavaScript has a few operators worth learning deliberately because they show up constantly in real code.
| Operator | Category | Example | Result |
|---|---|---|---|
| + - * / % | Arithmetic | 7 % 2 | 1 (remainder) |
| ** | Exponent | 2 ** 8 | 256 |
| =, +=, -= | Assignment | x += 5 | same as x = x + 5 |
| === / !== | Strict equality | 1 === "1" | false |
| && / || / ! | Logical | true && false | false |
| ?: | Ternary | age >= 18 ? "adult" : "minor" | inline if/else |
| ?? | Nullish coalescing | value ?? "default" | fallback only for null/undefined |
| ?. | Optional chaining | user?.address?.city | undefined instead of a crash |
|| falls back whenever the left side is any "falsy" value (0, "", null, undefined, NaN). ?? only falls back for null or undefined — use it whenever 0 or "" should count as a valid value.
Control Flow
Control flow statements decide which code runs, based on a condition. if/else handles most cases; switch is a cleaner alternative when you're comparing one value against many possibilities.
if / else if / else
Each diamond is one condition, checked top to bottom. The moment a condition is true, its Yes branch fires and the function returns immediately — the rest of the chain is never checked. Only if every condition is false do you fall through to the final else.
switch
Each case falls through to the next unless you break — a common source of bugs, so don't forget it.
Loops
Loops repeat a block of code. Pick the loop that matches your data: a counted for loop for numeric ranges, for...of for iterating values in arrays/strings, and while when you don't know the number of iterations ahead of time.
for
Every loop shares this same shape: ① set up a starting value once, ② check the condition, ③ run the body while it's true, ④ update the counter, then jump back to ② and check again. The loop only ends once the condition finally comes back false.
while & do...while
while checks the condition before each pass; do...while always runs at least once because it checks after.
for...of and for...in
for...of loops over the values of an iterable (arrays, strings, Maps, Sets). for...in loops over the keys of an object — avoid using it on arrays.
break and continue
Functions
A function is a reusable block of code you define once and call as many times as you need. JavaScript gives you three main ways to write one, plus a rich set of parameter features.
Function declarations vs. expressions vs. arrow functions
A declaration is hoisted (usable before its definition in the file). An expression assigns a function to a variable and is not hoisted. An arrow function is a shorter expression syntax that also doesn't bind its own this (see The "this" Keyword).
① Calling add(2, 3) sends its arguments across into the function's parameters — a and b. ②③ The function body runs using only those parameters and computes a + b. ④ The result travels back out through return and becomes the value of the call itself — that's why sum = add(2, 3) ends up holding 5.
Default and rest parameters
Default parameters fill in a value when the caller doesn't provide one. Rest parameters collect any number of extra arguments into a real array.
Every function returns something
If there's no explicit return, a function returns undefined. This trips up beginners more than almost anything else.
map, event handlers, etc.) and anywhere you need to preserve the surrounding this.
Strings & String Methods
Strings represent text and are immutable — every "modifying" method actually returns a brand-new string rather than changing the original.
| Method | Description |
|---|---|
| .length | Number of characters (a property, not a method) |
| .toUpperCase() / .toLowerCase() | Change case |
| .trim() | Remove whitespace from both ends |
| .slice(start, end) | Extract a section; accepts negative indexes |
| .split(separator) | Break a string into an array |
| .includes(text) | Check whether a substring exists |
| .replace(old, new) | Replace the first match (use a /g regex for all) |
| .indexOf(text) | Position of the first match, or -1 |
| .padStart(len, char) | Pad the start until it reaches a length |
+, use backtick strings with ${expression} — see the Template Literals section.
Numbers & Math
JavaScript has a single Number type for both integers and decimals, built on 64-bit floating point — which is why decimal math can look slightly "off," and why the built-in Math object exists for everything beyond basic arithmetic.
Arrays
An array is an ordered list of values, indexed from zero. Arrays are objects under the hood, which is why typeof [] reports "object" — but they come with a huge set of purpose-built methods, covered next.
Array Methods
These higher-order methods are how array data is actually processed in modern JavaScript — they replace most manual for loops over arrays and read more like a description of intent than a mechanical procedure.
| Method | What it does |
|---|---|
| .map() | Transforms every item, returns a new array of the same length |
| .filter() | Keeps only items that pass a test, returns a shorter (or equal) array |
| .reduce() | Folds the whole array down into a single value |
| .forEach() | Runs a function per item; returns nothing (for side effects only) |
| .find() / .findIndex() | Returns the first matching item, or its index |
| .some() / .every() | Returns true if any / all items pass a test |
| .sort() | Sorts in place — always pass a compare function for numbers |
| .includes() | True if the array contains a given value |
| .slice() / .splice() | slice copies a range; splice mutates the array in place |
| .concat() / spread | Combine arrays without mutating either one |
| .flat() | Flattens nested arrays by a given depth |
| .join() | Combines items into a single string with a separator |
| .push() / .pop() / .shift() / .unshift() | Add or remove items from either end — all mutate in place |
| .indexOf() / .lastIndexOf() | Index of the first (or last) matching value, or -1 |
| .at() | Index from the end with a negative number — no length math |
| .findLast() / .findLastIndex() | Like find/findIndex, but searches from the end |
| .flatMap() | map() then flatten one level, in a single pass |
| .reduceRight() | Like reduce, but walks the array right to left |
| .reverse() / .fill() / .copyWithin() | Mutate the array's existing slots in place |
| .entries() / .keys() / .values() | Iterators over [index, value] pairs, indexes, or values |
| .toString() / .toLocaleString() | Convert to a comma-joined, or locale-formatted, string |
| .toReversed() / .toSorted() / .toSpliced() / .with() | Non-mutating copies of reverse/sort/splice/index-assign |
| Array.from() / Array.of() / Array.isArray() | Build or check arrays — called on Array itself, not an instance |
map — transform every item
filter — keep only what matches
reduce — fold into one value
reduce is the most flexible (and most confusing at first) array method: it walks the array carrying an "accumulator" forward.
forEach vs. map
forEach is for side effects (like logging); it always returns undefined. If you need a new array back, use map instead.
find, some, every
sort — always pass a compare function for numbers
slice, splice, concat, flat, join
push, pop, shift, unshift — add & remove from either end
All four mutate the original array and all four run in constant time at the end, but shift/unshift have to re-index every remaining item, so they're slower on large arrays than push/pop.
indexOf, lastIndexOf, includes — find a value
at() — index from the end, no length math
findLast, findLastIndex — search from the end
flatMap — map, then flatten one level
reduceRight — like reduce, but right to left
reverse, fill, copyWithin — mutate existing slots in place
entries, keys, values — iterate with more context
toString, toLocaleString
Non-mutating copies (ES2023) — toReversed, toSorted, toSpliced, with
These are the "safe" counterparts to reverse/sort/splice — same result, but they return a new array and leave the original completely untouched.
Array static methods — Array.from, Array.fromAsync, Array.isArray, Array.of
These are called on Array itself, not on an existing array instance.
Objects & Object Methods
An object stores data as key-value pairs. It's the closest thing JavaScript has to a general-purpose "record" — most real-world data (a user, a product, an API response) ends up modeled as one. Beyond reading and writing properties, the Object constructor itself ships a full set of static methods for merging, locking down, and inspecting objects.
| Method | What it does |
|---|---|
| Object.keys() / .values() / .entries() | Arrays of an object's own keys, values, or [key, value] pairs |
| Object.fromEntries() | The reverse of .entries() — builds an object back from pairs |
| Object.assign() | Shallow-copies properties from one or more sources into a target |
| Object.create() | Creates a new object with a given prototype |
| Object.freeze() / .seal() / .preventExtensions() | Lock an object down, with increasing strictness |
| Object.isFrozen() / .isSealed() / .isExtensible() | Check which lock (if any) is currently applied |
| Object.defineProperty() / .defineProperties() | Add a property with fine-grained control (getters, enumerability…) |
| Object.getOwnPropertyDescriptor(s)() | Read that fine-grained configuration back out |
| Object.getPrototypeOf() / .setPrototypeOf() | Read or change what an object inherits from |
| Object.getOwnPropertyNames() / .getOwnPropertySymbols() | Own string keys, or own Symbol keys (keys() skips both non-enumerable and Symbol keys) |
| Object.is() | Like === , but correctly handles NaN and -0/+0 |
| Object.hasOwn() | True if a property exists directly on the object (not inherited) |
| Object.groupBy() | Groups an array's items into an object, keyed by a callback's result |
Creating & accessing objects
{} === {} is always false — two different objects are never equal, even with identical contents, because === compares references, not structure. Only comparing an object to itself (the same reference) returns true.
Object.fromEntries — the reverse of Object.entries
Object.assign vs. spread — merging & copying
Object.create — build from a specific prototype
freeze, seal, preventExtensions — locking objects down
Three levels, from strictest to loosest: freeze blocks every change, seal allows editing existing properties but not adding/removing, preventExtensions only blocks adding new ones.
defineProperty & property descriptors
Every property has hidden configuration — writable, enumerable, configurable — that plain assignment sets to true by default. defineProperty lets you set it explicitly, or attach a getter.
Prototype & key introspection
Object.is, Object.hasOwn, Object.groupBy — modern helpers
Destructuring
Destructuring unpacks values from arrays or objects into standalone variables in one line — instead of accessing each property individually.
Object destructuring
Array destructuring
Spread & Rest Operators
Both use the same ... syntax but do opposite jobs: spread expands a collection out into individual elements; rest gathers individual elements back into a collection. Which one you're looking at depends on where it appears.
Spread — expand a collection
Rest — gather into a collection
Template Literals
Backtick-delimited strings let you embed expressions directly with ${ }, and span multiple lines without escape characters — a big readability upgrade over string concatenation with +.
Execution Context & the Call Stack
Before JavaScript runs a single line of your code, it builds a container called an execution context — one for the top-level (global) code, and a brand new one every time a function is called. Every execution context, no matter which one, is built in exactly the same two phases — and understanding those two phases is what makes hoisting, this, and stack overflows stop feeling like magic.
Phase 1: Memory Creation, Phase 2: Code Execution
JavaScript never just runs your file top to bottom in one pass. For every execution context it creates, it does two full passes over that context's code:
① Memory Creation Phase — JS scans the code without running any of it and sets up memory in advance: every var is registered and initialized to undefined, every function declaration is stored in full (the whole function body, not just its name), and this gets bound. This scan is exactly what "hoisting" is — it's not variables physically moving to the top of the file, it's JavaScript pre-allocating memory for them before execution starts.
② Code Execution Phase — only now does JavaScript run your code line by line, top to bottom, for real: variables are assigned their actual values (overwriting the undefined placeholder from Phase 1), expressions are evaluated, and every function call pushes a brand new execution context — with its own Phase 1 and Phase 2 — onto the call stack.
The left box is the anatomy of a single execution context — Phase ① always finishes completely before Phase ② begins. The right box is the call stack: the Global Execution Context goes in first, and calling greet() pushes a second execution context on top of it — one that runs through the exact same two phases on its own code. When greet() returns, its context is popped off and control drops back to the one underneath.
let/const behave differently in Phase 1 via the Temporal Dead Zone.
Scope & Hoisting
Scope determines where a variable is visible. Hoisting describes how JavaScript sets up variables and functions before running your code line-by-line — which explains some surprising ordering behavior.
Block scope vs. function scope
Hoisting & the Temporal Dead Zone
Function declarations are hoisted completely (you can call them before they appear in the file). var declarations are hoisted but start as undefined. let and const are hoisted too, but stay in a "Temporal Dead Zone" — unusable until their line actually runs — which is why they throw instead of silently returning undefined.
Closures
A closure is a function that "remembers" the variables from the scope it was created in, even after that outer function has finished running. It's how JavaScript implements private state without classes — and it powers patterns like counters, memoization, and event handler factories.
A practical use: function factories
The "this" Keyword
this refers to "whatever object is calling the function" — and unlike most languages, that's determined by how a function is called, not where it's defined. This is the single most common source of JavaScript bugs for people coming from other languages.
function for object methods (so this refers to the object). Use an arrow function for callbacks nested inside a method, so this correctly stays pointed at the outer object instead of becoming undefined.
Higher-Order Functions & Callbacks
In JavaScript, functions are values — they can be stored in variables, passed as arguments, and returned from other functions. A higher-order function is any function that does at least one of those things. A callback is simply a function passed in to be called later.
Error Handling
try/catch lets your program recover from a runtime error instead of crashing entirely. Use it around code that can realistically fail — parsing data, calling an API, reading user input — not around every single line.
Custom error types
JSON
JSON (JavaScript Object Notation) is the universal text format for exchanging data between a browser and a server. It looks almost identical to a JS object literal, but it's a separate format with stricter rules (keys must be double-quoted strings, no functions or undefined allowed).
Date Object
The built-in Date object handles dates and times. Its API predates modern conventions in a few places (months are zero-indexed!), which is worth knowing before it surprises you.
date-fns or Intl.DateTimeFormat rather than hand-rolling it.
Regular Expressions
A regular expression (regex) is a pattern for matching text — used for validation (is this a valid email?), searching, and find-and-replace operations that are too flexible for plain string methods.
| Pattern | Matches |
|---|---|
\d | Any digit |
\w | Any word character (letter, digit, underscore) |
\s | Any whitespace |
+ * ? | One-or-more / zero-or-more / zero-or-one |
^ $ | Start / end of the string |
g flag | Match globally, not just the first occurrence |
Map & Set
Two collection types introduced in ES6 that solve problems plain objects and arrays handle awkwardly: Map is a key-value store that allows any value as a key, and Set is a list that automatically enforces uniqueness.
Map — key-value pairs, any key type
Set — a list of unique values
DOM Manipulation
The DOM (Document Object Model) is the browser's live, in-memory representation of your HTML page as a tree of objects. JavaScript's DOM API lets you read and change that tree — which is how a page updates without a full reload. These examples run in a real mini-page below the editor, not just the console.
Selecting elements
Creating and changing elements
Attributes vs. properties
Events
Events are how a page responds to what a user does — clicks, typing, scrolling, submitting a form. addEventListener is the standard way to react to them: it attaches a callback that runs whenever the event fires.
Listening for a click
The event object
The callback receives an event object describing what happened — which key was pressed, where the mouse was, which element was the target, and so on.
Event delegation
Instead of attaching a listener to every single button (expensive, and misses buttons added later), attach one listener to a shared parent and check event.target.
Local & Session Storage
Both let you save key-value data directly in the browser, no server needed. localStorage persists until explicitly cleared (even after closing the tab); sessionStorage disappears when the tab closes. Both only store strings — objects must be serialized with JSON.stringify.
Prototypes & Inheritance
Every JavaScript object has a hidden link to another object called its prototype, which it can borrow properties and methods from. This "prototype chain" is how inheritance worked in JavaScript long before the class keyword existed — and classes (next section) are really just a friendlier syntax built on top of it.
.map(), .push(), etc. from Array.prototype.
Classes
ES6 classes are syntax sugar over prototypal inheritance — a cleaner, more familiar way (especially if you know other languages) to define a blueprint for creating objects with shared behavior.
Getters, setters, and static members
Promises
A Promise represents a value that isn't ready yet but will be — the result of something asynchronous like a network request or a timer. It's always in one of three states: pending, fulfilled, or rejected, and it replaced the deeply-nested "callback hell" pattern from early JavaScript.
Running promises in parallel
Promise.all waits for every promise and rejects immediately if any one fails. Promise.race settles as soon as the first one finishes, win or lose. Promise.allSettled waits for all of them and never short-circuits, giving you a status report for each.
Async / Await
async/await is syntax that lets promise-based code read top-to-bottom like synchronous code, instead of a chain of .then() calls. Under the hood it's still promises — this is just a more readable way to write them.
Error handling with try/catch
Fetch API
fetch() is the standard browser function for making HTTP requests — calling an API, loading data, submitting a form without a page reload. It returns a Promise that resolves to a Response object, which you then read with .json() (also a Promise, since parsing the body is async).
mockFetch — a stand-in with the exact same shape as the real thing. In your own project, replace it with the real fetch(url) and the rest of the code is identical.
fetch with async/await
The Event Loop
JavaScript runs on a single thread, so it can't truly do two things at once — yet it handles timers, network requests, and UI events without freezing. The event loop is the mechanism that makes this possible: it constantly checks whether the call stack is empty, and if so, pulls the next queued callback in to run.
Reading the diagram against the example above: ①② the two synchronous console.log calls run directly on the call stack. ③ Promise.then() schedules its callback straight onto the microtask queue. ④ setTimeout hands off to a Web API, which only pushes the callback onto the macrotask queue once the timer elapses. Once the call stack is empty, the event loop drains the entire microtask queue first, and only then pulls a single macrotask — which is why the output lands in the order 1, 2, 3, 4.
setTimeout callback) is allowed to run, the entire microtask queue (Promise callbacks) is fully drained. That's why a Promise.then always jumps ahead of a setTimeout(fn, 0), no matter the order they were written in.
Modules
Modules let you split code across multiple files and explicitly choose what each file shares (export) and pulls in from elsewhere (import), instead of everything living in one giant global scope. Because modules only work across real files loaded by a browser or Node (not inside a single embedded sandbox like this document), the example below is a reference, not a live playground.
// Named exports — a file can have as many as it needs
export function add(a, b) {
return a + b;
}
export const PI = 3.14159;
// Default export — a file can only have one
export default function multiply(a, b) {
return a * b;
}
import multiply, { add, PI } from "./math.js";
console.log(add(2, 3)); // 5
console.log(multiply(2, 3)); // 6
console.log(PI); // 3.14159
<script type="module" src="app.js"></script>. In Node.js, either use this same import/export syntax in a .mjs file (or a project with "type": "module" in package.json), or use the older CommonJS require()/module.exports style still common in existing codebases.
Generators & Iterators
A generator is a special function that can pause itself with yield and resume later, producing a sequence of values on demand instead of computing them all up front. They're the mechanism for...of loops rely on under the hood, and they're handy for lazy sequences and custom iteration.
An infinite sequence, consumed lazily
Official Documentation — All 36 Topics
Every topic in this guide, linked straight to its official reference on MDN (or the relevant spec) — for when you need more depth than a live example can give you.