Scriptdrill

Scriptdrill / Cheat sheet

JavaScript cheat sheet

The modern JavaScript you reach for every day, on two pages: what each thing does, the exact code, the gotcha, and a drill with hidden tests for most of them. ES2023 syntax; works in Node 18+ and every current browser. Print the page or get the PDF by email.

No newsletter spam: the PDF, one follow-up with the free exercises, and nothing else unless you ask.

The ones worth learning first

WhatHow
Transform a listitems.map(x => x.price * x.qty)
Keep someitems.filter(x => x.stock > 0)
Reduce to one valueitems.reduce((sum, x) => sum + x.total, 0)
Find oneusers.find(u => u.id === id)
Sort without mutating[...arr].sort((a, b) => a.age - b.age) or arr.toSorted(...)
Group byObject.groupBy(items, x => x.category)
Destructureconst { name, tags = [] } = user; const [first, ...rest] = list;
Spread and copyconst copy = { ...obj, status: 'done' };
Optional chaininguser?.address?.city ?? 'unknown'
Await in parallelconst [a, b] = await Promise.all([fa(), fb()]);

Arrays: transform

map, filter, reducearr.map(fn) arr.filter(fn) arr.reduce((acc, x) => acc + x, 0) drill
Filter by a fielditems.filter(i => i.stock > 0) drill
Map to stringspeople.map(p => `${p.first} ${p.last}`) drill
every / sometags.every(t => item.tags.includes(t)) arr.some(fn) drill
find / findIndex / findLastarr.find(fn) arr.findIndex(fn) arr.findLast(fn)
includes / indexOfarr.includes(x) arr.indexOf(x)
flat / flatMapnested.flat() arr.flatMap(x => [x, x * 2]) drill
Sort numbersarr.sort((a, b) => a - b) (default sort is lexicographic)
Sort by several keysarr.sort((a, b) => a.dept.localeCompare(b.dept) || b.salary - a.salary) drill
Top n with tiessorted.filter(x => x.score >= sorted[n - 1].score) drill
Non-mutating versionstoSorted, toReversed, toSpliced, with (ES2023)
RangeArray.from({ length: n }, (_, i) => i + start) drill
Chunkfor (let i = 0; i < arr.length; i += size) out.push(arr.slice(i, i + size)) drill
Zipa.map((x, i) => [x, b[i]]) drill
Running totalslet s = 0; arr.map(x => (s += x)) drill
Unique[...new Set(arr)]
Unique by a field[...new Map(arr.map(x => [x.id, x])).values()] drill
Group byObject.groupBy(arr, x => x.type) or arr.reduce((g, x) => ((g[x.type] ||= []).push(x), g), {}) drill
Count byarr.reduce((c, x) => ((c[x.type] = (c[x.type] || 0) + 1), c), {}) drill
Totals per keyorders.reduce((t, o) => ((t[o.customer] = (t[o.customer] || 0) + o.total), t), {}) drill
Pivot to a lookupObject.fromEntries(rows.map(r => [r.id, r])) drill
Left joina.map(x => ({ ...x, ...(bById.get(x.id) || {}) })) drill
atarr.at(-1) (last element)

Objects

Keys, values, entriesObject.keys(o) Object.values(o) Object.entries(o)
From entriesObject.fromEntries(entries)
Copy and override{ ...o, status: 'done' }
Pick keysObject.fromEntries(keys.filter(k => k in o).map(k => [k, o[k]])) drill
Omit a keyconst { password, ...safe } = user;
Deep mergerecursive: for each key, if both are plain objects merge, else take the override drill
Read a nested pathpath.split('.').reduce((o, k) => o?.[k], obj) drill
Flatten nestedrecurse with prefix: out[`${prefix}${k}`] = v drill
Deep equalsame type, same keys, recurse on values; arrays by index drill
Deep clonestructuredClone(obj) (not JSON.parse(JSON.stringify) which drops dates and undefined)
FreezeObject.freeze(o) (shallow)
Optional chaining and nullisho?.a?.b ?? fallback (?? only for null/undefined, || also for 0 and '')
Logical assignmento.list ||= [] o.n ??= 0 o.flag &&= false
Computed key{ [key]: value }
Map and Setnew Map([[k, v]]) m.get(k) new Set(arr) s.has(x)

Strings

Template literal`Hello ${name}, you have ${n} items`
Split, join, reverse wordss.split(' ').reverse().join(' ') drill
Capitalize each words.replace(/\b\w/g, c => c.toUpperCase()) drill
Count matches(s.match(/[aeiou]/gi) || []).length drill
Trim, pads.trim() s.padStart(6, '0') s.padEnd(10)
Slugifys.toLowerCase().trim().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '') drill
Truncate with ellipsiss.length > n ? s.slice(0, n - 1) + '…' : s drill
Format moneynew Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(n) drill
Format a numbern.toLocaleString('en-US', { maximumFractionDigits: 2 })
Parse a query stringObject.fromEntries(new URLSearchParams(qs)) drill
Align columnscells.map((c, i) => c.padEnd(widths[i])).join(' ') drill
CSV to objectslines.slice(1).map(l => Object.fromEntries(l.split(',').map((v, i) => [header[i], v]))) drill
Starts, ends, includess.startsWith('a') s.endsWith('.js') s.includes('x')
replaceAlls.replaceAll('-', ' ') or s.replace(/-/g, ' ')
Regex groupsconst m = s.match(/(\d{4})-(\d{2})/); m[1] /(?<y>\d{4})/.exec(s).groups.y
Character code'A'.charCodeAt(0) String.fromCharCode(65)

Numbers and dates

ClampMath.min(Math.max(n, lo), hi) drill
Round to 2 decimalsMath.round(n * 100) / 100 (toFixed returns a string)
ParseNumber('42') parseInt('42px', 10) parseFloat('3.5kg')
CheckNumber.isInteger(n) Number.isNaN(x) Number.isFinite(x)
Random integerMath.floor(Math.random() * (max - min + 1)) + min
Nownew Date() Date.now() (ms)
Add daysconst d = new Date(date); d.setDate(d.getDate() + n) drill
Days betweenMath.round((b - a) / 86400000) drill
Last day of the monthnew Date(y, m + 1, 0).getDate() drill
ISO date stringd.toISOString().slice(0, 10)
Format a datenew Intl.DateTimeFormat('en-US', { dateStyle: 'medium' }).format(d)
Format a duration`${Math.floor(s / 3600)}h ${Math.floor(s % 3600 / 60)}m` drill
Month index gotchagetMonth() is 0-based; new Date('2026-01-05') is UTC midnight.

Functions and async

Arrow functionconst add = (a, b) => a + b; const f = x => ({ x }); (parentheses for an object)
Default and rest paramsfunction f(a, b = 2, ...rest) {}
Closure counterconst counter = () => { let n = 0; return () => ++n; };
Debouncelet t; return (...a) => { clearTimeout(t); t = setTimeout(() => fn(...a), ms); } drill
Throttlelet last = 0; return (...a) => { if (Date.now() - last >= ms) { last = Date.now(); fn(...a); } }
Memoizeconst cache = new Map(); return x => cache.has(x) ? cache.get(x) : (cache.set(x, fn(x)), cache.get(x)) drill
Oncelet done, r; return (...a) => done ? r : ((done = true), (r = fn(...a))) drill
Pipeconst pipe = (...fns) => x => fns.reduce((v, f) => f(v), x) drill
Sleepconst sleep = ms => new Promise(r => setTimeout(r, ms)); drill
Async / awaitasync function load() { const r = await fetch(url); return r.json(); }
Parallelconst [a, b] = await Promise.all([fa(), fb()]);
All settledconst results = await Promise.allSettled(promises);
First to finishPromise.race([p, timeout]) drill
In sequencefor (const t of tasks) await t(); (not forEach with async) drill
Concurrency limitworkers = Array.from({ length: limit }, async () => { while (i < tasks.length) await tasks[i++](); }) drill
Retryfor (let n = 0; ; n++) { try { return await fn(); } catch (e) { if (n >= max) throw e; await sleep(delay); } } drill
Timeout wrapperPromise.race([p, new Promise((_, rej) => setTimeout(() => rej(new Error('timeout')), ms))]) drill
try / catch / finallytry { ... } catch (e) { ... } finally { ... }
Custom errorclass NotFound extends Error { constructor(m) { super(m); this.name = 'NotFound'; } }

Gotchas

== vs ===Always ===. '1' == 1 is true, '1' === 1 is false.
Falsy valuesfalse, 0, -0, 0n, '', null, undefined, NaN
sort mutatesarr.sort() changes arr; use toSorted or copy first
const and objectsconst prevents reassignment, not mutation
this in arrowsArrow functions take this from the enclosing scope
forEach and awaitforEach ignores returned promises; use for...of
Floating point0.1 + 0.2 !== 0.3; compare with a tolerance or work in cents
typeof null'object'. Use x === null.
JSON and datesJSON.stringify turns dates into strings; parse them back
Array copy[...arr] and arr.slice() are shallow

scriptdrill.com: practice every one of these with instant checking. 96 entries.

Questions

What JavaScript comes up in coding interviews?

Array transforms (map, filter, reduce), group by and count by, deduplication, sorting by several keys, deep equal and deep clone, flatten, debounce and throttle, memoize, Promise.all with a concurrency limit, retry with backoff, and the closure questions. Every one of them has a drill here.

map, forEach or for...of?

map when you want a new array of the same length; filter and reduce for subsets and totals; for...of when you need await inside the loop or an early break; forEach only for side effects with no await.

When do I need reduce?

When the result is not an array of the same shape: a total, a lookup object, a grouping. If you find yourself pushing to an outer array inside reduce, map or filter is clearer.

Why is my async forEach not waiting?

forEach calls the callback and ignores the returned promise, so the loop finishes before any await resolves. Use for...of for sequential work or Promise.all(arr.map(async ...)) for parallel work.

A cheat sheet does not make them stick

Doing them with instant checking does. The free exercises need no account.

Start practicing