Web development cheat sheetJavaScript logo

JavaScript cheat sheet

JavaScript syntax on one page: variables, array methods, destructuring, classes, modules, async/await, fetch and the DOM, checked against ES2025.

Last updated

JavaScript is the language of the web: every browser runs it, and Node.js runs it on servers and in the terminal. The reference below is grouped by what you are trying to do, and the filter box searches all of it at once. Type array and everything about arrays comes to you, or type ES2025 to see what the latest edition added.

Every snippet is checked against ECMAScript 2025, the current edition of the standard, and was run on Node.js 24. Anything added after ES2020 says which edition in the notes column. Names like user, nums and el are placeholders for your own. Coming from another language? The Python, Ruby and PHP cheat sheets are grouped the same way, so they read side by side. Adding types? The TypeScript cheat sheet picks up where this one stops. The regex rows in the strings table are the basics: the Regex cheat sheet has the pattern syntax itself, from character classes to lookbehind.

Running JavaScript

TaskCodeNotes
Check which Node.js version you havenode --version
Run a file with Node.jsnode app.js
Open an interactive shellnodeLeave with .exit or Ctrl+D. The browser console (F12) works the same way
Run one line without a filenode -e 'console.log(2 ** 53)'
Print the result of an expressionnode -p '[1, 2, 3].length'
Rerun every time you savenode --watch app.jsStable in Node 22
Load variables from a .env filenode --env-file=.env app.jsNode 20.6+. Read them with process.env.API_KEY
Load a script in a web page<script src="app.js" defer></script>defer runs it after the HTML is parsed, in order
Load a module in a web page<script type="module" src="main.js"></script>Deferred automatically, and can use import
Print to the consoleconsole.log("total", total)Several values are separated by a space
Print an array of objects as a tableconsole.table(rows)
Warnings and errorsconsole.warn("low disk") console.error(err)In Node these go to stderr, not stdout
Time a piece of codeconsole.time("load") console.timeEnd("load")Prints load: 12.3ms, with the real time
Pause in the debuggerdebugger;Only stops while the browser DevTools are open, or under node --inspect
Strict mode"use strict";First line of a script. Modules and classes are strict already

Variables and types

TaskCodeNotes
A variable you will reassignlet count = 0;
A variable you will not reassignconst name = "Ada";The default choice. A const array can still be pushed to
The old way you will still seevar total = 0;Scoped to the function, not the block. Use let or const
Comment// one line /* several lines */
The seven primitive typesstring number bigint boolean undefined null symbolEverything else, arrays and functions included, is an object
What type is ittypeof valueReturns a string. typeof null is "object", a bug kept for compatibility
Is it an arrayArray.isArray(value)typeof [] is "object"
Is it made by a classvalue instanceof Date
No valueundefined nullundefined is what you get for something missing. null is what you set on purpose
Numbers42 3.14 1_000_000 0xff 1e3One number type, a 64-bit float. Underscores are ignored (ES2021)
Largest safe whole numberNumber.MAX_SAFE_INTEGER2 ** 53 - 1. Past it, whole numbers lose precision
Bigger whole numbers123n BigInt("9007199254740993")A BigInt. Cannot be mixed with numbers: 1n + 1 is a TypeError
String to numberNumber("42") +"42"NaN when it is not a number. Number("") is 0
Read a number from the start of a stringparseInt("42px", 10) parseFloat("3.5kg")Stops at the first character that does not fit. Always pass the base
Is it NaNNumber.isNaN(x)NaN === NaN is false, so x === NaN never works
Is it a whole numberNumber.isInteger(x)
Number to stringString(42) n.toString(2)toString(2) gives binary: (5).toString(2) is 101
Round to 2 decimal placesn.toFixed(2)Returns a string: (3.14159).toFixed(2) is 3.14
Round, down, up, cut offMath.round(x) Math.floor(x) Math.ceil(x) Math.trunc(x)Halves round up: Math.round(2.5) is 3 and Math.round(-2.5) is -2
Random whole number from 1 to 6Math.floor(Math.random() * 6) + 1Not for passwords or tokens. crypto.randomUUID() for an ID
Largest and smallestMath.max(3, 7, 1) Math.min(...nums)
Powers and roots2 ** 10 Math.sqrt(16) Math.abs(-4)
Infinity1 / 0Infinity, not an error. Number.isFinite(x) rules it out

The falsy values are false, 0, -0, 0n, the empty string, null, undefined and NaN. Everything else is truthy, including the string "0", an empty array and an empty object, so if (items.length) is how to ask whether an array has anything in it.

Operators

TaskCodeNotes
Equal, and not equala === b a !== bStrict: no type conversion. Use these by default
Loose equalitya == b a != bConverts types first, so 0 == "" and "1" == 1 are both true
Is it null or undefinedx == nullThe one common use of ==. Matches null and undefined only
Same value, NaN includedObject.is(a, b)Object.is(NaN, NaN) is true, Object.is(0, -0) is false
Arithmetica + b a - b a * b a / b7 / 2 is 3.5. There is no separate integer division
Whole-number divisionMath.trunc(7 / 2)3. Math.floor(-7 / 2) is -4, Math.trunc(-7 / 2) is -3
Remainder7 % 3Keeps the sign of the left side: -7 % 3 is -1
Raise to a power2 ** 101024. Not ^, which is bitwise xor
Add or subtract onei++ i-- i += 5
And, or, nota && b a || b !a&& and || return one of the two values, not always true or false
Default when null or undefinedconst port = config.port ?? 3000;ES2020. Keeps 0 and the empty string, unlike ||
Default when falsyconst name = input || "Anonymous";Also replaces 0 and the empty string
Set only if missingoptions.retries ??= 3;ES2021. ||= and &&= work the same way
Read a property that might not be thereuser?.address?.cityES2020. undefined instead of a TypeError
Call a method that might not be thereoptions.onDone?.()
Pick one of two valuesconst label = n === 1 ? "item" : "items";
Force a true or falseBoolean(value) !!value
Spread into a new array or object[...a, ...b] { ...defaults, ...overrides }In an object, later keys win
Does the object have this keyObject.hasOwn(user, "name")ES2022. "name" in user also looks up the prototype chain
Remove a propertydelete user.age
Compare strings"apple" < "banana"Compares character codes, so "Z" < "a" is true. Sort words with localeCompare
String and number mixed"5" + 3 "5" - 3"53" and 2. + joins if either side is a string, - always converts to numbers

Strings and template literals

Strings cannot be changed in place. Every method returns a new string, so assign the result: s = s.trim(). Positions start at 0.

TaskCodeNotes
Single, double or backtick quotes'cat' "cat" `cat`The same string. Backticks also allow placeholders and line breaks
Put a value in a string`Hello, ${name}`A template literal. Any expression goes inside the placeholder
Expression in a template literal`${items.length} ${items.length === 1 ? "item" : "items"}`
Lengths.lengthCounts UTF-16 units, so an emoji is usually 2. [...s].length counts code points
One characters[0] s.at(-1)First and last. at is ES2022 and counts back from the end with a negative
Part of a strings.slice(1, 4) s.slice(-3)The end position is not included
Change cases.toUpperCase() s.toLowerCase()
Trim whitespaces.trim() s.trimStart() s.trimEnd()
Split into an arrays.split(",")[...s] splits by code point, so most emoji stay whole
Join an array into a stringwords.join(", ")
Replace the first matchs.replace("cat", "dog")Only the first, when the pattern is a string
Replace every matchs.replaceAll("cat", "dog")ES2021
Does it contains.includes("cat")Case-sensitive
Starts or ends withs.startsWith("http") s.endsWith(".js")
Find the positions.indexOf("cat")-1 when missing
Pad to a lengthString(7).padStart(3, "0")007. padEnd pads on the right
Repeat"-".repeat(20)
Sort order of two stringsa.localeCompare(b)Negative, zero or positive. Handles accents and case sensibly
Test against a regex/^\d+$/.test(s)True when s is only digits
Every match of a regexs.match(/\d+/g)An array of strings, or null when nothing matches
Every match, with capture groups[...s.matchAll(/(\w+)=(\d+)/g)]Each item is a match: m[1] and m[2] are the groups
Replace by regexs.replace(/\s+/g, " ")Without the g flag, only the first match
Make user input safe inside a regexnew RegExp(RegExp.escape(input))ES2025. Node 24 and current browsers
Number with thousands separatorsn.toLocaleString("en-GB")1234567.5 becomes 1,234,567.5
Moneynew Intl.NumberFormat("en-GB", { style: "currency", currency: "GBP" }).format(9.5)£9.50
Character codes"A".codePointAt(0) String.fromCodePoint(65)65 and A. Works for emoji, unlike charCodeAt
Newline and tab"line\n" "col\t"
Backslashes kept as typedString.raw`C:\new\folder`
Base64 encode and decodebtoa("hi") atob("aGk=")Latin-1 text only. Anything else throws
Encode for a URLencodeURIComponent(query)For one value in a query string. decodeURIComponent reverses it

Arrays and array methods

An array is an ordered list that can hold any mix of values. Some methods change the array in place and some return a new one. The notes say which.

TaskCodeNotes
Create an arrayconst nums = [10, 20, 30];Empty: []
Array of the numbers 0 to 4Array.from({ length: 5 }, (_, i) => i)
Array of five zerosnew Array(5).fill(0)Only for plain values. fill({}) puts one shared object in every slot
Read by positionnums[0] nums.at(-1)undefined past the end, not an error
How many itemsnums.length
Is it emptynums.length === 0An empty array is truthy, so if (nums) is always true
Add to the endnums.push(40)In place. Returns the new length
Remove from the endnums.pop()In place. Returns the removed item
Add or remove at the startnums.unshift(5) nums.shift()In place
Remove or insert in the middlenums.splice(1, 2) nums.splice(1, 0, 15)In place. Remove 2 items at position 1, or insert 15 there
Copy part of an arraynums.slice(1, 3)New array. The end position is not included
Join two arrays[...a, ...b] a.concat(b)
Copy[...nums]Shallow: objects inside are still shared
Deep copystructuredClone(data)Copies nested arrays, objects, Dates, Maps and Sets. Not functions
Transform every itemnums.map((n) => n * 2)New array, same length
Keep some itemsnums.filter((n) => n > 15)New array
Combine into one valuenums.reduce((sum, n) => sum + n, 0)Always pass the starting value: an empty array without one throws
Run code for each itemnums.forEach((n, i) => console.log(i, n))Cannot break out. Use for...of when you need to
First item that matchesnums.find((n) => n > 15)undefined when nothing matches
Position of the first matchnums.findIndex((n) => n > 15)-1 when nothing matches
Last item that matchesnums.findLast((n) => n > 15)ES2023. findLastIndex for the position
Is the value in the arraynums.includes(20)Finds NaN too, unlike indexOf
Position of a valuenums.indexOf(20)-1 when missing
Do any or all matchnums.some((n) => n > 25) nums.every((n) => n > 0)
Sort numbersnums.sort((a, b) => a - b)In place. With no function it sorts as strings: [10, 9, 1] becomes [1, 10, 9]
Sort largest firstnums.sort((a, b) => b - a)
Sort wordsnames.sort((a, b) => a.localeCompare(b))
Sort objects by a fieldpeople.sort((a, b) => a.age - b.age)
Sorted copynums.toSorted((a, b) => a - b)ES2023. Leaves nums alone. Also toReversed and toSpliced
Copy with one item changednums.with(0, 99)ES2023
Reversenums.reverse()In place
Flatten nested arraysnested.flat() nested.flat(Infinity)One level by default
Map, then flatten one levellines.flatMap((line) => line.split(" "))
Group by a fieldObject.groupBy(people, (p) => p.team)ES2024. An object of arrays. Map.groupBy returns a Map
Remove duplicates[...new Set(items)]Keeps the first of each, in order
Unpack into variablesconst [first, second, ...rest] = nums;
Swap two variables[a, b] = [b, a];
Array from anything list-likeArray.from(new Set([1, 2])) Array.from("abc")[1, 2] and [a, b, c]
Empty the arraynums.length = 0;In place, so every reference sees it

Objects and destructuring

TaskCodeNotes
Create an objectconst user = { name: "Ada", age: 36 };Empty: {}
Read a propertyuser.name user["name"]undefined when missing. Brackets for a key held in a variable
Set a propertyuser.email = "ada@example.com";
Same name for key and variableconst user = { name, age };Short for { name: name, age: age }
Key from a variableconst totals = { [key]: 0 };
Methodconst cat = { speak() { return "meow"; } };
Keys, values, or bothObject.keys(user) Object.values(user) Object.entries(user)Arrays. entries gives [key, value] pairs
Loop over keys and valuesfor (const [key, value] of Object.entries(user)) {}
Object from pairsObject.fromEntries(pairs)Reverses Object.entries, so you can map or filter an object through an array
Merge into a new objectconst settings = { ...defaults, ...options };Shallow. Later keys win
Merge into an existing objectObject.assign(target, source)
Copy without some keysconst { password, ...safeUser } = user;
Pull out propertiesconst { name, age } = user;
Pull out and renameconst { name: userName } = user;
Default for a missing propertyconst { role = "member" } = user;Used for undefined only, not null
Pull out a nested propertyconst { address: { city } } = user;TypeError if address is missing
Destructure in the parameter listfunction greet({ name, greeting = "Hi" }) {}
Stop changesObject.freeze(config)Shallow. Changes are ignored, or a TypeError in strict mode
To a JSON stringJSON.stringify(user) JSON.stringify(user, null, 2)The second indents by 2. Drops undefined and functions. Dates become strings
From a JSON stringJSON.parse(text)SyntaxError when the JSON is not valid
Map: any value as a keyconst m = new Map(); m.set(key, value); m.get(key)Keys can be objects. Keeps insertion order. m.size, m.has, m.delete
Count how often each value appearscounts.set(w, (counts.get(w) ?? 0) + 1);counts is a Map
Map from an objectnew Map(Object.entries(obj))And back: Object.fromEntries(map)
Set: unique valuesconst tags = new Set(["js", "css", "js"]);tags.size is 2. tags.add, tags.has, tags.delete
Combine two setsa.union(b) a.intersection(b) a.difference(b)ES2025. Also symmetricDifference and isSubsetOf

Two objects are only === when they are the same object. { a: 1 } === { a: 1 } is false, and there is no built-in deep comparison.

Control flow and loops

TaskCodeNotes
If, else if, elseif (x > 0) {} else if (x < 0) {} else {}
Switchswitch (cmd) { case "start": run(); break; default: help(); }Compares with ===. Without break, it falls through to the next case
Loop a number of timesfor (let i = 0; i < 5; i++) {}0 to 4
Loop over an array's valuesfor (const item of items) {}Also works on strings, Maps, Sets and NodeLists
Loop with the positionfor (const [i, item] of items.entries()) {}
Loop over a Mapfor (const [key, value] of map) {}
Loop over an object's keysfor (const key in obj) {}Includes inherited keys. Do not use it on arrays
While loopwhile (queue.length > 0) {}
Run at least oncedo {} while (retry);
Leave a loopbreak;
Skip to the next itemcontinue;
Leave two loops at onceouter: for (const row of grid) { for (const cell of row) { if (cell) break outer; } }A labelled break

Functions and arrow functions

TaskCodeNotes
Function declarationfunction add(a, b) { return a + b; }Can be called above the line that defines it
Arrow functionconst add = (a, b) => a + b;One expression is returned without writing return
Arrow function with a bodyconst add = (a, b) => { return a + b; };With braces, you need return
Arrow with one parameterconst double = n => n * 2;The brackets are optional
Return an object from an arrowconst makeUser = (name) => ({ name });Wrap it in brackets, or the braces are read as the body
Function expressionconst add = function (a, b) { return a + b; };
Default valuefunction greet(name = "world") {}Used for undefined, not null
Any number of argumentsfunction sum(...nums) { return nums.reduce((a, b) => a + b, 0); }nums is a real array
Spread an array into argumentsMath.max(...nums)
Named optionsfunction connect({ host, port = 5432 } = {}) {}The = {} lets it be called with nothing
Return several valuesreturn [min, max];Then const [min, max] = range(nums)
Run code latersetTimeout(() => console.log("later"), 1000);Milliseconds. clearTimeout(id) cancels it
Run code repeatedlyconst id = setInterval(tick, 1000); clearInterval(id);
Closurefunction counter() { let n = 0; return () => ++n; }Each call to counter gets its own n
Generatorfunction* ids() { let i = 1; while (true) yield i++; }const gen = ids(); gen.next().value is 1, then 2
Fix this for laterconst handler = this.handleClick.bind(this);
Call with a chosen thisfn.call(obj, a, b) fn.apply(obj, [a, b])
Run once, straight away(() => { const secret = 42; })();Keeps its variables private. Rare now that modules exist

Arrow functions have no this of their own. They use the this of the code around them, which makes them right for callbacks inside a method and wrong as the method itself.

Classes

TaskCodeNotes
Define a classclass Point { constructor(x, y) { this.x = x; this.y = y; } }
Create an objectconst p = new Point(1, 2);Calling a class without new is a TypeError
Methoddist() { return Math.hypot(this.x, this.y); }Inside the class body. No function keyword, no commas
Field with a starting valuecount = 0;ES2022. In the class body, set on every new object
Private field#balance = 0;ES2022. this.#balance works only inside the class
Private method#check(amount) {}ES2022
Does it have the private fieldstatic isAccount(obj) { return #balance in obj; }ES2022
Getterget area() { return this.w * this.h; }Read as rect.area, no brackets
Setterset name(value) { this.#name = value.trim(); }Runs on user.name = " Ada "
Static methodstatic fromArray([x, y]) { return new Point(x, y); }Called on the class: Point.fromArray([1, 2])
Static fieldstatic count = 0;ES2022. Shared, read as Point.count
Inherit from a classclass Cat extends Animal {}
Call the parentsuper(name); super.speak();In a constructor, super() must come before any use of this
Custom string formtoString() { return `(${this.x}, ${this.y})`; }Used by String(p) and template literals
Is it made by this classp instanceof PointTrue for subclasses too
Class name of an objectp.constructor.name

A class body is always strict mode, and a class cannot be used above the line that defines it, unlike a function declaration. Methods live on the prototype and are shared by every instance.

Modules: import and export

A module is a file with its own scope that exports names for other files to import. Browsers load modules with script type="module". Node.js treats .mjs files, and .js files in a package whose package.json has "type": "module", as modules.

TaskCodeNotes
Named exportexport function add(a, b) { return a + b; }Works on const, let and class too
Export several at the endexport { add, subtract };
Default exportexport default class Cart {}One per file
Import named exportsimport { add, subtract } from "./math.js";Keep the .js: browsers and Node do not guess the extension
Import the default exportimport Cart from "./cart.js";Any name you like, no braces
Import under another nameimport { add as sum } from "./math.js";
Import everythingimport * as math from "./math.js";Then math.add(1, 2)
Pass exports straight throughexport { add } from "./math.js";For an index.js that gathers a folder
Run a module for its side effectsimport "./setup.js";
Load a module only when neededconst { add } = await import("./math.js");Returns a promise. Keeps rarely used code out of the first load
Import a JSON fileimport data from "./data.json" with { type: "json" };ES2025
await at the top of a moduleconst config = await loadConfig();ES2022. Modules only
This file's URL and folderimport.meta.url import.meta.dirnamedirname and filename are Node 20.11+
Import a Node.js built-inimport { readFile } from "node:fs/promises";
The older Node.js style, CommonJSconst fs = require("node:fs"); module.exports = { add };Still common in older packages and tutorials
Install a package from npmnpm install date-fnsThen import it by name, no ./ and no .js

Promises and async/await

A promise is a value that arrives later. An async function always returns a promise, and await pauses that function until the promise settles, without freezing the page or blocking other code.

TaskCodeNotes
Async functionasync function loadUser(id) {}Always returns a promise
Async arrow functionconst loadUser = async (id) => {};
Wait for a promiseconst res = await fetch(url);Inside an async function, or at the top of a module
Fetch JSONconst data = await (await fetch(url)).json();Skips the status check. See the next row
Check the response statusif (!res.ok) throw new Error(`HTTP ${res.status}`);fetch only rejects on network errors. A 404 or 500 still resolves
Send JSONawait fetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data) });
Handle a failuretry { await save(); } catch (err) { console.error(err); }
Wait for several at onceconst [user, posts] = await Promise.all([getUser(), getPosts()]);In parallel. Rejects as soon as any one rejects
Wait for all, even failuresconst results = await Promise.allSettled(tasks);Each result has status fulfilled with a value, or rejected with a reason
First one to finishawait Promise.race([task, timeout])
First one to succeedawait Promise.any([mirrorA(), mirrorB()])ES2021. Rejects only if every one fails
Give up after a few secondsawait fetch(url, { signal: AbortSignal.timeout(5000) });Rejects with a TimeoutError
Cancel a requestconst controller = new AbortController(); controller.abort();Pass controller.signal as the signal option to fetch
Wait a set timeconst sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));Then await sleep(500)
Make a promise from a callbacknew Promise((resolve, reject) => { img.onload = resolve; img.onerror = reject; })
A promise you resolve from outsideconst { promise, resolve, reject } = Promise.withResolvers();ES2024
Turn a throw into a rejectionPromise.try(() => JSON.parse(text))ES2025. Works for functions that may or may not be async
The older then stylefetch(url).then((res) => res.json()).then(show).catch(console.error);
Run code however it ends.finally(() => spinner.remove())Also try...finally with await
One at a time, in orderfor (const url of urls) { await download(url); }forEach does not wait: use for...of
Loop over an async streamfor await (const chunk of stream) {}

An error thrown inside an async function becomes a rejected promise. If nothing awaits it or calls catch, it is an unhandled rejection: a warning in the browser console, and in Node.js the process exits with an error.

Error handling

TaskCodeNotes
Catch an errortry { risky(); } catch (err) { console.error(err.message); }
Catch without naming the errortry { return JSON.parse(text); } catch { return null; }
Run code however the block endstry { work(); } finally { cleanUp(); }Runs after a return or an uncaught error too
Throw an errorthrow new Error("age must be positive");Throw an Error, not a string, so there is a stack trace
Built-in error typesTypeError RangeError SyntaxError ReferenceError
Handle one type onlyif (err instanceof TypeError) { useDefault(); } else { throw err; }catch has no type filter. Check inside it and rethrow the rest
Wrap an error with its causethrow new Error("could not load config", { cause: err });ES2022. Read it back as err.cause
Your own error typeclass NotFoundError extends Error { name = "NotFoundError"; }
Message and stack traceerr.message err.stack
Check an assumptionconsole.assert(total >= 0, "total went negative")Only logs. It does not throw or stop anything
Report errors nothing caught, in a browserwindow.addEventListener("error", (e) => report(e.error));
Report rejections nothing caught, in a browserwindow.addEventListener("unhandledrejection", (e) => report(e.reason));

Only catch errors you can do something about. A try around a whole function also swallows the typo in a variable name you would have wanted to hear about.

Dates

A Date is a single moment in time, stored as milliseconds since 1 January 1970 UTC. It is shown in the computer's local time zone unless you ask for another.

TaskCodeNotes
Nownew Date() Date.now()A Date object, and a plain number of milliseconds
Parse an ISO datenew Date("2026-09-27T14:30:00Z")A date with no time, like 2026-09-27, is read as midnight UTC
Make a date from partsnew Date(2026, 8, 27)27 September: months count from 0. Local time
Read partsd.getFullYear() d.getMonth() d.getDate() d.getDay()getMonth is 0 to 11. getDay is the weekday, 0 for Sunday
Standard string for storing and APIsd.toISOString()Always UTC: 2026-09-27T14:30:00.000Z
Readable dated.toLocaleDateString("en-GB", { dateStyle: "long" })27 September 2026
Add daysd.setDate(d.getDate() + 7);In place. Rolls over into the next month correctly
Days between two datesMath.round((b - a) / 86_400_000)Subtracting Dates gives milliseconds

DOM basics

The DOM is the web page as objects you can read and change. These run in the browser, not in Node.js. Load scripts with defer or type="module" so the elements exist when the code runs.

TaskCodeNotes
Find the first matchdocument.querySelector(".card")Any CSS selector. null when nothing matches
Find by iddocument.getElementById("menu")No # before the id
Find every matchdocument.querySelectorAll("li")A NodeList. It has forEach. [...list] for map and filter
Search inside one elementcard.querySelector("h2")
Nearest matching parentbutton.closest(".card")Checks the element itself first
Set textel.textContent = "Saved";Always shown as text, so it is safe for user input
Set HTMLel.innerHTML = "<strong>Saved</strong>";Never with user input: it can run their code
Read and set attributeslink.getAttribute("href") button.setAttribute("aria-expanded", "true")
data- attributesel.dataset.userIdReads data-user-id. Always a string
Add, remove or flip a classel.classList.add("open") el.classList.remove("open") el.classList.toggle("open")
Has a classel.classList.contains("open")
Set a styleel.style.backgroundColor = "gold";camelCase names. Toggling a class is usually cleaner
Hide and showel.hidden = true;
Read a form fieldinput.value checkbox.checkedvalue is always a string
Create an elementconst li = document.createElement("li");
Add it to the pagelist.append(li)Also prepend, before and after. append takes text too
Remove an elementli.remove()
Replace everything insidelist.replaceChildren(...items)With no arguments, empties it
Listen for a clickbutton.addEventListener("click", (event) => {});
Listen only oncebutton.addEventListener("click", start, { once: true });
Stop the browser's default actionevent.preventDefault();Stops a form submitting or a link navigating
One listener for many childrenlist.addEventListener("click", (e) => { const item = e.target.closest("li"); });Event delegation. Also covers items added later
Stop listeningbutton.removeEventListener("click", start);Needs the same function, so an inline arrow cannot be removed
Read a whole formObject.fromEntries(new FormData(form))Uses each field's name attribute. Values are strings
Run code once the HTML is parseddocument.addEventListener("DOMContentLoaded", init);Not needed with defer or type="module"
Save a value in the browserlocalStorage.setItem("theme", "dark") localStorage.getItem("theme")Strings only. JSON.stringify an object first
Read the query stringnew URLSearchParams(location.search).get("q")null when missing
Scroll to an elementel.scrollIntoView({ behavior: "smooth" })
Copy text to the clipboardawait navigator.clipboard.writeText(text);HTTPS or localhost only, and usually only in response to a click
Run code before the next repaintrequestAnimationFrame(draw);For animation. Runs once per screen refresh, usually 60 times a second

Array methods, start to finish

Most of the arrays table in one place: filter, map and reduce chained together, grouping, sorting a copy, and destructuring the result.

const orders = [
  { id: 1, customer: "Ada", total: 42.5, status: "paid" },
  { id: 2, customer: "Grace", total: 18, status: "refunded" },
  { id: 3, customer: "Ada", total: 7.25, status: "paid" },
  { id: 4, customer: "Linus", total: 99.99, status: "paid" },
];
 
const paid = orders.filter((o) => o.status === "paid");
 
const revenue = paid.reduce((sum, o) => sum + o.total, 0);
console.log(revenue.toFixed(2)); // 149.74
 
const customers = [...new Set(paid.map((o) => o.customer))];
console.log(customers); // [ 'Ada', 'Linus' ]
 
const byCustomer = Object.groupBy(paid, (o) => o.customer);
console.log(byCustomer.Ada.length); // 2
 
const biggestFirst = paid.toSorted((a, b) => b.total - a.total);
const [top, ...others] = biggestFirst;
console.log(top.id, others.length); // 4 2
console.log(paid[0].id); // 1: toSorted left paid alone
 
const labels = paid.map(({ id, total }) => `#${id}: £${total.toFixed(2)}`);
console.log(labels.join(", ")); // #1: £42.50, #3: £7.25, #4: £99.99

The parameter list ({ id, total }) destructures each order as it arrives, so the arrow body never has to write o.. Object.groupBy needs ES2024; on older browsers the same result is a reduce that pushes each order into groups[o.customer].

Array.prototype.sort is required to be stable, so orders with the same total keep their original order, which is what makes sorting by one field and then another work. V8, the engine in Chrome and Node.js, uses Timsort, a mix of merge sort and insertion sort. Both are on the site as step-through visualisations.

this, start to finish

this is set by how a function is called, not where it is written. This is the bug almost everyone meets in their first week, and the three usual fixes.

"use strict";
 
const timer = {
  seconds: 0,
  tick() {
    this.seconds += 1;
    return this.seconds;
  },
};
 
console.log(timer.tick()); // 1: called as timer.tick(), so this is timer
 
const tick = timer.tick;
try {
  tick(); // called on its own, so this is undefined
} catch (err) {
  console.log(err.constructor.name); // TypeError
}
 
// Fix 1: an arrow function, which calls the method on timer itself
const viaArrow = () => timer.tick();
console.log(viaArrow()); // 2
 
// Fix 2: bind, which makes a copy with this fixed
const bound = timer.tick.bind(timer);
console.log(bound()); // 3
 
// Fix 3: an arrow inside the method, which uses the method's own this
const clock = {
  seconds: 0,
  startCounting(times) {
    [...Array(times)].forEach(() => {
      this.seconds += 1; // the arrow has no this of its own, so it is clock
    });
    return this.seconds;
  },
};
console.log(clock.startCounting(3)); // 3

Swap the arrow in startCounting for function () { ... } and it throws a TypeError, because a plain function passed as a callback is called on its own. Passing timer.tick to setTimeout or addEventListener loses this the same way: the caller decides what this is, and it is not timer.

A class with private fields

Most of the classes and errors tables in one place: a private field, a getter, a static method, a subclass and a custom error.

class InsufficientFundsError extends Error {
  name = "InsufficientFundsError";
}
 
class Account {
  #balance = 0;
  static count = 0;
 
  constructor(owner) {
    this.owner = owner;
    Account.count += 1;
  }
 
  get balance() {
    return this.#balance;
  }
 
  deposit(amount) {
    this.#check(amount);
    this.#balance += amount;
    return this;
  }
 
  withdraw(amount) {
    this.#check(amount);
    if (amount > this.#balance) {
      throw new InsufficientFundsError(`${this.owner} has only ${this.#balance}`);
    }
    this.#balance -= amount;
    return this;
  }
 
  #check(amount) {
    if (!Number.isFinite(amount) || amount <= 0) {
      throw new RangeError(`amount must be a positive number, got ${amount}`);
    }
  }
 
  static isAccount(obj) {
    return #balance in obj;
  }
 
  toString() {
    return `${this.owner}: ${this.#balance}`;
  }
}
 
class SavingsAccount extends Account {
  constructor(owner, rate) {
    super(owner);
    this.rate = rate;
  }
 
  addInterest() {
    return this.deposit(Math.round(this.balance * this.rate));
  }
}
 
const savings = new SavingsAccount("Ada", 0.05).deposit(200).addInterest();
console.log(`${savings}`); // Ada: 210
console.log(savings.balance, Account.count); // 210 1
console.log(Account.isAccount(savings), Account.isAccount({})); // true false
 
try {
  savings.withdraw(500);
} catch (err) {
  if (err instanceof InsufficientFundsError) {
    console.log(err.name, "-", err.message); // InsufficientFundsError - Ada has only 210
  } else {
    throw err;
  }
}

savings.#balance outside the class is a SyntaxError before the code even runs, and savings.balance = 0 fails because the getter has no setter: silently in a plain script, and with a TypeError in a module or strict code. Returning this from deposit is what lets the calls chain.

async and await with fetch, start to finish

A small helper that does the three things every fetch call needs: check the status, time out, and parse the JSON. Then two ways of running several requests.

async function getJson(url, { timeoutMs = 5000 } = {}) {
  const res = await fetch(url, { signal: AbortSignal.timeout(timeoutMs) });
  if (!res.ok) {
    throw new Error(`HTTP ${res.status} from ${url}`);
  }
  return res.json();
}
 
const api = "https://api.github.com/repos";
 
// One request
try {
  const repo = await getJson(`${api}/nodejs/node`);
  console.log(repo.full_name); // nodejs/node
} catch (err) {
  console.error("Could not load the repo:", err.message);
}
 
// Several at once: all must succeed
const names = ["nodejs/node", "denoland/deno"];
const repos = await Promise.all(names.map((name) => getJson(`${api}/${name}`)));
console.log(repos.map((r) => r.full_name)); // [ 'nodejs/node', 'denoland/deno' ]
 
// Several at once: keep whatever succeeded
const results = await Promise.allSettled(
  ["nodejs/node", "no-such-owner/no-such-repo"].map((name) => getJson(`${api}/${name}`))
);
for (const result of results) {
  if (result.status === "fulfilled") {
    console.log("ok", result.value.full_name); // ok nodejs/node
  } else {
    console.log("failed", result.reason.message); // failed HTTP 404 from https://api.github.com/...
  }
}

This runs as a module, where await works at the top level: save it as repos.mjs and run node repos.mjs, or load it with type="module" in a page. names.map(...) starts every request before the first await, so they run in parallel. An await inside a for...of loop would run them one after another. To check or tidy a JSON response by hand, paste it into the JSON formatter.

DOM basics, start to finish

A to-do list: one form, one list, one click listener for every item, and the list saved in localStorage so it survives a reload.

<form id="new-todo">
  <input name="title" required placeholder="What needs doing?" />
  <button>Add</button>
</form>
<ul id="todos"></ul>
 
<script type="module">
  const form = document.querySelector("#new-todo");
  const list = document.querySelector("#todos");
  const saved = JSON.parse(localStorage.getItem("todos") ?? "[]");
 
  function render(todos) {
    list.replaceChildren(
      ...todos.map((todo, index) => {
        const li = document.createElement("li");
        li.textContent = todo.title;
        li.dataset.index = index;
        li.classList.toggle("done", todo.done);
        return li;
      })
    );
    localStorage.setItem("todos", JSON.stringify(todos));
  }
 
  form.addEventListener("submit", (event) => {
    event.preventDefault();
    const { title } = Object.fromEntries(new FormData(form));
    saved.push({ title: title.trim(), done: false });
    render(saved);
    form.reset();
  });
 
  list.addEventListener("click", (event) => {
    const li = event.target.closest("li");
    if (!li) return;
    const todo = saved[Number(li.dataset.index)];
    todo.done = !todo.done;
    render(saved);
  });
 
  render(saved);
</script>

textContent rather than innerHTML is what makes it safe to show whatever someone typed. The single listener on the ul handles clicks for items that did not exist when it was added, which is the point of event delegation. preventDefault stops the form reloading the page. For the markup side, from labelling that input to the form attributes and input types these scripts read, see the HTML cheat sheet.

Gotchas

The mistakes almost everyone makes in their first month of JavaScript.

Looks rightWhat actually happensDo this instead
if (input == 0)Also true for "", "0" and []===, and x == null only for null or undefined
typeof value === "object"Also true for null and arraysvalue !== null && typeof value === "object", or Array.isArray
[10, 9, 1].sort()[1, 10, 9]: sorted as strings.sort((a, b) => a - b)
0.1 + 0.2 === 0.3false: the sum is 0.30000000000000004Compare with a tolerance, keep money in pennies
x === NaNAlways false, even when x is NaNNumber.isNaN(x)
"5" + 3"53": joined as stringsNumber("5") + 3
["1", "2", "3"].map(parseInt)[1, NaN, NaN]: the index is passed as the base.map(Number)
setTimeout(obj.method, 100)this is lost inside the methodsetTimeout(() => obj.method(), 100)
items.forEach(async (i) => await save(i))Does not wait: the code after it runs firstfor (const i of items) await save(i), or Promise.all
const make = () => { name: "Ada" }Returns undefined: the braces are a function body() => ({ name: "Ada" })
return on its own line, value belowReturns undefined: a semicolon is inserted after returnStart the value on the same line as return
new Date(2026, 9, 27)27 October: months count from 0new Date(2026, 8, 27) for September
{ a: 1 } === { a: 1 }false: two different objectsCompare the fields, or JSON.stringify for simple data
const list = []; list.push(1)Works: const stops reassignment, not changesObject.freeze if it really must not change
for (var i = 0; i < 3; i++) setTimeout(() => console.log(i))Prints 3 three timeslet i, which makes a new i for each pass
await fetch(url) on a 404Resolves, no errorCheck res.ok and throw

Common questions

Which version of JavaScript does this cheat sheet cover?

ECMAScript 2025, the current edition of the standard, with every snippet run on Node.js 24. Anything added after ES2020 names its edition in the notes column. Most of it has worked in every major browser for years; the newest rows, such as RegExp.escape, the Set methods, Promise.try and JSON imports from ES2025, need a browser from 2025 or later.

What is the difference between == and === in JavaScript?

=== checks that two values are the same type and the same value, with no conversion. == converts the types first and then compares, which gives results like 0 == "" being true and "1" == 1 being true. Use === and !== everywhere. The one common exception is x == null, which is true for both null and undefined and nothing else.

Should I use let, const or var?

Use const by default and let when you need to reassign the variable. Avoid var in new code. const only stops the name being pointed at something else; the object or array it holds can still change. var is scoped to the whole function rather than the block, and can be used before the line that declares it, which causes bugs that let and const turn into errors.

How does this work in JavaScript?

In a normal function or method, this is set by how the function is called, not where it is written. Called as user.greet(), this is user. Pass the same method somewhere as a callback, like setTimeout(user.greet, 100), and it is no longer called on user, so this is not user either: it is undefined in strict code, or whatever the caller picks. Arrow functions have no this of their own and use the this of the surrounding code, which is why they fix most callback problems. bind(obj) makes a copy of a function with this fixed.

What is the difference between an arrow function and a normal function?

An arrow function is shorter, returns a single expression without the return keyword, and has no this, arguments or super of its own: it uses the ones from the code around it. It also cannot be called with new. Use arrows for callbacks and short helpers. Use a normal function or method syntax for object methods that need this, and for functions you want to call above the line that defines them.

What is the difference between null and undefined?

undefined means a value was never set: a variable declared without a value, a missing object property, a missing function argument, or a function with no return. null is a value you set on purpose to say there is nothing here. Both are falsy, x == null catches both, and the ?? operator treats both as missing.

Should I use async/await or .then()?

They do the same thing, since await works on promises. async/await reads top to bottom like ordinary code and uses try and catch for errors, so it is the usual choice for new code. then chains still turn up in older code and are handy for a single quick transformation. Either way, run independent work in parallel with Promise.all rather than awaiting each item in turn.

Is JavaScript the same as Java?

No. They are unrelated languages that share part of a name for marketing reasons dating back to 1995. JavaScript is dynamically typed and runs in every web browser and, through Node.js, on servers. Java is a statically typed, compiled language that runs on the Java Virtual Machine. The official name of the JavaScript standard is ECMAScript.

Why is 0.1 + 0.2 not equal to 0.3 in JavaScript?

Every JavaScript number is a 64-bit binary float, and 0.1 and 0.2 have no exact binary form, so the sum is 0.30000000000000004. Python, Java and C# do the same with their floats. Round when you display a value with toFixed or Intl.NumberFormat, compare with a tolerance such as Math.abs(a - b) < Number.EPSILON, and keep money in whole pennies or cents.

See all cheat sheets

Want this explained by a cat?

The videos cover the same ground in sixty seconds. If there is a tool you want a cheat sheet for next, ask.