// JavaScript Course — Chapter Exercises

JavaScript
Exercises

8 exercises for every chapter — from simple warm-ups to real-world challenges. Work through them in the Playground after studying each chapter. Every exercise has clear success criteria so you know exactly when you have got it right.

12 Chapters 96 Exercises Easy → Challenging Expected Outputs
Chapter 01

Variables

Practice declaring variables correctly, understanding when to use const vs let, and writing clear descriptive names. These habits matter from day one.

8 Exercises
EX 01
Your First Variables
Easy
+
Task

Declare variables to describe yourself. Use const for things that will not change, and let for things that might.

  • Your name (const)
  • Your age (let — it will change next year)
  • Your city (const)
  • Whether you are currently studying (let)
  • Log all four values to the console with a descriptive label for each
✓ Success Criteria
  • All four variables declared with the correct keyword
  • Console shows: Name: Ama Mensah, Age: 22, City: Kumasi, Studying: true
  • No var used anywhere
EX 02
Const Protects Values
Easy
+
Task

Prove to yourself that const prevents reassignment.

  • Declare const schoolName = 'AngelCode Labs'
  • Try to reassign it: schoolName = 'Other School'
  • Wrap the reassignment in a try/catch block and log the error message
  • Then declare a let variable and successfully reassign it
✓ Success Criteria
  • The try/catch catches a TypeError
  • Console shows: Error: Assignment to constant variable.
  • The let variable reassigns successfully without any error
EX 03
Good vs Bad Names
Easy
+
Task

Rewrite these poorly named variables with clear, descriptive camelCase names:

  • let x = 'Accra' → should describe a city
  • let n = 25 → should describe someone's age
  • let f = true → should describe whether a form was submitted
  • let d = new Date() → should describe the current date
  • Log each with its new name. No single-letter variables allowed.
✓ Success Criteria
  • All names are camelCase and self-describing
  • No abbreviations — userAge not usrAge
  • Someone reading the names can understand what each stores without seeing its value
EX 04
Receipt Calculator
Easy
+
Task

Build a simple receipt calculator using variables.

  • Declare const variables: itemName (string), unitPrice (number), quantity (number)
  • Calculate and store: subtotal, tax (15% of subtotal), total
  • Log a formatted receipt: Item, Quantity, Subtotal, Tax, Total
✓ Success Criteria
  • Console shows a readable receipt with all five lines
  • All values are calculated from the original variables — no hardcoded totals
  • Changing unitPrice or quantity automatically updates all calculated values
EX 05
Swap Two Variables
Medium
+
Task

Swap the values of two variables — first the old way (using a temporary variable), then the modern way.

  • Declare let a = 'first' and let b = 'second'
  • Method 1: Use a third variable temp to swap them. Log the result.
  • Reset them back to original values
  • Method 2: Swap using destructuring in one line: [a, b] = [b, a]. Log the result.
✓ Success Criteria
  • Both methods produce: a = 'second', b = 'first'
  • Method 2 is exactly one line of code
EX 06
Counting with Let
Medium
+
Task

Simulate a vote counter for three candidates.

  • Declare let variables for three candidates, each starting at 0
  • Simulate 10 votes by incrementing the variables (use ++ and +=)
  • Calculate the total votes and the winner
  • Log: each candidate's vote count, total votes, and the winner's name
✓ Success Criteria
  • Total votes equals exactly 10
  • Winner is the candidate with the highest count
  • All vote additions use ++ or += — not direct assignment
EX 07
Scope Experiment
Medium
+
Task

Understand how block scope works with let and const.

  • Declare const city = 'Accra' outside any block
  • Inside an if (true) { } block, declare let region = 'Greater Accra'
  • Log city from inside the block — it works
  • Log region from inside the block — it works
  • Try to log region from outside the block — wrap in try/catch and log the error
✓ Success Criteria
  • city accessible both inside and outside the block
  • region accessible only inside the block
  • Outside access throws a ReferenceError: region is not defined
EX 08
Student Profile Builder
Challenging
+
Task

Build a complete student profile using only variables — no objects or arrays yet.

  • Declare variables for: full name, student ID, programme, year (1–4), GPA, whether they are on scholarship, and their expected graduation year
  • Calculate: yearsRemaining (4 minus current year), isHonours (GPA above 3.5)
  • Build and log a full profile card string using string concatenation
  • Then log whether the student qualifies for honours and how many years remain
✓ Success Criteria
  • All 7 profile variables declared with correct keywords
  • Calculated values derived from the base variables — not hardcoded
  • Profile string is readable and complete when logged
  • Honours and years-remaining are both correct
Chapter 02

Data Types

Explore all six primitive types and learn how to check, convert, and work with them safely. Understanding types is what prevents the most common JavaScript bugs.

8 Exercises
EX 01
typeof Explorer
Easy
+
Task

Use typeof to check the type of every value listed below and log the result.

  • 'Hello', 42, true, undefined, null, {}, [], function(){}
  • Before running, write your prediction as a comment next to each
  • Pay special attention to null and [] — they are surprises
✓ Success Criteria
  • All 8 types logged correctly
  • typeof null === 'object' — you understand this is a known bug
  • typeof [] === 'object' — you understand arrays are objects
EX 02
String vs Number
Easy
+
Task

A user types their age into a form. It arrives as a string. Show the problem and fix it.

  • Declare const userInput = '25'
  • Try adding 5 to it: userInput + 5 — log the result and explain why
  • Convert it to a number using Number(), then add 5 — log the correct result
  • Also show what happens with Number('hello') and how to check for that
✓ Success Criteria
  • '25' + 5 produces '255' (string concatenation)
  • Number('25') + 5 produces 30
  • Number('hello') produces NaN
  • isNaN(Number('hello')) correctly returns true
EX 03
Truthy and Falsy
Easy
+
Task

Test each value and log whether it is truthy or falsy using an if statement.

  • Values to test: 0, 1, '', 'hello', null, undefined, false, true, NaN, [], {}
  • For each: log [value] is truthy or [value] is falsy
  • [] and {} will surprise you
✓ Success Criteria
  • All 11 values tested and labelled correctly
  • The 6 falsy values are: 0, '', null, undefined, false, NaN
  • [] and {} are both truthy (empty objects and arrays are truthy)
EX 04
undefined vs null
Easy
+
Task

Demonstrate the difference between undefined and null in a real scenario.

  • Declare let selectedProduct without a value — log it (undefined: nothing chosen yet)
  • Set it to null — log it (null: user deliberately cleared their selection)
  • Show undefined == null (true) vs undefined === null (false) and explain why
  • Write a function hasValue(x) that returns true only if x is neither null nor undefined
✓ Success Criteria
  • hasValue(0) returns true (0 is a valid value)
  • hasValue(null) returns false
  • hasValue(undefined) returns false
  • hasValue('') returns true (empty string is a valid value)
EX 05
Safe Type Conversion
Medium
+
Task

Write a function safeNumber(input) that converts any input to a number safely.

  • If the conversion produces NaN, return 0 as a default
  • If input is already a number, return it unchanged
  • If input is null or undefined, return 0
  • Test it with: '42', 'hello', null, undefined, true, '', '3.14'
✓ Success Criteria
  • safeNumber('42')42
  • safeNumber('hello')0
  • safeNumber(null)0
  • safeNumber(true)1
  • safeNumber('3.14')3.14
EX 06
Price Formatter
Medium
+
Task

Format prices correctly for display on a Ghana e-commerce site.

  • Write formatPrice(amount) that takes a number and returns a string like ₵45.00
  • Use .toFixed(2) for the decimal formatting
  • Handle non-number inputs gracefully (return '₵0.00')
  • Test with: 45, 45.5, 45.999, 'hello', null
✓ Success Criteria
  • formatPrice(45)'₵45.00'
  • formatPrice(45.999)'₵46.00' (rounds up)
  • formatPrice('hello')'₵0.00'
EX 07
Type Guard Functions
Medium
+
Task

Write a set of type-checking utility functions used in real applications.

  • isString(x) — returns true only for real strings
  • isNumber(x) — returns true only for real numbers (not NaN)
  • isBoolean(x) — returns true only for booleans
  • isArray(x) — returns true only for arrays (not objects)
  • Test each with at least 3 different inputs including edge cases
✓ Success Criteria
  • isNumber(NaN)false (NaN is not a usable number)
  • isArray([])true, isArray({})false
  • isString(42)false
  • All functions work correctly on null and undefined without crashing
EX 08
Form Input Validator
Challenging
+
Task

Build a function that validates a registration form submission.

  • Write validateForm(data) where data is an object: { name, age, email, agreeToTerms }
  • Validate: name is a non-empty string, age is a number between 16 and 120, email is a string containing '@', agreeToTerms is exactly true (boolean)
  • Return an object: { valid: true/false, errors: [] }
  • Test with both valid and invalid data
✓ Success Criteria
  • Valid data returns { valid: true, errors: [] }
  • Age of '25' (string) fails — must be a number
  • Missing '@' in email fails
  • agreeToTerms: 1 fails — must be boolean true, not number 1
  • Multiple errors all reported at once — not just the first one
Chapter 03

Operators

Practice arithmetic, comparison, and logical operators. Focus especially on the difference between == and === — getting this wrong causes bugs that are very hard to find.

8 Exercises
EX 01
Arithmetic Practice
Easy
+
Task

Calculate the following and log each result with a description.

  • 17 divided by 5 — what is the whole number result? (use Math.floor)
  • 17 divided by 5 — what is the remainder? (use modulo %)
  • 2 to the power of 8 (use exponentiation **)
  • Increment a variable from 10 to 15 using +=
  • Check if 97 is an odd number using modulo
✓ Success Criteria
  • 17 / 5 floor → 3
  • 17 % 5 → 2
  • 2 ** 8 → 256
  • 97 % 2 → 1 (odd)
EX 02
== vs === Danger Zone
Easy
+
Task

Demonstrate why == is dangerous and === is safe.

  • Test and log: 0 == false, 0 === false
  • Test and log: '' == false, '' === false
  • Test and log: null == undefined, null === undefined
  • Test and log: '5' == 5, '5' === 5
  • Write a comment next to each explaining why the results differ
✓ Success Criteria
  • All 8 comparisons logged with correct true/false result
  • Every == comparison has a comment explaining the coercion that happened
  • Conclusion logged: always use ===
EX 03
Logical Operators
Easy
+
Task

Model a cinema ticket pricing system using logical operators.

  • Variables: isStudent, isWeekend, isMember, age
  • Full price (₵50) if none of the above apply
  • Student discount (₵30) if isStudent AND age is under 25
  • Weekend surcharge: add ₵10 if isWeekend is true
  • Member discount: subtract ₵5 if isMember is true
  • Children under 12 are free regardless of other conditions
✓ Success Criteria
  • Student aged 22, weekend, not a member → ₵40
  • Adult, not weekend, member → ₵45
  • Child aged 10 → ₵0 (free)
EX 04
Nullish Coalescing
Easy
+
Task

Use the nullish coalescing operator ?? to provide safe defaults.

  • Write a function getDisplayName(user) that returns user.name ?? 'Guest'
  • Show why ?? is better than || when the value could legitimately be 0 or ''
  • Demo: const score = 0; const display = score || 'No score' — what is the problem?
  • Fix it: const display = score ?? 'No score' — what is the result now?
✓ Success Criteria
  • getDisplayName({ name: 'Ama' })'Ama'
  • getDisplayName({ name: null })'Guest'
  • 0 || 'No score''No score' (wrong — 0 is a valid score)
  • 0 ?? 'No score'0 (correct)
EX 05
Optional Chaining
Medium
+
Task

Use optional chaining ?. to safely access deeply nested data.

  • Create an object: const order = { customer: { name: 'Kwame', address: { city: 'Kumasi' } } }
  • Access order.customer.address.city safely using ?.
  • Try to access order.customer.phone.number without ?. — catch the error
  • Then access it with ?. — it returns undefined instead of crashing
  • Combine with ?? to provide a fallback: order.customer?.phone?.number ?? 'No phone'
✓ Success Criteria
  • Without ?.: TypeError: Cannot read properties of undefined
  • With ?.: undefined (no crash)
  • With ?. ??: 'No phone'
EX 06
Ternary Chains
Medium
+
Task

Write a grade classifier using the ternary operator.

  • Write a function classify(score) using a ternary chain
  • 90+ → 'A', 80–89 → 'B', 70–79 → 'C', 60–69 → 'D', below 60 → 'F'
  • Test with: 95, 83, 72, 65, 45
  • Then rewrite the same logic using if/else and compare readability
✓ Success Criteria
  • Both versions return identical results for all 5 test values
  • Ternary version is a single expression (no braces)
  • Comment added: which version is easier to read for a beginner?
EX 07
Short-Circuit Patterns
Medium
+
Task

Use short-circuit evaluation for common patterns seen in real code.

  • Default: const greeting = userName || 'Guest' — log when userName is '' vs 'Ama'
  • Guard: isLoggedIn && showDashboard() — call the function only when logged in
  • Nullish assignment: config.timeout ??= 3000 — only set if currently null/undefined
  • Conditional assignment: user.role &&= user.role.toUpperCase()
✓ Success Criteria
  • '' || 'Guest''Guest'
  • 'Ama' || 'Guest''Ama'
  • showDashboard() only called when isLoggedIn is true
  • ??= does not overwrite an existing value of 0
EX 08
Shopping Cart Calculator
Challenging
+
Task

Build a full shopping cart price engine using operators.

  • Variables: subtotal, isMember, couponCode, isWeekend
  • Member discount: 10% off if isMember
  • Coupon: if couponCode === 'SAVE20' apply an additional 20% off the discounted price
  • Weekend surcharge: add 5% if isWeekend
  • Free shipping if total after all discounts is above ₵200, otherwise add ₵15
  • Log the full breakdown: subtotal, discount, coupon saving, surcharge, shipping, final total
✓ Success Criteria
  • All calculations use operators — no hardcoded intermediate values
  • Subtotal ₵300, member, coupon SAVE20, not weekend → discount ₵30, coupon ₵54, shipping free, total ₵216
  • Subtotal ₵100, not member, no coupon, weekend → surcharge ₵5, shipping ₵15, total ₵120
Chapter 04

Control Flow

Practice making decisions and repeating code. These are the tools that make programs actually useful — without them everything runs the same way every single time.

8 Exercises
EX 01
Age Gate
Easy
+
Task

Write an age verification function for a website.

  • Write checkAccess(age)
  • Under 13: 'No access — children not permitted'
  • 13–17: 'Limited access — parental guidance required'
  • 18–64: 'Full access'
  • 65 and over: 'Senior access — larger text mode enabled'
  • Test all four ranges
✓ Success Criteria
  • All four age ranges return the correct message
  • Boundary values (13, 18, 65) return the correct message
  • Uses if / else if / else not switch
EX 02
Days of the Week
Easy
+
Task

Use a switch statement to describe each day.

  • Write describeDay(day) where day is 1–7 (1 = Monday)
  • Monday–Friday: 'Weekday: [day name]'
  • Saturday: 'Weekend: Saturday — rest day'
  • Sunday: 'Weekend: Sunday — church day'
  • Any other number: 'Invalid day'
  • Monday, Tuesday, and Wednesday should fall through to a shared 'Early week' note
✓ Success Criteria
  • All 7 day numbers handled correctly
  • describeDay(8) returns 'Invalid day'
  • Fall-through used for at least one group of cases
  • Every case has a break except the fall-through ones
EX 03
Count to 100
Easy
+
Task

Use a for loop to log numbers with labels.

  • Loop from 1 to 100
  • If the number is divisible by both 3 and 5: log 'FizzBuzz'
  • If divisible by 3 only: log 'Fizz'
  • If divisible by 5 only: log 'Buzz'
  • Otherwise: log the number
  • This is a classic programming interview question
✓ Success Criteria
  • Numbers 1, 2 logged as-is
  • Number 3 → 'Fizz'
  • Number 5 → 'Buzz'
  • Number 15 → 'FizzBuzz'
  • Check 15 before checking 3 or 5 — order matters
EX 04
Multiplication Table
Easy
+
Task

Generate a multiplication table using nested loops.

  • Use two nested for loops — outer for rows (1–10), inner for columns (1–10)
  • Build each row as a string of formatted results, e.g. 3 × 4 = 12
  • Log each complete row as one string
  • Add a header row: — Multiplication Table —
✓ Success Criteria
  • 10 rows, each with 10 calculations
  • Output is readable — each row on its own line
  • Row 7 includes: 7 × 7 = 49
EX 05
Password Validator
Medium
+
Task

Write a password strength checker using control flow.

  • Write checkPassword(pw)
  • At least 8 characters: +1 point
  • Contains a number: +1 point
  • Contains an uppercase letter: +1 point
  • Contains a special character (!@#$%): +1 point
  • Score 0–1: 'Weak', 2–3: 'Medium', 4: 'Strong'
  • Return an object: { strength, score, feedback }
✓ Success Criteria
  • checkPassword('abc')Weak, score 0
  • checkPassword('Password1')Medium, score 3
  • checkPassword('P@ssword1!')Strong, score 4
  • Feedback lists what is missing for a higher score
EX 06
While Loop ATM
Medium
+
Task

Simulate an ATM withdrawal system using a while loop.

  • Start with a balance of ₵500
  • Use a while loop to process an array of withdrawal amounts: [100, 200, 150, 300, 50]
  • Each iteration: check if balance is sufficient. If yes, deduct and log the transaction. If no, log 'Insufficient funds' and stop the loop with break
  • After the loop, log the final balance
✓ Success Criteria
  • ₵100 withdrawn: balance ₵400
  • ₵200 withdrawn: balance ₵200
  • ₵150 withdrawn: balance ₵50
  • ₵300 attempt: 'Insufficient funds' — loop stops
  • ₵50 never attempted (loop already broke)
  • Final balance: ₵50
EX 07
Number Pyramid
Medium
+
Task

Draw a number pyramid using nested loops and string building.

  • For a pyramid of height 5, output should look like:
    1
   1 2
  1 2 3
 1 2 3 4
1 2 3 4 5
  • Use a for loop for rows, a nested loop to build the number sequence, and string padding for alignment
  • Make height configurable — test with height 3 and height 7
✓ Success Criteria
  • Numbers are left-aligned within each row
  • Pyramid is correctly centred for any height
  • Works for height 1 (just logs '1')
EX 08
School Report Generator
Challenging
+
Task

Process an array of student objects and generate a full class report.

const students = [
  { name: 'Ama',   scores: [78, 82, 91] },
  { name: 'Kofi',  scores: [55, 60, 48] },
  { name: 'Abena', scores: [90, 95, 88] },
  { name: 'Kwame', scores: [70, 65, 72] },
];
  • For each student: calculate average score and assign a letter grade
  • Log each student's name, average, grade, and pass/fail status (pass = average 60+)
  • After all students, log: class average, highest scorer, lowest scorer, number of passes and failures
  • Use a for...of loop, continue to skip failed students in the honours count
✓ Success Criteria
  • Ama: avg 83.67, Grade B, Pass
  • Kofi: avg 54.33, Grade F, Fail
  • Abena: avg 91, Grade A, Pass
  • Class stats all correct
  • Top student: Abena (91)
Chapter 05

Functions

Practice writing clean, reusable functions. Focus on single responsibility — each function does exactly one thing. This chapter also covers arrow functions and default parameters.

8 Exercises
EX 01
Your First Functions
Easy
+
Task

Write four basic functions — two as declarations and two as arrow functions.

  • function square(n) — returns n × n
  • function isEven(n) — returns true if n is even
  • const cube = (n) => — returns n × n × n
  • const greet = (name) => — returns 'Hello, [name]!'
  • Call each function with two different inputs and log the results
✓ Success Criteria
  • square(7)49
  • isEven(4)true, isEven(7)false
  • cube(3)27
  • greet('Kwame')'Hello, Kwame!'
EX 02
Default Parameters
Easy
+
Task

Write functions that use default parameters so they work even when arguments are missing.

  • createProduct(name, price = 0, currency = 'GHS') — returns a formatted string
  • sendEmail(to, subject = 'No Subject', body = '') — logs a simulated email
  • repeat(str, times = 1) — returns the string repeated that many times
  • Test each function with and without the optional arguments
✓ Success Criteria
  • createProduct('Kente') → uses default price ₵0 and currency GHS
  • repeat('ha')'ha' (once)
  • repeat('ha', 3)'hahaha'
  • No function crashes when optional arguments are omitted
EX 03
Function Returns
Easy
+
Task

Practice using return values by chaining function calls.

  • Write celsiusToFahrenheit(c) — formula: (c × 9/5) + 32
  • Write fahrenheitToCelsius(f) — formula: (f − 32) × 5/9
  • Write roundTo2(n) — rounds to 2 decimal places
  • Chain them: convert 100°C to Fahrenheit, back to Celsius, round to 2 decimal places. Should return exactly 100.
✓ Success Criteria
  • celsiusToFahrenheit(0)32
  • celsiusToFahrenheit(100)212
  • Round-trip: roundTo2(fahrenheitToCelsius(celsiusToFahrenheit(100)))100
EX 04
Calculation Library
Easy
+
Task

Build a small maths utility library of arrow functions.

  • add(a, b), subtract(a, b), multiply(a, b), divide(a, b)
  • divide should return 'Cannot divide by zero' if b is 0
  • average(...nums) — uses rest parameters to accept any number of values
  • clamp(value, min, max) — returns value clamped between min and max
✓ Success Criteria
  • divide(10, 0)'Cannot divide by zero'
  • average(10, 20, 30, 40)25
  • clamp(150, 0, 100)100
  • clamp(-5, 0, 100)0
EX 05
Functions as Arguments
Medium
+
Task

Practice passing functions as arguments — the foundation of callbacks.

  • Write applyTwice(fn, value) — applies fn to value, then applies fn to the result
  • Write transform(numbers, fn) — applies fn to every item in an array and returns a new array
  • Test with: applyTwice(x => x * 2, 3) → should give 12
  • Test with: transform([1,2,3,4], x => x * x) → should give [1,4,9,16]
✓ Success Criteria
  • applyTwice(x => x + 10, 5)25
  • transform([1,2,3], x => x * 3)[3, 6, 9]
  • Both functions work with any callback — not hardcoded to one operation
EX 06
Closure Counter
Medium
+
Task

Use closures to create private state — a fundamental pattern in JavaScript.

  • Write makeCounter(start = 0, step = 1)
  • Returns an object with: increment(), decrement(), reset(), value()
  • The count variable must be private — not accessible from outside
  • Create two independent counters and show they do not share state
✓ Success Criteria
  • counter.increment() three times then counter.value()3
  • counter.countundefined (private)
  • Two separate counters increment independently
  • makeCounter(10, 5).increment() → value is 15
EX 07
Input Sanitiser
Medium
+
Task

Write a pipeline of small functions that clean user input — one function per job.

  • trim(str) — removes leading and trailing whitespace
  • lowercase(str) — converts to lowercase
  • removeSpaces(str) — replaces all spaces with hyphens
  • limitLength(str, max = 50) — truncates to max characters
  • Write sanitiseSlug(input) that chains all four in order
✓ Success Criteria
  • sanitiseSlug(' Hello World ')'hello-world'
  • sanitiseSlug(' My Amazing Blog Post Title That Is Very Long Indeed ') is truncated at 50 chars
  • Each small function works independently and can be reused
EX 08
Discount Engine
Challenging
+
Task

Build a flexible discount engine using functions that return functions.

  • Write makeDiscount(rate) — returns a function that applies that discount rate to any price
  • Write makeFloorDiscount(rate, floor) — discount only applies if price is above the floor
  • Write composeDiscounts(...discountFns) — applies all discounts in sequence
  • Create: 10% member discount, 5% loyalty discount, compose them and apply to ₵400
✓ Success Criteria
  • makeDiscount(0.10)(200)180
  • makeFloorDiscount(0.20, 300)(250)250 (no discount — below floor)
  • makeFloorDiscount(0.20, 300)(400)320
  • Composed 10% + 5% on ₵400 → 342 (applied sequentially)
Chapter 06

Arrays

Master the array methods you will use every day: map, filter, reduce, find, and forEach. These replace loops for most data processing tasks.

8 Exercises
EX 01
Array Basics
Easy
+
Task

Create and manipulate a shopping list array.

  • Create an array of 5 items
  • Add two items using push (end) and unshift (beginning)
  • Remove the last item with pop and store it in a variable
  • Log the array length before and after each operation
  • Access the third item using its index
✓ Success Criteria
  • All operations logged with before/after length
  • The popped item is stored and logged separately
  • Third item accessed with index [2]
EX 02
Map Transform
Easy
+
Task

Use map to transform an array of product prices.

const prices = [45, 120, 30, 89, 210];
  • Create withTax — all prices with 15% tax added
  • Create discounted — all prices with 10% removed
  • Create labels — each price formatted as '₵45.00'
  • The original prices array must remain unchanged
✓ Success Criteria
  • withTax[0]51.75
  • discounted[4]189
  • labels[2]'₵30.00'
  • prices still equals [45, 120, 30, 89, 210]
EX 03
Filter Challenge
Easy
+
Task

Filter a student list based on different criteria.

const students = [
  { name: 'Ama',   score: 78, passed: true  },
  { name: 'Kofi',  score: 45, passed: false },
  { name: 'Abena', score: 92, passed: true  },
  { name: 'Yaw',   score: 55, passed: false },
  { name: 'Efua',  score: 88, passed: true  },
];
  • Filter: students who passed
  • Filter: students who scored above 80
  • Filter: students whose name starts with 'A'
✓ Success Criteria
  • Passed: Ama, Abena, Efua (3 students)
  • Score above 80: Abena, Efua (2 students)
  • Name starts with A: Ama, Abena
EX 04
Reduce to Summary
Medium
+
Task

Use reduce to summarise an array of transactions.

const transactions = [
  { type: 'credit', amount: 500 },
  { type: 'debit',  amount: 120 },
  { type: 'credit', amount: 350 },
  { type: 'debit',  amount: 80  },
  { type: 'debit',  amount: 200 },
];
  • Calculate total credits, total debits, and net balance using reduce
  • Find the single largest transaction using reduce
✓ Success Criteria
  • Total credits: 850
  • Total debits: 400
  • Net balance: 450
  • Largest transaction: { type: 'credit', amount: 500 }
EX 05
Find and Search
Medium
+
Task

Practice the search methods on a product array.

const products = [
  { id: 1, name: 'Kente',   price: 85,  stock: 12 },
  { id: 2, name: 'Batik',    price: 60,  stock: 0  },
  { id: 3, name: 'Adinkra', price: 120, stock: 5  },
  { id: 4, name: 'Smock',   price: 200, stock: 3  },
];
  • Find the product with id 3 using find
  • Check if any product is out of stock using some
  • Check if all products have a price above 50 using every
  • Find the index of 'Smock' using findIndex
✓ Success Criteria
  • find returns { id: 3, name: 'Adinkra', ... }
  • some out of stock → true
  • every above 50 → true
  • Smock index → 3
EX 06
Sort and Slice
Medium
+
Task

Sort and paginate an array of products.

  • Sort the products array by price ascending (cheapest first)
  • Sort by price descending (most expensive first)
  • Sort alphabetically by name
  • Get the top 2 most expensive items using sort + slice
  • Important: sort creates a copy first using spread to avoid mutating the original
✓ Success Criteria
  • Price ascending: Batik, Kente, Adinkra, Smock
  • Alphabetical: Adinkra, Batik, Kente, Smock
  • Top 2 expensive: [Smock (200), Adinkra (120)]
  • Original products array is unchanged after all sorting
EX 07
Flatten and Deduplicate
Medium
+
Task

Process nested and duplicate data.

const tags = [
  ['html', 'css', 'beginner'],
  ['css', 'flexbox', 'intermediate'],
  ['javascript', 'beginner', 'dom'],
];
  • Flatten all tags into a single array using flat()
  • Remove duplicates using Set
  • Sort the unique tags alphabetically
  • Count how many times 'beginner' appears in the original nested array
✓ Success Criteria
  • Flat array has 9 items (with duplicates)
  • Unique sorted array: ['beginner', 'css', 'dom', 'flexbox', 'html', 'intermediate', 'javascript']
  • 'beginner' appears 2 times
EX 08
Inventory Report
Challenging
+
Task

Process a full product inventory using only array methods — no for loops.

const inventory = [
  { name: 'Kente',   category: 'fabric',  price: 85,  qty: 12 },
  { name: 'Batik',    category: 'fabric',  price: 60,  qty: 0  },
  { name: 'Sandals',  category: 'footwear',price: 150, qty: 8  },
  { name: 'Basket',   category: 'craft',   price: 45,  qty: 20 },
  { name: 'Adinkra', category: 'fabric',  price: 120, qty: 5  },
];
  • Total stock value (price × qty for each, summed)
  • All out-of-stock items (qty === 0)
  • Fabric items only, sorted by price descending
  • Group by category using reduce into an object: { fabric: [...], footwear: [...], craft: [...] }
  • Most expensive in-stock item
✓ Success Criteria
  • Total stock value: ₵4,775
  • Out of stock: ['Batik']
  • Fabric sorted: Adinkra (120), Kente (85), Batik (60)
  • Most expensive in-stock: Sandals (₵150)
  • Grouped object has correct items in each category
Chapter 07

Objects

Objects are the core data structure of JavaScript. Every piece of data from a server arrives as an object. Practice creating, reading, modifying, and combining objects correctly.

8 Exercises
EX 01
Build a User Profile
Easy
+
Task

Create a user profile object and access its properties.

  • Create an object with: firstName, lastName, age, email, isActive
  • Add a method fullName() that returns the first and last name combined
  • Add a method greet() that returns 'Hello, I am [fullName] and I am [age] years old.'
  • Access email using dot notation and also bracket notation
✓ Success Criteria
  • user.fullName()'Ama Mensah'
  • user.greet()'Hello, I am Ama Mensah and I am 22 years old.'
  • user.email and user['email'] return the same value
EX 02
Object Modification
Easy
+
Task

Practice modifying objects by adding, updating, and deleting properties.

  • Start with a product object: { name: 'Kente', price: 85, category: 'fabric' }
  • Add a new property stock: 10
  • Update the price to 95
  • Delete the category property
  • Log the object after each change to see it evolve
  • Check if category still exists using the in operator
✓ Success Criteria
  • After all changes: { name: 'Kente', price: 95, stock: 10 }
  • 'category' in productfalse
  • 'stock' in producttrue
EX 03
Object Spread and Merge
Easy
+
Task

Use spread syntax to copy and merge objects without mutating originals.

  • Create defaultSettings = { theme: 'dark', fontSize: 14, language: 'en' }
  • Create userSettings = { theme: 'light', fontSize: 18 }
  • Merge them with spread: user settings should override defaults
  • Prove the original objects are unchanged after merging
  • Create an updated copy of a user object with only the email changed
✓ Success Criteria
  • Merged: { theme: 'light', fontSize: 18, language: 'en' }
  • defaultSettings.theme is still 'dark' after merge
  • Updated user object has new email but all other properties unchanged
EX 04
Destructuring
Medium
+
Task

Practice object destructuring — extracting values cleanly.

const order = {
  id: 1042,
  customer: { name: 'Kwame', city: 'Kumasi' },
  items: ['Kente', 'Adinkra'],
  total: 205,
  paid: true,
};
  • Destructure: id, total, and paid in one line
  • Destructure customer.name with a rename: { customer: { name: customerName } }
  • Destructure with a default: add notes = 'None' (it does not exist in the object)
  • Use destructuring in a function parameter: write formatOrder({ id, total, customer })
✓ Success Criteria
  • customerName'Kwame'
  • notes'None' (default applied)
  • formatOrder(order) logs a string using all three destructured properties
EX 05
Object.keys / values / entries
Medium
+
Task

Use the Object static methods to iterate and transform an object.

const scores = {
  Ama: 78, Kofi: 92, Abena: 65, Yaw: 88
};
  • Log all student names using Object.keys()
  • Calculate the class average using Object.values() and reduce
  • Find the top scorer using Object.entries() and reduce
  • Create a new object with scores doubled using Object.fromEntries + map
✓ Success Criteria
  • Average: 80.75
  • Top scorer: Kofi (92)
  • Doubled: { Ama: 156, Kofi: 184, Abena: 130, Yaw: 176 }
EX 06
Nested Objects
Medium
+
Task

Work with deeply nested object data — the kind you get from a server.

const school = {
  name: 'AngelCode Academy',
  location: { city: 'Accra', region: 'Greater Accra' },
  departments: {
    tech: { head: 'Mr Asante', students: 120 },
    arts: { head: 'Ms Agyeman', students: 85  },
  },
};
  • Access: city, tech department head, total students across all departments
  • Add a new department: science: { head: 'Dr Boateng', students: 60 }
  • Update the tech student count to 135
  • Calculate the new total students
✓ Success Criteria
  • City: 'Accra'
  • Tech head: 'Mr Asante'
  • Original total: 205
  • After updates: 280 (135 + 85 + 60)
EX 07
Array of Objects Pipeline
Medium
+
Task

Process an array of order objects — the most common real-world pattern.

const orders = [
  { id: 1, customer: 'Ama',   amount: 120, status: 'paid'    },
  { id: 2, customer: 'Kofi',  amount: 85,  status: 'pending' },
  { id: 3, customer: 'Abena', amount: 200, status: 'paid'    },
  { id: 4, customer: 'Yaw',   amount: 55,  status: 'cancelled'},
];
  • Get all paid order amounts using filter + map
  • Total revenue from paid orders using reduce
  • Transform each order: add a formattedAmount property (e.g. '₵120.00')
  • Find the order belonging to 'Abena'
✓ Success Criteria
  • Paid amounts: [120, 200]
  • Revenue: 320
  • Every order in the transformed array has a formattedAmount property
  • Abena's order: { id: 3, customer: 'Abena', amount: 200, ... }
EX 08
Config Builder
Challenging
+
Task

Build a configuration system that merges settings at multiple levels.

  • Declare three objects: defaults, envConfig (production overrides), userConfig (user preferences)
  • Each has properties like: theme, language, notifications, timeout, debug
  • Merge in order: defaults, then env overrides, then user preferences (each layer wins over the previous)
  • Write getConfig(key) that reads from the merged config with a fallback
  • Freeze the final config so it cannot be accidentally modified
✓ Success Criteria
  • User preferences override env overrides, which override defaults
  • getConfig('missing-key') returns a sensible default, not an error
  • Trying to modify the frozen config throws an error in strict mode
  • Log the complete merged config object showing all sources combined
Chapter 08

DOM Manipulation

This is where JavaScript becomes visible. Practice selecting elements, changing content, adding classes, and building HTML dynamically — the skills used in every real web project.

8 Exercises
EX 01
Select and Read
Easy
+
Task

Practice selecting elements and reading their content.

  • Create an HTML page with: an <h1 id="title">, three <p class="paragraph">, and a <button>
  • Select the h1 by ID and log its textContent
  • Select all paragraphs and log how many there are
  • Select the button using querySelector and log its tag name
✓ Success Criteria
  • h1 content logged correctly
  • Paragraph count logs 3
  • Button tag name logs 'BUTTON'
  • No errors in the console
EX 02
Change Content
Easy
+
Task

Use JavaScript to change what is displayed on the page.

  • Select an <h1> and change its text to 'Updated by JavaScript'
  • Select a <p> and change its colour to #f0c040 using style.color
  • Select an <img> and change its src attribute using setAttribute
  • Select an input and change its placeholder attribute
✓ Success Criteria
  • Page shows the updated h1 text
  • Paragraph text turns gold
  • Image source changed — DevTools shows new src value
  • Input shows new placeholder text
EX 03
classList Switcher
Easy
+
Task

Practice managing CSS classes from JavaScript.

  • Create a <div id="box"> and CSS classes: .active (green border), .hidden (display none), .highlighted (yellow background)
  • Add active class and check it is there with contains
  • Remove active, add highlighted
  • Toggle hidden twice — confirm box shows, then hides
  • Replace highlighted with active using replace
✓ Success Criteria
  • After add: box.classList.contains('active')true
  • After toggle twice: box returns to original visible state
  • After replace: box has active class, NOT highlighted
EX 04
Create Elements
Easy
+
Task

Build HTML dynamically using createElement.

  • Create a <ul id="list"> in the HTML
  • Write JavaScript that creates and appends 5 <li> elements, each with different text
  • Each <li> should have a class of list-item
  • Add a Remove Last button that removes the last <li> when clicked
✓ Success Criteria
  • 5 items appear in the list after the JS runs
  • Each has class list-item visible in DevTools
  • Clicking button removes one item each time
  • Clicking when list is empty causes no error
EX 05
Dark Mode Toggle
Medium
+
Task

Build a working dark mode toggle using DOM and classList.

  • Create a page with light background, dark text, and a Toggle Dark Mode button
  • Define CSS classes: .dark on <body> switches to dark background and light text
  • Write JavaScript that toggles the dark class on body when the button is clicked
  • Update the button text: 'Switch to Dark' / 'Switch to Light'
  • Save the preference in localStorage and apply it on page load
✓ Success Criteria
  • Button click toggles the visual appearance
  • Button text updates to reflect the current state
  • Refreshing the page restores the last chosen mode
EX 06
Product Card Generator
Medium
+
Task

Render an array of product objects as HTML cards on the page.

const products = [
  { name: 'Kente Fabric',  price: 85,  inStock: true  },
  { name: 'Leather Sandals',price: 150, inStock: false },
  { name: 'Woven Basket',   price: 45,  inStock: true  },
];
  • For each product, create a <div class="card"> with: name, price (formatted), and a badge that says In Stock or Out of Stock'
  • Use createElement — no innerHTML
  • Out of stock cards get a greyed-out style
✓ Success Criteria
  • 3 cards rendered on the page
  • Leather Sandals card has an Out of Stock badge
  • Out of stock card is visually distinct (different opacity or colour)
  • No innerHTML used anywhere
EX 07
DOM Traversal
Medium
+
Task

Navigate the DOM tree using traversal properties.

  • Create an HTML list with a parent <ul> and 5 <li> children
  • Select the third <li> directly using querySelector
  • From that element, access: its parent, its previous sibling, its next sibling
  • From the parent, access the first and last child
  • Use closest('.container') to find the nearest ancestor with that class
✓ Success Criteria
  • Parent of third <li> is the <ul>
  • Previous sibling is the second <li>
  • First child of <ul> is the first <li>
  • closest returns the correct ancestor
  • All accessed via traversal — not new querySelector calls
EX 08
Dynamic Table Builder
Challenging
+
Task

Build a fully dynamic data table from a JavaScript array.

const data = [
  { name: 'Ama',   score: 78, grade: 'B' },
  { name: 'Kofi',  score: 45, grade: 'F' },
  { name: 'Abena', score: 91, grade: 'A' },
];
  • Generate <thead> from the object keys of the first item
  • Generate <tbody> rows from each data item
  • Failing rows (grade F) get a red background
  • Add a footer row showing the class average score
  • Add a Sort by Score button that re-renders the table sorted
✓ Success Criteria
  • Table headers: name | score | grade
  • Kofi's row is red-tinted
  • Footer shows average: 71.33
  • Sort button re-renders table in descending score order
  • All built with createElement — no innerHTML
Chapter 09

Events

Events are how users interact with your page. Practice attaching listeners, reading event data, and the important patterns: delegation, preventing defaults, and form handling.

8 Exercises
EX 01
Click Counter
Easy
+
Task

Build a simple click counter with increment and reset buttons.

  • HTML: a display <span id="count">0</span>, a +1 button, and a Reset button
  • Clicking +1 increments the displayed number
  • Clicking Reset sets it back to 0
  • When count reaches 10, change the colour to red and disable the +1 button
✓ Success Criteria
  • Count updates in real-time on every click
  • At 10: count is red and +1 button is disabled
  • Reset: count returns to 0, button re-enabled, colour restored
EX 02
Live Input Preview
Easy
+
Task

Show a live preview of what the user types.

  • HTML: a text input and a preview <p> below it
  • As the user types, the paragraph updates in real-time using the input event
  • Show the character count next to the preview: 23 / 100
  • When the count exceeds 80, turn the counter red as a warning
  • Pressing Escape clears the input and resets the preview
✓ Success Criteria
  • Preview updates instantly with each keystroke
  • Counter turns red at 81+ characters
  • Escape clears both the input and the preview
EX 03
Form Validation
Easy
+
Task

Handle a form submission and validate the inputs before accepting them.

  • Form with: name (required, min 2 chars), email (must contain @), and password (min 6 chars)
  • On submit: call e.preventDefault() to stop the page reload
  • Validate all three fields. Show red error messages below failing fields
  • On success: clear the form and show a green success message
✓ Success Criteria
  • Page does not reload on submit
  • Each field shows its own specific error message
  • All errors appear at once — not one at a time
  • Valid submission clears form and shows success message
EX 04
Mouse Tracker
Easy
+
Task

Track the mouse position and update the UI in real-time.

  • Display live X/Y coordinates as the mouse moves over a box
  • Use the mousemove event and read e.clientX and e.clientY
  • Move a small dot element to follow the cursor inside the box
  • Change the box background colour based on the X position (0 = left colour, max = right colour)
✓ Success Criteria
  • Coordinates update smoothly as mouse moves
  • Dot follows the cursor
  • Background changes colour as cursor moves left-right
EX 05
Event Delegation
Medium
+
Task

Use event delegation to handle clicks on a list — even on items added later.

  • Create a <ul> with 3 <li> items, each with a data-id attribute
  • Attach ONE click listener to the <ul> — not to individual <li> items
  • Use e.target.closest('li') to identify which item was clicked
  • Add a Add Item button that adds a new <li> — clicking the new item must also work
  • Log the data-id of the clicked item
✓ Success Criteria
  • Only ONE event listener on the parent (check DevTools)
  • Clicking original items logs their data-id
  • Clicking newly added items also logs their data-id
  • Clicking outside the list items does nothing
EX 06
Keyboard Shortcuts
Medium
+
Task

Implement keyboard shortcuts for a simple text editor area.

  • A <textarea> that responds to key combinations
  • Ctrl+B — wraps selected text in **bold**
  • Ctrl+I — wraps selected text in _italic_
  • Ctrl+K — wraps selected text in [link text](url)
  • Use e.ctrlKey and e.key and always call e.preventDefault()
  • Show a hint bar below listing the available shortcuts
✓ Success Criteria
  • Selecting text then pressing Ctrl+B wraps it: **selected**
  • Browser's default Ctrl+B (bold) is prevented
  • Works with any selected text, not just specific words
EX 07
Image Gallery
Medium
+
Task

Build a simple image gallery with keyboard and click navigation.

  • Display 5 thumbnail images in a row
  • Clicking a thumbnail shows it full-size in a lightbox overlay
  • The lightbox has Previous and Next buttons
  • Left/right arrow keys also navigate
  • Pressing Escape or clicking outside the image closes the lightbox
✓ Success Criteria
  • Clicking any thumbnail opens the lightbox with that image
  • Arrow keys cycle through all 5 images
  • Escape closes the lightbox
  • Next wraps around from last image to first
EX 08
Drag to Reorder
Challenging
+
Task

Build a drag-and-drop list using the HTML Drag and Drop API.

  • Create a list of 5 items, each with draggable="true"
  • Wire up: dragstart, dragover, drop events
  • Store the dragged item's index in event.dataTransfer.setData
  • On drop: reorder the actual array and re-render the list
  • Visual feedback: highlight the drop target while dragging over it
✓ Success Criteria
  • Items can be dragged and dropped to any position
  • Drop target has a visible highlight while item is dragged over it
  • Order is genuinely changed — not just visual; log the new array order
  • Works for any combination of source and target positions
Chapter 10

Strings & Template Literals

String manipulation is used in almost every program. Practice template literals, all the common string methods, and building real output like receipts, reports, and formatted messages.

8 Exercises
EX 01
Template Literal Messages
Easy
+
Task

Rewrite these messy string concatenations using template literals.

  • 'Hello ' + name + ', you have ' + count + ' messages.'
  • '₵' + (price * qty).toFixed(2) + ' total'
  • A multi-line address block using the old \n way, then rewrite with a proper multi-line template literal
  • Embed an expression: show score as a percentage with a pass/fail indicator in one template literal
✓ Success Criteria
  • All four messages correctly built using backtick strings
  • Multi-line address uses real line breaks, not \n
  • Expression example: 'Score: 78% — PASS' built in one template literal
EX 02
String Cleaning
Easy
+
Task

A user has submitted messy form data. Clean it up.

const rawName  = '  ama mensah  ';
const rawEmail = '  AMA@EXAMPLE.COM  ';
const rawPhone = '  +233 (0) 24-123-4567  ';
  • Clean name: trim, then capitalise each word (Ama Mensah)
  • Clean email: trim and lowercase
  • Clean phone: remove all spaces, brackets, and dashes
✓ Success Criteria
  • Name → 'Ama Mensah'
  • Email → 'ama@example.com'
  • Phone → '+2330241234567'
EX 03
Search and Replace
Easy
+
Task

Practice finding and replacing content in strings.

  • Write censorWord(text, word) that replaces all occurrences of word with asterisks the same length
  • Write highlightQuery(text, query) that wraps every occurrence of query in <mark>...</mark>
  • Write slugify(title) that converts a blog title to a URL slug: lowercase, spaces to hyphens, remove special chars
✓ Success Criteria
  • censorWord('bad word here', 'bad')'*** word here'
  • highlightQuery('hello world', 'world')'hello <mark>world</mark>'
  • slugify('Hello World! #1')'hello-world-1'
EX 04
Split and Parse
Easy
+
Task

Parse structured text data using split.

  • Parse this CSV line into an object: 'Ama Mensah,22,Accra,student'
  • Parse a full name into first/last: 'Kwame Asante Boateng' → first: 'Kwame', last: 'Boateng', middle: 'Asante'
  • Parse a time string '14:35:22' into hours, minutes, seconds
  • Join an array of words back into a sentence with proper capitalisation
✓ Success Criteria
  • CSV → { name: 'Ama Mensah', age: 22, city: 'Accra', role: 'student' }
  • Name parse → { first: 'Kwame', middle: 'Asante', last: 'Boateng' }
  • Time → { hours: 14, minutes: 35, seconds: 22 }
EX 05
Receipt Builder
Medium
+
Task

Build a formatted receipt string from an order object.

const order = {
  id: 'ORD-2026-1042',
  items: [
    { name: 'Kente Fabric',  qty: 2, price: 85  },
    { name: 'Woven Basket',   qty: 1, price: 45  },
  ],
  customer: 'Ama Mensah',
};
  • Build a receipt string with: header line, item lines (name, qty, line total), subtotal, 15% tax, and grand total
  • Align values using padStart / padEnd so columns are neat
  • Use template literals throughout
✓ Success Criteria
  • Receipt shows: Kente Fabric × 2 = ₵170.00
  • Columns are aligned — amounts right-justified
  • Subtotal: ₵215.00, Tax: ₵32.25, Total: ₵247.25
EX 06
Truncate and Ellipsis
Medium
+
Task

Write string utilities for displaying content in constrained spaces.

  • truncate(str, maxLength = 50) — cuts at maxLength and adds ...
  • truncateWords(str, wordCount = 10) — cuts at a word boundary, not mid-word
  • excerpt(str, query) — finds the query in the string and returns the surrounding context (50 chars before and after)
✓ Success Criteria
  • truncate('Hello World', 8)'Hello...'
  • truncateWords always cuts at a space — never mid-word
  • excerpt highlights the query within its context
EX 07
Number Formatting
Medium
+
Task

Build number formatting utilities for a financial dashboard.

  • formatCurrency(amount, currency = 'GHS')'GHS 1,250.00'
  • formatCompact(n)1.2K, 2.5M, 1.1B
  • formatPercent(value, total)'45.3%'
  • addCommas(n)'1,250,000' (manual implementation, no Intl)
✓ Success Criteria
  • formatCurrency(1250)'GHS 1,250.00'
  • formatCompact(1500)'1.5K'
  • formatPercent(3, 8)'37.5%'
  • addCommas(1000000)'1,000,000'
EX 08
SMS Message Generator
Challenging
+
Task

Build an SMS message system for a school notification platform.

  • Write buildSMS(template, data) — takes a template string with {placeholders} and an object, returns the filled message
  • Templates: result notification, fee reminder, event invitation
  • SMS limit: 160 chars per message. If longer, split into multiple parts and label them (1/2), (2/2)
  • Strip HTML tags from any template content before sending
✓ Success Criteria
  • buildSMS('Hello {name}, your score is {score}.', { name: 'Ama', score: 78 })'Hello Ama, your score is 78.'
  • 160+ char message splits correctly with part labels
  • HTML tags stripped: 'Hello <b>Ama</b>''Hello Ama'
Chapter 11

Async JavaScript & Fetch

Real applications communicate with servers. Practice async/await, fetching data, handling errors, and showing loading states — the skills used in every modern web application.

8 Exercises
EX 01
Your First Fetch
Easy
+
Task

Fetch data from a public API and display it on the page.

  • Fetch a single post from: https://jsonplaceholder.typicode.com/posts/1
  • Use async/await inside an async function
  • Log the response object, then log the parsed JSON
  • Display the post title and body on the page in an <article> element
✓ Success Criteria
  • Data fetched successfully (no errors in console)
  • Title and body appear on the page
  • Code uses async/await — not .then()
EX 02
Loading Spinner
Easy
+
Task

Show a loading state while data is being fetched.

  • Create a spinner element and a container for content
  • Before the fetch: show the spinner, hide the content
  • After the fetch: hide the spinner, show the content
  • Use a finally block to always hide the spinner, even on error
  • Fetch 10 posts from /posts?_limit=10 and render them as a list
✓ Success Criteria
  • Spinner visible while loading (may be brief)
  • Spinner always removed — even if the fetch fails
  • 10 posts listed correctly
EX 03
Error Handling
Easy
+
Task

Handle errors gracefully — network failures and HTTP error codes.

  • Fetch from a URL that returns 404: /posts/99999
  • Check response.ok and throw a custom error if false
  • Catch network errors separately from HTTP errors
  • Display a user-friendly error message on the page — not in the console
  • Add a Retry button that attempts the fetch again
✓ Success Criteria
  • 404 response shows: 'Error: Post not found (404)' on page
  • Error shown in the UI — not just console
  • Retry button attempts the fetch again
  • No unhandled promise rejections in console
EX 04
Parallel Fetches
Medium
+
Task

Fetch multiple resources simultaneously using Promise.all.

  • Fetch user #1 and their posts at the same time (parallel, not sequential)
  • Show how sequential await is slower than Promise.all
  • Use console.time and console.timeEnd to compare both approaches
  • Render: user name, user email, and their 10 most recent post titles
✓ Success Criteria
  • Both URLs fetched in parallel with Promise.all
  • Console shows time measurements proving parallel is faster
  • User name, email, and posts all displayed correctly
EX 05
POST Request
Medium
+
Task

Send data to a server using a POST request.

  • Build a form with: title and body fields
  • On submit, POST the data to https://jsonplaceholder.typicode.com/posts
  • Include the correct headers: Content-Type: application/json
  • Serialise the form data with JSON.stringify
  • Display the server's response (the created post with its new ID)
✓ Success Criteria
  • Form submission sends correct JSON body
  • Response shows the new post with id: 101 (JSONPlaceholder always returns this)
  • Success message displays the new post ID
  • Form clears after successful submission
EX 06
Search with Debounce
Medium
+
Task

Build a live search that fetches results as the user types — efficiently.

  • Search input that fetches users matching the query from /users
  • Filter the results client-side by name containing the query (JSONPlaceholder does not support server search)
  • Debounce the search by 400ms — only fetch after the user stops typing
  • Show a loading state during the fetch
  • Show 'No results' if the filter returns an empty array
✓ Success Criteria
  • Only one fetch per pause in typing — not one per keystroke
  • Results filter correctly as query changes
  • Empty query shows all users
  • 'No results' shown for a query with no matches
EX 07
Infinite Scroll
Medium
+
Task

Load more posts automatically as the user scrolls to the bottom of the page.

  • Start by loading the first 10 posts
  • Use IntersectionObserver on a sentinel element at the page bottom
  • When the sentinel becomes visible: load the next 10 posts
  • Show a spinner while loading each page of posts
  • Stop loading when all 100 posts have been fetched
✓ Success Criteria
  • First 10 posts load on page load
  • Scrolling to the bottom loads 10 more automatically
  • Spinner appears during each load
  • After all 100 posts: shows 'All posts loaded' — no more loads
EX 08
Full CRUD Dashboard
Challenging
+
Task

Build a complete Create/Read/Update/Delete interface using the JSONPlaceholder API.

  • Read: Fetch and display all posts in a table
  • Create: A form that POSTs a new post and adds it to the top of the table
  • Update: Click a row to edit inline — PUT on save
  • Delete: A delete button on each row — DELETE request, then remove the row
  • Show a toast notification for every successful operation
✓ Success Criteria
  • All four CRUD operations work correctly
  • Each sends the correct HTTP method (GET, POST, PUT, DELETE)
  • UI updates immediately after each operation — no page reload
  • Toast messages: 'Post created', 'Post updated', 'Post deleted'
  • All errors handled gracefully with user-facing messages
Chapter 12

Best Practices & Clean Code

Writing code that works is the first step. Writing code that is readable, maintainable, and professional is the next. These exercises focus on habits that distinguish experienced developers from beginners.

8 Exercises
EX 01
Refactor the Mess
Easy
+
Task

Rewrite this bad code following all best practices from the chapter.

// BAD — rewrite this
var x = 'Ama';
var y = 22;
function d(a, b) {
  var z = a + b;
  if (z == 100) { return true; }
  else { return false; }
}
  • Replace all var with const or let
  • Give all variables and functions descriptive names
  • Replace == with ===
  • Simplify the if/else to a single expression
✓ Success Criteria
  • No var in the rewritten code
  • All names are descriptive and self-documenting
  • Function body is one line: return total === target
  • Function works identically to the original
EX 02
Guard Clauses
Easy
+
Task

Rewrite deeply nested if/else using guard clauses to exit early.

// BAD — deeply nested
function processOrder(order) {
  if (order) {
    if (order.paid) {
      if (order.items.length > 0) {
        return 'Processing';
      }
    }
  }
  return 'Cannot process';
}
  • Rewrite using guard clauses — return early for each failing condition
  • The happy path (success) should be the last thing in the function
  • Add a different error message for each failed guard
✓ Success Criteria
  • No nested if blocks in the rewritten version
  • Each guard returns a specific message: 'No order', 'Order not paid', 'No items'
  • Function produces identical results to the original for all inputs
EX 03
DRY Principle
Easy
+
Task

Identify and eliminate the repeated logic in this code.

// BAD — repeated code
const tax10  = (p) => p + (p * 0.10);
const tax15  = (p) => p + (p * 0.15);
const tax20  = (p) => p + (p * 0.20);
const tax25  = (p) => p + (p * 0.25);
  • Replace the four functions with a single addTax(price, rate) function
  • Or create a factory: const makeTax = (rate) => (price) => price + (price * rate)
  • Prove the behaviour is identical
✓ Success Criteria
  • Repeated code reduced to a single reusable function
  • addTax(100, 0.15)115
  • If the tax formula ever needs to change, you only change it in one place
EX 04
Error Handling Everywhere
Medium
+
Task

Add proper error handling to code that currently crashes silently.

  • Write parseUserData(jsonString) — parse JSON safely. Return null if invalid, not a crash
  • Write divide(a, b) — throw a descriptive Error if b is 0
  • Write getElement(id) — throw if the element does not exist in the DOM
  • Test each with both valid and invalid inputs. All errors should be caught and logged — nothing should crash
✓ Success Criteria
  • parseUserData('bad json')null (no crash)
  • divide(10, 0) throws Error: Cannot divide by zero
  • getElement('non-existent') throws Error: Element #non-existent not found
  • All errors are caught and logged — no unhandled exceptions
EX 05
Write Pure Functions
Medium
+
Task

Identify impure functions and rewrite them as pure functions.

// IMPURE — modifies external state
let total = 0;
function addToTotal(amount) { total += amount; }

// IMPURE — mutates the input array
function addItem(arr, item) { arr.push(item); return arr; }
  • Rewrite addToTotal as a pure function: takes current total and amount, returns new total
  • Rewrite addItem as pure: returns a new array with the item — does not mutate the original
  • Prove the original array is unchanged after calling the pure version
✓ Success Criteria
  • Pure addToTotal(100, 50)150, no external variable modified
  • Pure addItem([1,2,3], 4)[1,2,3,4], original array unchanged
  • Same inputs always produce same outputs — no side effects
EX 06
Async Best Practices
Medium
+
Task

Rewrite sequential async code as parallel and add proper error handling.

// BAD — sequential when parallel is possible
async function loadDashboard() {
  const user  = await fetchUser();
  const posts = await fetchPosts();
  const stats = await fetchStats();
}
  • Rewrite using Promise.all to fetch all three simultaneously
  • Add try/catch with a meaningful error message in the UI
  • Add a loading state that shows before the fetches start
  • Use console.time to prove the parallel version is faster
✓ Success Criteria
  • All three fetches fire simultaneously
  • Time measurement shows parallel is faster than sequential
  • Error shown in UI if any fetch fails
  • Loading state appears before and disappears after
EX 07
Defensive Programming
Medium
+
Task

Harden these functions so they cannot crash regardless of what they receive.

  • Write getFirstName(user) — safely returns the first name even if user is null, undefined, or has no name property. Use optional chaining and nullish coalescing.
  • Write sumArray(arr) — returns 0 if arr is not an array, filters out non-numbers before summing
  • Write renderList(items, container) — validates both arguments, handles empty arrays, no innerHTML
✓ Success Criteria
  • getFirstName(null)'Unknown' (not a crash)
  • sumArray('hello')0
  • sumArray([1, 'x', 2, null, 3])6
  • renderList([], container) shows 'No items to display'
EX 08
Code Review
Challenging
+
Task

Review and completely rewrite this function — it works, but it has serious problems.

var d = [];
function p(i) {
  var x = document.getElementById(i);
  if (x != null) {
    var v = x.value;
    if (v != null && v != '') {
      d.push(v);
      return true;
    } else { return false; }
  } else { return false; }
}
  • List every problem (at least 6 issues)
  • Rewrite completely: descriptive names, const/let, guard clauses, no global mutation, proper error handling
  • The function should be pure — take the element ID and a data array as parameters, return a new array
✓ Success Criteria
  • At least 6 problems identified in comments
  • No var, no single-letter names, no global variables
  • Uses guard clauses instead of nested if/else
  • Pure function: same inputs → same output, no side effects
  • Rewritten version is clearly more readable

Ready for the project?

You have completed all 96 exercises. Now build the full capstone project to prove your skills.

View Capstone Project →