๐ŸŸจ

JavaScript Cheat Sheet

The only language that runs natively in every browser - the backbone of the modern web, and increasingly servers too (Node.js). Copy-paste examples plus live Run/Submit exercises, running instantly in your own browser.

Jump to: Your first programVariablesData types Numbers & mathStringsComparison & logic Ternary & defaultsif / elseLoops break & continueArraysArray methods ObjectsFunctionsArrow functions ClosuresTimersTemplate literals DestructuringThe DOMEvents JSONlocalStorageModules Classes & OOPError handlingPromises async / await & fetch ๐Ÿš€ Practice problems

Your first program

console.log("Hello, world!");
๐Ÿ“ In the browser, JavaScript also runs in the Console tab of DevTools (F12). No compiling - it just runs.

Variables

let age = 25;        // can be reassigned
age = 26;
const name = "Sam";  // cannot be reassigned
// avoid "var" - it has confusing scoping rules from old JS

Data types

let price = 9.99;         // number (no separate int/float)
let name = "Mo";          // string
let isValid = true;      // boolean
let nothing = null;      // intentionally empty
let notSet;               // undefined - declared but never assigned
console.log(typeof price);  // "number"

Numbers & math

JavaScript has only one number type - no separate int/float. That makes decimal math a little dangerous for anything involving money.

console.log(0.1 + 0.2);         // 0.30000000000000004 - binary floating-point can't store 0.1 exactly
console.log(0.1 + 0.2 === 0.3); // false!

// industry-standard fix: do money math in CENTS (integers), convert to dollars at the very end
const priceCents = 1090 + 2095;
const dollars = Math.round(priceCents) / 100;
console.log(dollars.toFixed(2));   // "31.85"
โš ๏ธ Never do money math directly in dollars/floats - rounding errors compound across many operations. Store amounts as whole-number cents and only divide by 100 for display.

Strings

const a = 'single quotes';        // preferred default
const b = "double, to avoid escaping";
const c = 'I\'m learning JS';    // \' escapes the quote inside

const qty = 3, total = 2095;
console.log(`Items (${qty}): $${(total / 100).toFixed(2)}`);  // template literal, multi-line capable

console.log('2.95' + 20.95);   // "2.9520.95" - string + number CONCATENATES, doesn't add!
โš ๏ธ + between a string and a number coerces the number to text instead of doing math - when building a template literal with math inside, wrap the numeric expression in parentheses first (as in the example above) so it evaluates before becoming part of the string.

Comparison & logical operators

console.log(5 === '5');   // false - === checks type AND value
console.log(5 == '5');    // true - == silently converts types first
// always use === / !== - never == / != - to avoid surprise type coercion

if (age >= 18 && hasId) { }   // AND - both must be true
if (isVip || total > 100) { }   // OR - at least one true, short-circuits
if (!isBanned) { }              // NOT
๐Ÿ“ Falsy values (treated as false in a condition): false, 0, '', NaN, undefined, null. Everything else - including "0" and empty arrays/objects - is truthy.

Ternary & default/guard patterns

const label = age >= 18 ? 'Adult' : 'Minor';   // condition ? ifTrue : ifFalse

// "default" pattern - || falls through to a fallback when the left side is falsy
const currency = selectedCurrency || 'USD';

// "guard" pattern - short-circuits out before running the right-hand side
const isReady = data && data.length > 0;

if / else

let age = 20;
if (age >= 18) {
    console.log("Adult");
} else if (age >= 13) {
    console.log("Teen");
} else {
    console.log("Child");
}

Loops

for (let i = 0; i < 5; i++) {
    console.log(i);
}

let n = 3;
while (n > 0) {
    console.log(n);
    n--;
}

break & continue

for (let i = 0; i < 10; i++) {
    if (i === 5) break;       // stop the loop entirely
    if (i % 2 === 0) continue; // skip just this iteration
    console.log(i);              // prints 1, 3
}
โš ๏ธ In a while loop, using continue before the increment step runs is a classic way to accidentally create an infinite loop - make sure the counter still advances before skipping.

Arrays

let nums = [10, 20, 30];
console.log(nums[0]);        // 10
nums.push(40);             // add to the end
console.log(nums.length);   // 4

for (const n of nums) {   // for...of - loop over VALUES
    console.log(n);
}

Array methods

JavaScript's array methods are how most real code is written - favor these over manual loops.

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

const doubled = nums.map(n => n * 2);        // [2,4,6,8,10] - transform each item
const evens   = nums.filter(n => n % 2 === 0); // [2,4] - keep matching items
const total   = nums.reduce((sum, n) => sum + n, 0); // 15 - combine into one value
const found   = nums.find(n => n > 3);        // 4 - first match

nums.forEach((value, index) => {          // the preferred way to just LOOP (no return value)
    console.log(`${index}: ${value}`);
});

Objects

const person = {
    name: "Ada",
    age: 30,
    greet() {
        console.log(`Hi, I'm ${this.name}`);
    }
};
console.log(person.name);   // Ada
person.greet();             // Hi, I'm Ada

Functions

function add(a, b) {
    return a + b;
}
console.log(add(2, 3));  // 5

function calculateTax(cost, taxRate = 0.1) {   // default parameter - used if the caller omits it
    return cost * taxRate;
}
console.log(calculateTax(100));   // 10 - uses the default 0.1
๐Ÿ“ Functions are "first-class" values in JS - you can store one in a variable and pass it into another function (a callback) to be called later. Just be sure to pass the function itself (myFn), not the result of calling it (myFn()) - a very common mistake with callbacks and timers.

Arrow functions

A shorter function syntax, extremely common in modern JS - especially with array methods.

const add = (a, b) => a + b;
console.log(add(2, 3));   // 5

const square = n => n * n;   // one param -> parens optional
console.log(square(4));    // 16

Closures

A function "remembers" the variables from the scope it was created in, even after that outer function has already returned - this is a closure.

function makeCounter() {
    let count = 0;             // captured by the inner function below
    return function() {
        count++;
        return count;
    };
}

const counter = makeCounter();
console.log(counter());   // 1
console.log(counter());   // 2 - count kept its value between calls, private to this counter
๐Ÿ“ Each call to makeCounter() creates a brand-new, independent count - closures are how you get private, per-instance state without needing a class.

Timers

const id = setTimeout(() => {
    console.log('Ran once, after 1 second');
}, 1000);

const intervalId = setInterval(() => {
    console.log('Runs every second, forever');
}, 1000);

clearInterval(intervalId);   // stop it - otherwise it never ends on its own

Template literals

const name = "Sam", age = 25;
console.log(`${name} is ${age} years old`);  // backticks + ${} - no more string concatenation

Destructuring

const person = { name: "Ada", age: 30 };
const { name, age } = person;   // pull fields out into variables
console.log(name, age);         // Ada 30

const [first, second] = [10, 20];  // works on arrays too

The DOM

document is a built-in object that links your JS to the actual webpage, so you can read and change what's on screen.

const buttonEl = document.querySelector('.js-subscribe-button');  // CSS-style selector
buttonEl.innerHTML = 'Subscribed';   // replace what's inside an element (parses HTML)

const allButtons = document.querySelectorAll('button');  // every match, as a list

buttonEl.classList.add('is-subscribed');      // toggle a CSS class from JS
buttonEl.classList.remove('is-subscribed');
โš ๏ธ innerHTML parses its string as real HTML (tags and all); innerText sets plain text and also strips extra whitespace when you read it back - they're not interchangeable.

Events

addEventListener is the modern, preferred way to react to user actions - an element can have several listeners, and they can be removed later.

buttonEl.addEventListener('click', () => {
    console.log('Button was clicked!');
});

document.body.addEventListener('keydown', (event) => {
    if (event.key === 'r') {
        console.log('You pressed R');
    }
});
๐Ÿ“ Elements can carry custom data-* HTML attributes (e.g. data-product-id="abc123"), readable in JS as element.dataset.productId - a clean way to attach an ID for an event handler to read back.

JSON

JSON (JavaScript Object Notation) is a text format for data that any language can read - used constantly for sending/storing objects.

const product = { name: 'Shirt', price: 1999 };

const text = JSON.stringify(product);   // '{"name":"Shirt","price":1999}' - object -> string
const back = JSON.parse(text);         // string -> object again
console.log(back.name);   // Shirt

localStorage

Lets a page save small amounts of data in the browser that survives a page refresh - but it only stores strings, so objects need JSON.stringify/parse.

localStorage.setItem('score', JSON.stringify({ wins: 3, losses: 1 }));

const score = JSON.parse(localStorage.getItem('score')) || { wins: 0, losses: 0 };
console.log(score.wins);   // 3

localStorage.removeItem('score');

Modules

Splitting code across files avoids naming collisions between separate <script> tags - each module explicitly declares what it shares.

// cart.js
export function addToCart(productId) { /* ... */ }
export const cart = [];

// main.js
import { addToCart, cart } from './cart.js';
addToCart('abc123');
๐Ÿ“ A file that uses import/export needs <script type="module" src="main.js"></script>, and generally needs to be served over a local dev server rather than opened directly as a file:// path.

Classes & OOP

A class is a blueprint for creating many similar objects, bundling data and behavior together. extends/super let one class build on another.

class Product {
    #internalNote;               // # = truly private, only accessible inside this class

    constructor(name, priceCents) {
        this.name = name;
        this.priceCents = priceCents;
    }
    getPrice() {
        return (this.priceCents / 100).toFixed(2);
    }
}

class Clothing extends Product {      // inherits everything from Product
    constructor(name, priceCents, size) {
        super(name, priceCents);     // must call the parent constructor first
        this.size = size;
    }
}

const shirt = new Clothing('T-Shirt', 1999, 'M');
console.log(shirt.getPrice());          // "19.99" - inherited method
console.log(shirt instanceof Product);  // true
โš ๏ธ Inside a regular method, this refers to the object it was called on - but a plain (non-arrow) callback passed to something like forEach loses that binding. Arrow functions don't have their own this - they inherit it from where they were written, which is why they're the safe default for callbacks inside a class method.

Error handling

try {
    const data = JSON.parse('not valid json');
} catch (error) {
    console.log('Something went wrong: ' + error.message);
} finally {
    console.log('This always runs');
}

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

Promises

A Promise represents a value that isn't ready yet, but will "resolve" (succeed) or "reject" (fail) later - the fix for deeply nested callbacks.

const waitOneSecond = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('Done waiting!');
    }, 1000);
});

waitOneSecond
    .then((result) => console.log(result))   // runs once resolve() fires
    .catch((error) => console.log(error));   // runs if reject() fires instead

// Promise.all waits for several promises to finish before continuing
Promise.all([waitOneSecond, fetch('/api/data')]).then(results => { /* ... */ });

async / await & fetch

fetch() is the modern, Promise-based way to talk to a server. async/await is syntax sugar over Promises that reads like ordinary top-to-bottom code - the generally preferred style over raw .then() chains.

async function loadOrder(orderId) {
    try {
        const response = await fetch(`/orders/${orderId}`);
        const order = await response.json();
        console.log(order);
    } catch (error) {
        console.log('Unexpected error, please try again later.');
    }
}
๐Ÿ“ await only works inside a function marked async. Prefer async/await > raw Promises > nested callbacks - each step reads top-to-bottom instead of nesting deeper and deeper.

๐Ÿš€ Practice problems

Runs instantly in your own browser - no server round-trip, same as Python.

Sum an array EASY

const nums = [4, 8, 15, 16, 23, 42];
const sum = nums.reduce((a, b) => a + b, 0);
console.log(sum);

Reverse a string EASY

function reverse(s) {
    return s.split("").reverse().join("");
}
console.log(reverse("javascript"));

Ternary adult/minor checker EASY

const age = 16;
const label = age >= 18 ? 'Adult' : 'Minor';
console.log(label);

Default value with the OR pattern EASY

function greet(name) {
    const safeName = name || 'Guest';
    console.log(`Hello, ${safeName}!`);
}
greet('Ada');
greet();   // undefined is falsy -> falls back to 'Guest'

forEach total calculator EASY

const prices = [999, 1499, 2000];   // cents
let totalCents = 0;
prices.forEach((price) => {
    totalCents += price;
});
console.log((totalCents / 100).toFixed(2));

Prime number checker EASY

function isPrime(n) {
    if (n < 2) return false;
    for (let i = 2; i * i <= n; i++) {
        if (n % i === 0) return false;
    }
    return true;
}
const numbers = [7, 10, 13, 1, 2];
for (const num of numbers) {
    console.log(num + ' is prime: ' + isPrime(num));
}

Palindrome checker EASY

function isPalindrome(str) {
    const clean = str.toLowerCase();
    let reversed = '';
    for (let i = clean.length - 1; i >= 0; i--) {
        reversed += clean[i];
    }
    return clean === reversed;
}
console.log(isPalindrome('racecar'));
console.log(isPalindrome('hello'));
console.log(isPalindrome('Level'));

Leap year checker EASY

function isLeapYear(year) {
    return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
}
const years = [2000, 1900, 2024, 2023];
for (const year of years) {
    console.log(year + ': ' + isLeapYear(year));
}

Digit sum of a number EASY

function digitSum(n) {
    let sum = 0;
    let num = n;
    while (num > 0) {
        sum += num % 10;
        num = Math.floor(num / 10);
    }
    return sum;
}
console.log(digitSum(1234));
console.log(digitSum(555));
console.log(digitSum(9));

Vowel counter EASY

function countVowels(str) {
    let count = 0;
    for (const char of str) {
        const c = char.toLowerCase();
        if (c === 'a' || c === 'e' || c === 'i' || c === 'o' || c === 'u') {
            count++;
        }
    }
    return count;
}
console.log(countVowels('Hello World'));
console.log(countVowels('JavaScript'));
console.log(countVowels('xyz'));

Greeting with default parameters EASY

function greet(name = 'Guest', greeting = 'Hello') {
    return greeting + ', ' + name + '!';
}
console.log(greet());
console.log(greet('Ada'));
console.log(greet('Sam', 'Welcome'));

Temperature converter (C to F/K) EASY

function celsiusToFahrenheit(c) {
    return c * 9 / 5 + 32;
}
function celsiusToKelvin(c) {
    return c + 273.15;
}
const temp = 25;
console.log(temp + 'C = ' + celsiusToFahrenheit(temp) + 'F');
console.log(temp + 'C = ' + celsiusToKelvin(temp) + 'K');

Letter grade calculator EASY

function letterGrade(score) {
    return score >= 90 ? 'A' : score >= 80 ? 'B' : score >= 70 ? 'C' : score >= 60 ? 'D' : 'F';
}
const scores = [95, 82, 71, 60, 40];
for (const score of scores) {
    console.log(score + ' -> ' + letterGrade(score));
}

Truthy/falsy input sanitizer EASY

function sanitize(value) {
    return value ? value : 'N/A';
}
const inputs = ['Alice', '', 0, 42, null, undefined, 'Bob'];
for (const input of inputs) {
    console.log(sanitize(input));
}

Recursive factorial EASY

function factorial(n) {
    if (n <= 1) return 1;
    return n * factorial(n - 1);
}
console.log(factorial(5));
console.log(factorial(0));
console.log(factorial(7));

Filter and map together MEDIUM

const nums = [1, 2, 3, 4, 5, 6, 7, 8];
const result = nums.filter(n => n % 2 === 0).map(n => n * n);
console.log(result.join(","));

Closure counter factory MEDIUM

function makeCounter() {
    let count = 0;
    return function() {
        count++;
        return count;
    };
}
const counterA = makeCounter();
const counterB = makeCounter();
console.log(counterA(), counterA(), counterA());   // 1 2 3
console.log(counterB());                            // 1 - totally independent

Class with inheritance MEDIUM

class Animal {
    constructor(name) { this.name = name; }
    speak() { return `${this.name} makes a sound.`; }
}
class Dog extends Animal {
    speak() { return `${this.name} barks.`; }   // overrides the parent method
}
const animals = [new Animal('Generic'), new Dog('Rex')];
animals.forEach(a => console.log(a.speak()));

JSON round-trip MEDIUM

const cart = [{ id: 'a1', qty: 2 }, { id: 'b2', qty: 1 }];
const saved = JSON.stringify(cart);
console.log(saved);

const restored = JSON.parse(saved);
console.log(restored[0].qty + restored[1].qty);   // 3

GCD and LCM calculator MEDIUM

function gcd(a, b) {
    while (b !== 0) {
        const temp = b;
        b = a % b;
        a = temp;
    }
    return a;
}
function lcm(a, b) {
    return (a * b) / gcd(a, b);
}
console.log('GCD:', gcd(48, 18));
console.log('LCM:', lcm(4, 6));

Bubble sort an array MEDIUM

function bubbleSort(original) {
    const result = [];
    for (const item of original) {
        result.push(item);
    }
    for (let i = 0; i < result.length; i++) {
        for (let j = 0; j < result.length - i - 1; j++) {
            if (result[j] > result[j + 1]) {
                const temp = result[j];
                result[j] = result[j + 1];
                result[j + 1] = temp;
            }
        }
    }
    return result;
}
const numbers = [5, 2, 9, 1, 5, 6];
console.log(bubbleSort(numbers));

Character frequency counter MEDIUM

function charFrequency(str) {
    const freq = {};
    for (const char of str) {
        if (freq[char]) {
            freq[char]++;
        } else {
            freq[char] = 1;
        }
    }
    return freq;
}
console.log(charFrequency('banana'));

Armstrong number checker MEDIUM

function isArmstrong(n) {
    let power = 0;
    let temp = n;
    while (temp > 0) {
        power++;
        temp = Math.floor(temp / 10);
    }
    let sum = 0;
    temp = n;
    while (temp > 0) {
        const digit = temp % 10;
        sum += Math.pow(digit, power);
        temp = Math.floor(temp / 10);
    }
    return sum === n;
}
const numbers = [153, 370, 9474, 123];
for (const num of numbers) {
    console.log(num + ': ' + isArmstrong(num));
}

Destructuring a config object MEDIUM

function setupServer({ host, port, timeout = 30 }) {
    return host + ':' + port + ' (timeout: ' + timeout + 's)';
}
const config1 = { host: 'localhost', port: 8080 };
const config2 = { host: '192.168.1.1', port: 443, timeout: 60 };
console.log(setupServer(config1));
console.log(setupServer(config2));

Filter, map, and reduce pipeline MEDIUM

const products = [
    { name: 'Laptop', price: 1000, inStock: true },
    { name: 'Mouse', price: 25, inStock: true },
    { name: 'Monitor', price: 300, inStock: false },
    { name: 'Keyboard', price: 75, inStock: true }
];
const total = products
    .filter((p) => p.inStock)
    .map((p) => p.price * 1.1)
    .reduce((sum, price) => sum + price, 0);
console.log(total.toFixed(2));

Closure-based bank account MEDIUM

function createAccount(initialBalance) {
    let balance = initialBalance;
    return {
        deposit: function(amount) {
            balance += amount;
            return balance;
        },
        withdraw: function(amount) {
            if (amount > balance) {
                return 'Insufficient funds';
            }
            balance -= amount;
            return balance;
        },
        getBalance: function() {
            return balance;
        }
    };
}
const account = createAccount(100);
console.log(account.deposit(50));
console.log(account.withdraw(30));
console.log(account.withdraw(1000));
console.log(account.getBalance());

Circle class with a getArea method MEDIUM

class Circle {
    constructor(radius) {
        this.radius = radius;
    }
    getArea() {
        return Math.PI * this.radius * this.radius;
    }
    getCircumference() {
        return 2 * Math.PI * this.radius;
    }
}
const circle = new Circle(5);
console.log(circle.getArea().toFixed(2));
console.log(circle.getCircumference().toFixed(2));

Stack simulation with closures MEDIUM

function createStack() {
    const items = [];
    return {
        push: function(item) {
            items.push(item);
        },
        pop: function() {
            return items.pop();
        },
        peek: function() {
            return items[items.length - 1];
        },
        size: function() {
            return items.length;
        }
    };
}
const stack = createStack();
stack.push(10);
stack.push(20);
stack.push(30);
console.log(stack.peek());
console.log(stack.pop());
console.log(stack.size());

Anagram checker MEDIUM

function sortString(str) {
    const chars = [];
    for (const c of str.toLowerCase()) {
        chars.push(c);
    }
    for (let i = 0; i < chars.length; i++) {
        for (let j = 0; j < chars.length - i - 1; j++) {
            if (chars[j] > chars[j + 1]) {
                const temp = chars[j];
                chars[j] = chars[j + 1];
                chars[j + 1] = temp;
            }
        }
    }
    let result = '';
    for (const c of chars) {
        result += c;
    }
    return result;
}
function isAnagram(a, b) {
    return sortString(a) === sortString(b);
}
console.log(isAnagram('listen', 'silent'));
console.log(isAnagram('hello', 'world'));
console.log(isAnagram('Elvis', 'Lives'));

Reverse the digits of a number MEDIUM

function reverseNumber(n) {
    let num = n;
    let reversed = 0;
    while (num > 0) {
        const digit = num % 10;
        reversed = reversed * 10 + digit;
        num = Math.floor(num / 10);
    }
    return reversed;
}
console.log(reverseNumber(12345));
console.log(reverseNumber(100));
console.log(reverseNumber(7));

Count word frequency HARD

function wordCount(text) {
    const counts = {};
    for (const word of text.split(" ")) {
        counts[word] = (counts[word] || 0) + 1;
    }
    return counts;
}
const result = wordCount("the cat sat on the mat the cat ran");
for (const word in result) {
    console.log(`${word}: ${result[word]}`);
}

Promise chain HARD

function fetchUser(id) {
    return new Promise((resolve) => {
        resolve({ id: id, name: 'Ada' });
    });
}
function fetchOrders(user) {
    return new Promise((resolve) => {
        resolve(['order1', 'order2']);
    });
}

fetchUser(1)
    .then((user) => fetchOrders(user))     // returning a promise from .then() chains flatly instead of nesting
    .then((orders) => console.log(orders.length))
    .catch((error) => console.log('Failed: ' + error));

async / await with error handling HARD

function fetchPrice(itemId) {
    return new Promise((resolve, reject) => {
        if (itemId < 0) reject(new Error('Invalid item id'));
        else resolve(1999);
    });
}

async function printTotal(itemId) {
    try {
        const priceCents = await fetchPrice(itemId);
        console.log((priceCents / 100).toFixed(2));
    } catch (error) {
        console.log('Error: ' + error.message);
    }
}
printTotal(1);
printTotal(-1);

Perfect number checker HARD

function isPerfectNumber(n) {
    let sum = 0;
    for (let i = 1; i < n; i++) {
        if (n % i === 0) {
            sum += i;
        }
    }
    return sum === n;
}
const numbers = [6, 28, 12, 496];
for (const num of numbers) {
    console.log(num + ' is perfect: ' + isPerfectNumber(num));
}

Pascal's triangle HARD

Builds Pascal's triangle using nested loops and prior-row lookups.

function pascalsTriangle(rows) {
    const triangle = [];
    for (let i = 0; i < rows; i++) {
        const row = [];
        for (let j = 0; j <= i; j++) {
            if (j === 0 || j === i) {
                row.push(1);
            } else {
                row.push(triangle[i - 1][j - 1] + triangle[i - 1][j]);
            }
        }
        triangle.push(row);
    }
    return triangle;
}
const result = pascalsTriangle(5);
for (const row of result) {
    let line = '';
    for (const num of row) {
        line += num + ' ';
    }
    console.log(line.trim());
}

Binary search HARD

function binarySearch(arr, target) {
    let low = 0;
    let high = arr.length - 1;
    while (low <= high) {
        const mid = Math.floor((low + high) / 2);
        if (arr[mid] === target) {
            return mid;
        } else if (arr[mid] < target) {
            low = mid + 1;
        } else {
            high = mid - 1;
        }
    }
    return -1;
}
const sorted = [2, 5, 8, 12, 16, 23, 38, 45, 56, 72];
console.log(binarySearch(sorted, 23));
console.log(binarySearch(sorted, 2));
console.log(binarySearch(sorted, 100));

Memoized Fibonacci HARD

A closure captures a cache object so repeated calls reuse previously computed Fibonacci values.

function createFibonacci() {
    const cache = {};
    function fib(n) {
        if (cache[n] !== undefined) {
            return cache[n];
        }
        if (n <= 1) {
            return n;
        }
        const result = fib(n - 1) + fib(n - 2);
        cache[n] = result;
        return result;
    }
    return fib;
}
const fibonacci = createFibonacci();
console.log(fibonacci(10));
console.log(fibonacci(15));
console.log(fibonacci(1));

Polymorphic shape hierarchy HARD

class Shape {
    constructor(name) {
        this.name = name;
    }
    area() {
        return 0;
    }
    describe() {
        return this.name + ' area: ' + this.area().toFixed(2);
    }
}
class Circle extends Shape {
    constructor(radius) {
        super('Circle');
        this.radius = radius;
    }
    area() {
        return Math.PI * this.radius * this.radius;
    }
}
class Square extends Shape {
    constructor(side) {
        super('Square');
        this.side = side;
    }
    area() {
        return this.side * this.side;
    }
}
const shapes = [new Circle(3), new Square(4)];
for (const shape of shapes) {
    console.log(shape.describe());
}

Save and reload a shopping cart (JSON) HARD

function saveCart(cart) {
    return JSON.stringify(cart);
}
function loadCart(json) {
    return JSON.parse(json);
}
const cart = {
    items: [
        { name: 'Shirt', price: 20, qty: 2 },
        { name: 'Shoes', price: 60, qty: 1 }
    ],
    coupon: null
};
const saved = saveCart(cart);
console.log(saved);
const loaded = loadCart(saved);
let total = 0;
for (const item of loaded.items) {
    total += item.price * item.qty;
}
console.log('Total: ' + total);
console.log(loaded.coupon === null);

Receipt formatter with template literals HARD

function formatReceipt(items) {
    let receipt = `--- Receipt ---\n`;
    let total = 0;
    for (const item of items) {
        const lineTotal = item.price * item.qty;
        total += lineTotal;
        receipt += `${item.name} x${item.qty}: $${lineTotal.toFixed(2)}\n`;
    }
    receipt += `Total: $${total.toFixed(2)}`;
    return receipt;
}
const items = [
    { name: 'Coffee', price: 3.5, qty: 2 },
    { name: 'Bagel', price: 2.25, qty: 1 }
];
console.log(formatReceipt(items));

Custom error validation HARD

A custom Error subclass carries a descriptive message through throw/catch.

class ValidationError extends Error {
    constructor(message) {
        super(message);
        this.name = 'ValidationError';
    }
}
function validateAge(age) {
    if (typeof age !== 'number') {
        throw new ValidationError('Age must be a number');
    }
    if (age < 0 || age > 120) {
        throw new ValidationError('Age must be between 0 and 120');
    }
    return true;
}
const inputs = [25, -5, 'thirty', 150];
for (const input of inputs) {
    try {
        validateAge(input);
        console.log(input + ' is valid');
    } catch (error) {
        console.log(input + ' error: ' + error.message);
    }
}

Promise.all with two synchronous promises HARD

function getUser() {
    return new Promise((resolve) => {
        resolve({ id: 1, name: 'Ada' });
    });
}
function getOrders() {
    return new Promise((resolve) => {
        resolve([{ id: 101, total: 50 }, { id: 102, total: 75 }]);
    });
}
Promise.all([getUser(), getOrders()]).then(([user, orders]) => {
    console.log(user.name + ' has ' + orders.length + ' orders');
    let total = 0;
    for (const order of orders) {
        total += order.total;
    }
    console.log('Total spent: ' + total);
});

Sequential async/await with rejection handling HARD

runPipeline is awaited fully before the next call starts, so output stays strictly sequential.

function fetchStep1() {
    return new Promise((resolve) => {
        resolve('Step 1 complete');
    });
}
function fetchStep2(shouldFail) {
    return new Promise((resolve, reject) => {
        if (shouldFail) {
            reject(new Error('Step 2 failed'));
        } else {
            resolve('Step 2 complete');
        }
    });
}
async function runPipeline(shouldFail) {
    try {
        const result1 = await fetchStep1();
        console.log(result1);
        const result2 = await fetchStep2(shouldFail);
        console.log(result2);
        console.log('Pipeline finished');
    } catch (error) {
        console.log('Pipeline error: ' + error.message);
    }
}
async function main() {
    await runPipeline(false);
    await runPipeline(true);
}
main();

Triangle classifier by side lengths HARD

function classifyTriangle(a, b, c) {
    if (a + b <= c || a + c <= b || b + c <= a) {
        return 'Not a valid triangle';
    }
    if (a === b && b === c) {
        return 'Equilateral';
    }
    if (a === b || b === c || a === c) {
        return 'Isosceles';
    }
    return 'Scalene';
}
console.log(classifyTriangle(3, 3, 3));
console.log(classifyTriangle(3, 3, 5));
console.log(classifyTriangle(3, 4, 5));
console.log(classifyTriangle(1, 1, 10));

Book class with an availability filter HARD

class Book {
    constructor(title, author, available) {
        this.title = title;
        this.author = author;
        this.available = available;
    }
    describe() {
        return `${this.title} by ${this.author}`;
    }
}
const library = [
    new Book('1984', 'George Orwell', true),
    new Book('Dune', 'Frank Herbert', false),
    new Book('Foundation', 'Isaac Asimov', true),
    new Book('Brave New World', 'Aldous Huxley', false)
];
const availableBooks = library.filter((book) => book.available);
console.log('Available books: ' + availableBooks.length);
for (const book of availableBooks) {
    console.log(book.describe());
}