JavaScript: Syntax Cheatsheet and Practice Quiz
Twelve syntax patterns behind most JavaScript assessment questions: arrays, objects, closures, promises and the event loop, with a 20-question practice quiz.
Syntax cheatsheet
12 patterns. Pick one on the map for its syntax, when to use it and the trap behind it.
Basics
const, let and var
Scope and reassignment
- Syntax
const x = 10; // no reassignment (an object inside can still change) let y = 20; // block-scoped, reassignable var z = 30; // function-scoped, hoisted as undefined: avoid- When to use it
- Default to const; switch to let only when the binding must be reassigned (counters, loop indexes). Never var in new code.
- What it does
- const and let are block-scoped and sit in the temporal dead zone until their line runs. var is function-scoped and hoisted to the top of the function with the value undefined. Function declarations hoist whole, so they can be called before their line.
- Watch out
- const freezes the binding, not the value: const user = {} still allows user.name = "x". Reading a let before its declaration throws a ReferenceError; reading a var gives undefined.
- Example
- console.log(a); // undefined (var is hoisted) var a = 1; console.log(b); // ReferenceError (temporal dead zone) let b = 2; hello(); // works: function declarations hoist whole function hello() {}
Basics
=== and truthy / falsy
Strict comparison
- Syntax
5 === "5" // false (value AND type) 5 == "5" // true (coerces: avoid) // falsy: false, 0, -0, "", null, undefined, NaN // truthy: everything else, including [], {}, "0", "false"- When to use it
- Every comparison. The one accepted use of == is x == null, which matches both null and undefined.
- What it does
- === compares value and type without coercion. Boolean contexts (if, &&, ||, !) convert the value using the falsy list: seven falsy values, everything else is truthy.
- Watch out
- An empty array and an empty object are truthy, so if (arr) does not test emptiness: use arr.length === 0. NaN !== NaN, so use Number.isNaN(). [] == false is true because of coercion.
- Example
- [] == false // true (coercion) [] === false // false if (arr.length === 0) {} // the right emptiness check Number.isNaN(NaN) // true null == undefined // true; null === undefined // false
Arrays
map, filter, reduce
Transform, keep, fold
- Syntax
nums.map(x => x * 2) // same length, new values nums.filter(x => x % 2 === 0) // a subset nums.reduce((acc, x) => acc + x, 0) // one value- When to use it
- map to transform every element, filter to keep a subset, reduce to produce one value: a sum, a lookup object, grouped data.
- What it does
- All three return a new array or value and leave the original untouched, so they chain top to bottom like a pipeline. The callback receives (element, index, array).
- Watch out
- Always pass reduce an initial value: without it the first element becomes the accumulator and an empty array throws. A map callback with braces and no return gives an array of undefined. forEach returns undefined, so it cannot be chained.
- Example
- const total = orders .filter(o => o.status === 'completed') .map(o => o.amount) .reduce((sum, a) => sum + a, 0); const byId = users.reduce((acc, u) => { acc[u.id] = u; return acc; }, {});
Arrays
find, some, every, sort
Search and order
- Syntax
users.find(u => u.id === 5) // first match or undefined users.some(u => u.age >= 18) // at least one → boolean users.every(u => u.age >= 18) // all → boolean [1, 2, 3].includes(2) // true nums.sort((a, b) => a - b) // ascending numbers (in place)- When to use it
- find for one item, some and every for yes/no checks, includes for primitives, sort with a comparator for anything numeric or by field.
- What it does
- find and some stop at the first hit; every stops at the first miss. sort reorders the array in place using the sign of the comparator: negative keeps a before b.
- Watch out
- sort() without a comparator sorts as strings (10 before 9) and it mutates: copy first with [...nums].sort() or use toSorted(). findIndex returns -1 when nothing matches, find returns undefined.
- Example
- const sorted = [...users].sort((a, b) => a.age - b.age); // original untouched const desc = [...nums].sort((a, b) => b - a); users.sort((a, b) => a.name.localeCompare(b.name)); [10, 9, 1].sort() // [1, 10, 9] (string order!)
Objects
Spread and destructuring
Copy, merge, unpack
- Syntax
const b = [...a, 3, 4]; // array copy + append const updated = { ...user, age: 34 }; // object copy + override const { name, age = 0 } = user; // unpack with a default const [first, , third] = list; // skip a position const { id, ...rest } = obj; // rest must be last function User({ name, age }) {} // destructured parameter- When to use it
- Immutable updates (React state), merging defaults with options, pulling fields out of API responses and props.
- What it does
- Spread copies own enumerable properties or iterable items into a new array or object; later keys override earlier ones. Destructuring binds variables to positions or keys in one line.
- Watch out
- Spread is shallow: nested objects are still shared references. Rest must be the last element. Destructuring a missing key gives undefined unless you add a default.
- Example
- const defaults = { theme: 'light', lang: 'en' }; const opts = { ...defaults, lang: 'es' }; // { theme: 'light', lang: 'es' } const { data: { items = [] } = {} } = response; // nested with defaults const [x, y] = [y, x]; // swap
Objects
?. and ??
Safe access, real defaults
- Syntax
user?.address?.city // undefined instead of a TypeError user.getName?.() // call only if it exists items?.[0] const name = user.name ?? 'Unknown'; // only null / undefined fall back count ??= 10; // assign only when null / undefined- When to use it
- Reading nested API data that may be missing; defaults for values where 0, "" or false are legitimate.
- What it does
- ?. short-circuits to undefined when the left side is null or undefined. ?? returns the right side only for null or undefined, unlike ||, which also replaces 0, "" and false.
- Watch out
- 0 || 10 is 10 but 0 ?? 10 is 0. Optional chaining does not catch errors thrown inside the call, only a missing receiver. Mixing ?? with || or && needs parentheses.
- Example
- const port = config.port ?? 3000; // keeps an explicit 0 const label = config.label || 'n/a'; // '' becomes 'n/a' const city = order?.customer?.address?.city ?? '—';
Objects
Object.keys, values, entries
Iterate objects
- Syntax
Object.keys(obj) // ['a', 'b'] Object.values(obj) // [1, 2] Object.entries(obj) // [['a', 1], ['b', 2]] Object.fromEntries(pairs) Object.hasOwn(obj, 'a')- When to use it
- Looping over a lookup object, converting between arrays and objects, filtering or mapping an object's fields.
- What it does
- The three accessors return arrays of own enumerable string keys in insertion order, so every array method becomes available; fromEntries reverses entries.
- Watch out
- Keys are strings even for numeric-looking properties. for...in also walks inherited properties: prefer Object.keys. Use Object.hasOwn instead of obj.hasOwnProperty.
- Example
- const prices = { a: 10, b: 25, c: 7 }; const expensive = Object.fromEntries( Object.entries(prices).filter(([, p]) => p > 9) ); // { a: 10, b: 25 } for (const [k, v] of Object.entries(prices)) console.log(k, v);
Collections
Map and Set
Counting and dedupe
- Syntax
const freq = new Map(); for (const x of arr) freq.set(x, (freq.get(x) ?? 0) + 1); freq.has(x); freq.size; [...freq.entries()] const unique = [...new Set(arr)];- When to use it
- Frequency counts, O(1) lookups by any key type, removing duplicates, caching results, detecting duplicates in one pass.
- What it does
- Map keeps insertion order, allows object keys and has O(1) average get, set and has. Set stores unique values with the same operations. Both are iterable with for...of.
- Watch out
- Map.get on a missing key returns undefined, hence the ?? 0. Object keys are compared by reference, not by shape, so Set does not dedupe objects with equal contents. A plain object also works as a counter but turns keys into strings.
- Example
- const count = {}; for (const w of words) count[w] = (count[w] || 0) + 1; const hasDuplicate = new Set(nums).size !== nums.length; const top = [...freq.entries()].sort((a, b) => b[1] - a[1])[0]; // most frequent
Functions
Closures, this and debounce
Functions that remember
- Syntax
function counter() { let count = 0; return () => ++count; // remembers count } const inc = counter(); inc(); inc(); // 2 function debounce(fn, delay) { let timer; return (...args) => { clearTimeout(timer); timer = setTimeout(() => fn(...args), delay); }; }- When to use it
- Private state without classes, memoisation, handlers that need context, search boxes (debounce) and scroll or resize handlers (throttle).
- What it does
- An inner function keeps a live reference to its outer scope after the outer function has returned. Arrow functions take this from the surrounding scope; regular functions get this from how they are called.
- Watch out
- Debounce fires once after the user stops; throttle fires at most once per interval. A var in a loop shares one binding across all closures; let creates one per iteration.
- Example
- function throttle(fn, delay) { let last = 0; return (...args) => { const now = Date.now(); if (now - last >= delay) { last = now; fn(...args); } }; } const search = debounce(q => fetch('/api?q=' + q), 300); // one request after typing stops
Async
Promises and async / await
Waiting without blocking
- Syntax
async function getUsers() { try { const res = await fetch('/api/users'); if (!res.ok) throw new Error('Request failed'); return await res.json(); } catch (err) { console.error(err); } } const [users, posts] = await Promise.all([getUsers(), getPosts()]);- When to use it
- Any I/O: HTTP calls, timers, file reads. Promise.all for independent requests, allSettled when some may fail, race for timeouts, any for the first success.
- What it does
- await pauses the async function, not the thread, and unwraps the promise. An async function always returns a promise; a thrown error becomes a rejection that .catch or try/catch receives.
- Watch out
- fetch only rejects on network failure, so check res.ok for 404 and 500. Promise.all rejects as soon as one promise rejects. await inside a for loop runs sequentially; collect promises and Promise.all them to run in parallel.
- Example
- Promise.resolve(1).then(x => x + 1) // resolves to 2 const results = await Promise.allSettled(tasks); const ok = results.filter(r => r.status === 'fulfilled').map(r => r.value); await Promise.race([fetchData(), timeout(5000)]);
Async
Event loop order
Sync, microtasks, macrotasks
- Syntax
console.log('A'); setTimeout(() => console.log('B'), 0); // macrotask Promise.resolve().then(() => console.log('C')); // microtask console.log('D'); // A D C B- When to use it
- Every "what is printed?" question, and every bug where a callback runs later than expected.
- What it does
- The call stack runs synchronous code to the end, then all queued microtasks (promise callbacks, queueMicrotask), then one macrotask (setTimeout, setInterval, I/O), then the microtasks again, and so on.
- Watch out
- setTimeout(fn, 0) is never immediate. An await splits the function: the code after it is a microtask. A long synchronous loop blocks everything, including rendering and timers.
- Example
- async function f() { console.log(1); await null; console.log(3); } f(); console.log(2); // 1 2 3
Patterns
Two pointers, sliding window, Big-O
From O(n²) to O(n)
- Syntax
let l = 0, r = arr.length - 1; // two pointers while (l < r) { /* compare arr[l], arr[r] */ l++; r--; } let left = 0; // sliding window for (let right = 0; right < arr.length; right++) { while (/* window invalid */) left++; // arr[left..right] is a valid window }- When to use it
- Pairs in a sorted array, palindromes, longest substring without repeats, max sum of k elements, duplicates in O(n).
- What it does
- Two pointers move inward or in step over sorted data. A sliding window keeps a valid range and only moves its ends forward. A Set or Map turns a nested-loop search into one pass.
- Watch out
- Array index, push, pop and Map/Set operations are O(1); shift and unshift are O(n); sorting is O(n log n). Use a head index instead of shift() for a queue.
- Example
- function isPalindrome(s) { const t = s.toLowerCase().replace(/[^a-z0-9]/g, ''); let l = 0, r = t.length - 1; while (l < r) { if (t[l++] !== t[r--]) return false; } return true; } const seen = new Set(); const firstDup = nums.find(n => seen.has(n) || (seen.add(n), false));
Pick a pattern on the map to see its syntax, when to use it, what it does and what to watch out for.
Practice quiz
20 multiple-choice questions drawn at random from the 40 in this track, one per screen, graded at the end. Every question points back to a pattern on the map.
Pick an answer, move freely between the questions and finish whenever you like: the score and the explanation of every question appear at the end.
- 20 questions
- No time limit
- Graded at the end
The quiz needs JavaScript. The cheatsheet above works without it.
Practice material for timed coding assessments (HackerRank, Codility, TestGorilla and similar). Behaviour described follows the ECMAScript specification as implemented in current browsers and Node.js.