These exercises are designed to help you practice and solidify your understanding of fundamental to intermediate JavaScript concepts. Work through them step by step, and don’t hesitate to experiment with the code!
Section 1: Fundamentals (Exercises 1-10)
Exercise 1: Declare and Assign Variables
- Skills Highlighted: Variable declaration (let, const), variable assignment, basic data types (string, number).
- Problem Statement: Declare two variables: one named userName and assign it your name (as a string), and another named favoriteNumber and assign it your favorite number (as a number). Then, print both variables to the console.
- Step-by-step Solution:
- Declare userName using let and assign your name.
- Declare favoriteNumber using const and assign your favorite number.
- Use console.log() to display the value of userName.
- Use console.log() to display the value of favoriteNumber.
- Code:
// Exercise 1: Declare and Assign Variables
let userName = “Alice”; // Declaring a string variable
const favoriteNumber = 7; // Declaring a number constant
console.log(“User Name:”, userName);
console.log(“Favorite Number:”, favoriteNumber); - Explanation:
- let is used for variables whose values might change.
- const is used for variables whose values will not change after initialization. It’s good practice to use const by default and switch to let only if reassigning is necessary.
- “Alice” is a string literal (text).
- 7 is a number literal.
- console.log() is a built-in JavaScript function used for outputting messages to the web console or terminal.
Exercise 2: Basic Arithmetic Operations
- Skills Highlighted: Arithmetic operators (+, -, *, /), variable assignment, numbers.
- Problem Statement: Given two numbers, num1 = 10 and num2 = 5, calculate their sum, difference, product, and quotient. Print each result to the console with a descriptive label.
- Step-by-step Solution:
- Declare num1 and num2.
- Calculate the sum and store it in a variable sum.
- Calculate the difference and store it in difference.
- Calculate the product and store it in product.
- Calculate the quotient and store it in quotient.
- Print all results.
- Code:
// Exercise 2: Basic Arithmetic Operations
let num1 = 10;
let num2 = 5;
let sum = num1 + num2;
let difference = num1 – num2;
let product = num1 * num2;
let quotient = num1 / num2;
console.log(“Sum:”, sum);
console.log(“Difference:”, difference);
console.log(“Product:”, product);
console.log(“Quotient:”, quotient); - Explanation:
- JavaScript supports standard arithmetic operators.
- + for addition, – for subtraction, * for multiplication, / for division.
- The results of these operations are stored in new variables and then logged.
Exercise 3: String Concatenation
- Skills Highlighted: String data type, string concatenation (+ operator, template literals).
- Problem Statement: Declare two string variables: firstName = “John” and lastName = “Doe”. Create a third variable fullName that combines firstName and lastName with a space in between. Print fullName.
- Step-by-step Solution:
- Declare firstName and lastName.
- Concatenate them to form fullName.
- Print fullName.
- Code:
// Exercise 3: String Concatenation
let firstName = “John”;
let lastName = “Doe”;
// Method 1: Using the + operator
let fullName = firstName + ” ” + lastName;
console.log(“Full Name (using +):”, fullName);
// Method 2: Using template literals (recommended for complex strings)
let fullGreeting = `Hello, my name is ${firstName} ${lastName}!`;
console.log(“Full Greeting (using template literal):”, fullGreeting); - Explanation:
- The + operator can concatenate strings.
- Template literals (using backticks `) allow for embedding expressions directly within strings using ${expression}. This is often cleaner for creating complex strings.
Exercise 4: Conditional Statement (If/Else)
- Skills Highlighted: Conditional logic (if, else), comparison operators (>, <), boolean expressions.
- Problem Statement: Declare a variable age and set it to a number. Write an if-else statement that checks if the age is 18 or older. If it is, print “You are an adult.” Otherwise, print “You are a minor.”
- Step-by-step Solution:
- Declare age.
- Use an if statement to check age >= 18.
- If true, print the adult message.
- Use an else block for the false case and print the minor message.
- Code:
// Exercise 4: Conditional Statement (If/Else)
let age = 20; // Try changing this value to 16, 18, 25
if (age >= 18) {
console.log(“You are an adult.”);
} else {
console.log(“You are a minor.”);
}
let anotherAge = 16;
if (anotherAge >= 18) {
console.log(“You are an adult. (for anotherAge)”);
} else {
console.log(“You are a minor. (for anotherAge)”);
} - Explanation:
- The if statement evaluates a condition (an expression that results in true or false).
- If the condition is true, the code inside the if block executes.
- If the condition is false, the code inside the else block executes.
- >= is the “greater than or equal to” comparison operator.
Exercise 5: Conditional Statement (If/Else If/Else)
- Skills Highlighted: if-else if-else chain, multiple conditions, logical operators (&&, ||).
- Problem Statement: Declare a variable score and set it to a number between 0 and 100. Write a series of if-else if-else statements to assign a grade based on the score:
- 90-100: “A”
- 80-89: “B”
- 70-79: “C”
- 60-69: “D”
- Below 60: “F”
Print the assigned grade. - Step-by-step Solution:
- Declare score.
- Start with if (score >= 90).
- Add else if (score >= 80).
- Continue with else if for each grade range.
- Use a final else for scores below 60.
- Code:
// Exercise 5: Conditional Statement (If/Else If/Else)
let score = 85; // Try changing this value (e.g., 95, 72, 55)
let grade;
if (score >= 90) {
grade = “A”;
} else if (score >= 80) {
grade = “B”;
} else if (score >= 70) {
grade = “C”;
} else if (score >= 60) {
grade = “D”;
} else {
grade = “F”;
}
console.log(`With a score of ${score}, your grade is: ${grade}`); - Explanation:
- The if-else if-else structure allows you to check multiple conditions sequentially.
- Once a condition evaluates to true, its corresponding block is executed, and the rest of the else if and else blocks are skipped.
- The order of conditions matters; always put the most specific conditions (e.g., higher score ranges) first.
Exercise 6: For Loop (Basic Iteration)
- Skills Highlighted: for loop, iteration, increment operator (++).
- Problem Statement: Use a for loop to print numbers from 1 to 10, each on a new line.
- Step-by-step Solution:
- Initialize the loop counter i to 1.
- Set the loop condition to i <= 10.
- Increment i by 1 in each iteration.
- Inside the loop, print the value of i.
- Code:
// Exercise 6: For Loop (Basic Iteration)
console.log(“Numbers from 1 to 10:”);
for (let i = 1; i <= 10; i++) {
console.log(i);
} - Explanation:
- A for loop has three parts in its parentheses:
- Initialization: let i = 1; (executed once at the beginning).
- Condition: i <= 10; (checked before each iteration; if true, loop continues).
- Increment/Decrement: i++ (executed after each iteration).
Exercise 7: While Loop (Conditional Iteration)
- Skills Highlighted: while loop, conditional iteration, decrement operator (–).
- Problem Statement: Use a while loop to count down from 5 to 1. Print each number.
- Step-by-step Solution:
- Declare a variable count and initialize it to 5.
- Set the while loop condition to count >= 1.
- Inside the loop, print count.
- Decrement count by 1 in each iteration.
- Code:
// Exercise 7: While Loop (Conditional Iteration)
let count = 5;
console.log(“Countdown from 5:”);
while (count >= 1) {
console.log(count);
count–; // Decrement count by 1
}
console.log(“Blast off!”); - Explanation:
- A while loop continues to execute its block of code as long as its specified condition remains true.
- It’s crucial to ensure that the condition eventually becomes false to avoid an infinite loop. In this case, count– ensures count will eventually be less than 1.
Exercise 8: Simple Function Definition and Call
- Skills Highlighted: Function declaration, function parameters, function arguments, function call.
- Problem Statement: Create a function named greet that takes one argument, name. The function should print a greeting message like “Hello, [name]!”. Call this function with your name.
- Step-by-step Solution:
- Define the function greet using the function keyword, taking name as a parameter.
- Inside the function, use console.log() to print the greeting using a template literal.
- Call the greet function, passing your name as an argument.
- Code:
// Exercise 8: Simple Function Definition and Call
function greet(name) {
console.log(`Hello, ${name}!`);
}
// Call the function
greet(“Alice”);
greet(“Bob”);
greet(“Charlie”); - Explanation:
- Functions are reusable blocks of code.
- name inside the parentheses of function greet(name) is a parameter – a placeholder for a value that will be passed into the function.
- When you call greet(“Alice”), “Alice” is an argument – the actual value passed to the function.
- The code inside the function’s curly braces {} is executed when the function is called.
Exercise 9: Function with Return Value
- Skills Highlighted: Function return values, arithmetic operations within functions.
- Problem Statement: Create a function named addNumbers that takes two arguments, a and b. The function should return their sum. Call this function and store the result in a variable, then print the variable.
- Step-by-step Solution:
- Define the function addNumbers with parameters a and b.
- Inside the function, calculate a + b.
- Use the return keyword to send the sum back as the function’s result.
- Call addNumbers and assign its return value to a new variable.
- Print the variable.
- Code:
// Exercise 9: Function with Return Value
function addNumbers(a, b) {
return a + b; // Returns the sum of a and b
}
// Call the function and store its result
let result1 = addNumbers(5, 3);
console.log(“Sum of 5 and 3:”, result1); // Expected: 8
let result2 = addNumbers(100, 20);
console.log(“Sum of 100 and 20:”, result2); // Expected: 120 - Explanation:
- The return statement specifies the value a function sends back when it finishes execution.
- When a function is called, it evaluates to its return value. If no return statement is present, the function implicitly returns undefined.
Exercise 10: Introduction to Arrays
- Skills Highlighted: Array declaration, accessing array elements (by index), push() method, length property.
- Problem Statement: Create an array called fruits containing “Apple”, “Banana”, and “Orange”.
- Print the entire fruits array.
- Print the first fruit in the array.
- Print the last fruit in the array.
- Add “Grape” to the end of the fruits array.
- Print the updated array.
- Step-by-step Solution:
- Declare fruits array with initial values.
- Print the array.
- Access first element using [0].
- Access last element using [fruits.length – 1].
- Use fruits.push(“Grape”).
- Print the array again.
- Code:
// Exercise 10: Introduction to Arrays
let fruits = [“Apple”, “Banana”, “Orange”];
console.log(“Original fruits array:”, fruits);
// Accessing elements by index (arrays are zero-indexed)
console.log(“First fruit:”, fruits[0]); // “Apple”
console.log(“Second fruit:”, fruits[1]); // “Banana”
console.log(“Third fruit:”, fruits[2]); // “Orange”
// Accessing the last element using .length property
console.log(“Last fruit:”, fruits[fruits.length – 1]); // “Orange”
// Adding an element to the end of the array
fruits.push(“Grape”);
console.log(“Fruits array after adding ‘Grape’:”, fruits);
console.log(“New last fruit:”, fruits[fruits.length – 1]); // “Grape”
console.log(“Total number of fruits:”, fruits.length); // 4 - Explanation:
- Arrays are ordered collections of data. They are declared using square brackets [].
- Elements are accessed by their index, starting from 0. So, the first element is at index 0, the second at 1, and so on.
- The length property gives the number of elements in the array.
- The push() method adds one or more elements to the end of an array.
Section 2: Intermediate Concepts (Exercises 11-20)
Exercise 11: Iterating Over an Array with For Loop
- Skills Highlighted: Array iteration, for loop, length property.
- Problem Statement: Given an array of numbers numbers = [10, 20, 30, 40, 50], use a for loop to print each number in the array.
- Step-by-step Solution:
- Declare the numbers array.
- Set up a for loop with a counter i starting from 0.
- The loop condition should be i < numbers.length (to go through all valid indices).
- Inside the loop, access numbers[i] and print it.
- Code:
// Exercise 11: Iterating Over an Array with For Loop
let numbers = [10, 20, 30, 40, 50];
console.log(“Numbers in the array:”);
for (let i = 0; i < numbers.length; i++) {
console.log(numbers[i]);
} - Explanation:
- Looping through an array with a for loop is a common pattern.
- The loop counter i typically starts at 0 (the first index) and goes up to, but not including, array.length. This ensures all elements from index 0 to length – 1 are visited.
Exercise 12: Iterating Over an Array with For…of Loop
- Skills Highlighted: for…of loop, array iteration (modern approach).
- Problem Statement: Given the same array of numbers numbers = [10, 20, 30, 40, 50], use a for…of loop to print each number. Compare its simplicity with the traditional for loop.
- Step-by-step Solution:
- Declare the numbers array.
- Use a for (let num of numbers) loop.
- Inside the loop, num will directly represent each element. Print num.
- Code:
// Exercise 12: Iterating Over an Array with For…of Loop
let numbers = [10, 20, 30, 40, 50];
console.log(“Numbers in the array (using for…of):”);
for (let number of numbers) { // ‘number’ here directly gets each element’s value
console.log(number);
} - Explanation:
- The for…of loop is a more modern and often preferred way to iterate over iterable objects like arrays.
- It directly provides the value of each element in each iteration, removing the need to manage an index explicitly. This makes the code cleaner and less error-prone when you only need the element values.
Exercise 13: Array Method: forEach()
- Skills Highlighted: Array forEach() method, callback functions, anonymous functions.
- Problem Statement: Given an array of names names = [“Alice”, “Bob”, “Charlie”], use the forEach() method to print a greeting for each name (e.g., “Hello, Alice!”).
- Step-by-step Solution:
- Declare the names array.
- Call names.forEach().
- Pass a callback function to forEach(). This function will be executed for each element.
- The callback function should take one argument (the current name).
- Inside the callback, print the greeting.
- Code:
// Exercise 13: Array Method: forEach()
let names = [“Alice”, “Bob”, “Charlie”];
console.log(“Greetings:”);
names.forEach(function(name) { // The function here is a “callback”
console.log(`Hello, ${name}!`);
});
// Using arrow function syntax (more common in modern JS)
console.log(“\nGreetings (using arrow function):”);
names.forEach(name => {
console.log(`Hi there, ${name}.`);
}); - Explanation:
- The forEach() method executes a provided function once for each array element.
- The function passed to forEach() is called a callback function.
- The callback function automatically receives the current element as its first argument (and optionally the index and the array itself).
- Arrow functions (name => { … }) provide a more concise syntax for writing callback functions.
Exercise 14: Object Basics
- Skills Highlighted: Object creation, properties, dot notation, bracket notation, accessing object values.
- Problem Statement: Create an object named person with the following properties:
- name: “John Doe”
- age: 30
- isStudent: false
Print the person’s name using dot notation, their age using bracket notation, and their student status. - Step-by-step Solution:
- Declare the person object using curly braces {}.
- Define the properties and their values.
- Access name using person.name.
- Access age using person[“age”].
- Access isStudent using person.isStudent.
- Print all values.
- Code:
// Exercise 14: Object Basics
let person = {
name: “John Doe”,
age: 30,
isStudent: false,
“favorite color”: “blue” // Property with a space in its name
};
console.log(“Person’s details:”);
console.log(“Name:”, person.name); // Accessing with dot notation
console.log(“Age:”, person[“age”]); // Accessing with bracket notation
console.log(“Is Student:”, person.isStudent);
// Accessing property with a space in its name (only bracket notation works)
console.log(“Favorite Color:”, person[“favorite color”]);
// Modifying a property
person.age = 31;
console.log(“Updated Age:”, person.age);
// Adding a new property
person.city = “New York”;
console.log(“City:”, person.city);
console.log(“Updated Person object:”, person); - Explanation:
- Objects are unordered collections of key-value pairs (properties).
- Dot notation (object.property) is generally preferred for accessing properties when the property name is a valid identifier (no spaces, special characters, etc.).
- Bracket notation (object[“property”]) is required when property names contain spaces or special characters, or when the property name is dynamic (e.g., stored in a variable).
Exercise 15: Function with Object as Argument
- Skills Highlighted: Passing objects to functions, object destructuring (optional but good to introduce).
- Problem Statement: Create a function displayBookInfo that takes a book object as an argument. The book object should have title and author properties. The function should print a message like “Book: [Title] by [Author]”. Call this function with a sample book object.
- Step-by-step Solution:
- Define the displayBookInfo function that accepts one parameter, book.
- Inside the function, access book.title and book.author to form the message.
- Create a sample book object.
- Call displayBookInfo with the sample object.
- Code:
// Exercise 15: Function with Object as Argument
function displayBookInfo(book) {
console.log(`Book: ${book.title} by ${book.author}`);
}
// Optional: Using object destructuring in function parameters
function displayBookInfoDestructured({ title, author }) {
console.log(`Book (destructured): ${title} by ${author}`);
}
let myBook = {
title: “The Great Gatsby”,
author: “F. Scott Fitzgerald”,
year: 1925
};
let anotherBook = {
title: “1984”,
author: “George Orwell”,
pages: 328
};
displayBookInfo(myBook);
displayBookInfo(anotherBook); // Even if ‘pages’ is present, function only uses ‘title’ and ‘author’
displayBookInfoDestructured(myBook); - Explanation:
- Objects can be passed as arguments to functions, allowing you to encapsulate related data.
- Object destructuring (e.g., ({ title, author })) is a powerful syntax that allows you to “unpack” properties from objects into distinct variables. This can make function signatures and code cleaner when you only need specific properties.
Exercise 16: Array of Objects
- Skills Highlighted: Arrays of objects, iterating through arrays of objects, accessing properties.
- Problem Statement: Create an array named students. Each element in students should be an object with name and grade properties. Add at least three student objects to the array. Then, loop through the students array and print each student’s name and grade.
- Step-by-step Solution:
- Declare the students array, populating it with objects.
- Use a for…of loop (or forEach) to iterate over the students array.
- Inside the loop, access student.name and student.grade for each student object.
- Print the information.
- Code:
// Exercise 16: Array of Objects
let students = [
{ name: “Alice”, grade: 92 },
{ name: “Bob”, grade: 78 },
{ name: “Charlie”, grade: 85 },
{ name: “Diana”, grade: 60 }
];
console.log(“Student Grades:”);
for (let student of students) {
console.log(`${student.name}: ${student.grade}`);
}
// Using forEach
console.log(“\nStudent Grades (using forEach):”);
students.forEach(student => {
console.log(`- ${student.name} got a ${student.grade}`);
}); - Explanation:
- Arrays can hold any data type, including objects. This is a very common structure for representing collections of structured data.
- You iterate through the array to get each object, and then access the properties of that object.
Exercise 17: Basic String Methods
- Skills Highlighted: String length, toUpperCase(), toLowerCase(), indexOf(), slice().
- Problem Statement: Given a string message = “Hello JavaScript World”.
- Print the length of the string.
- Print the string in uppercase.
- Print the string in lowercase.
- Find the index of the word “JavaScript”.
- Extract the substring “JavaScript”.
- Step-by-step Solution:
- Declare message.
- Use message.length.
- Use message.toUpperCase().
- Use message.toLowerCase().
- Use message.indexOf(“JavaScript”).
- Use message.slice() with appropriate start and end indices.
- Code:
// Exercise 17: Basic String Methods
let message = “Hello JavaScript World”;
console.log(“Original Message:”, message);
console.log(“Length of message:”, message.length); // 22
console.log(“Uppercase:”, message.toUpperCase()); // “HELLO JAVASCRIPT WORLD”
console.log(“Lowercase:”, message.toLowerCase()); // “hello javascript world”
// Finding the index of a substring
let jsIndex = message.indexOf(“JavaScript”);
console.log(“Index of ‘JavaScript’:”, jsIndex); // 6 (index where ‘J’ starts)
// Extracting a substring using slice()
// slice(startIndex, endIndex – 1)
let extractedWord = message.slice(jsIndex, jsIndex + “JavaScript”.length);
console.log(“Extracted word:”, extractedWord); // “JavaScript”
// Check if a string includes a substring
console.log(“Does message include ‘World’?”, message.includes(“World”)); // true - Explanation:
- Strings in JavaScript have many built-in methods for manipulation.
- .length is a property, not a method, so no parentheses.
- toUpperCase() and toLowerCase() return new strings.
- indexOf() returns the starting index of the first occurrence of a substring, or -1 if not found.
- slice(start, end) extracts a portion of a string. The end index is exclusive (the character at end is not included).
Exercise 18: Function to Reverse a String
- Skills Highlighted: String iteration, string concatenation, loop, string methods.
- Problem Statement: Create a function reverseString that takes a string as an argument and returns the reversed string. For example, reverseString(“hello”) should return “olleh”.
- Step-by-step Solution:
- Define reverseString with a str parameter.
- Initialize an empty string reversedStr.
- Loop backwards through the input str (from str.length – 1 down to 0).
- In each iteration, append the current character str[i] to reversedStr.
- Return reversedStr.
- Code:
// Exercise 18: Function to Reverse a String
function reverseString(str) {
let reversedStr = “”;
for (let i = str.length – 1; i >= 0; i–) {
reversedStr += str[i]; // Append character to the reversed string
}
return reversedStr;
}
// Alternative using array methods (more advanced, but common)
function reverseStringMethod(str) {
return str.split(”).reverse().join(”);
}
console.log(“Reversed ‘hello’:”, reverseString(“hello”)); // Expected: “olleh”
console.log(“Reversed ‘world’:”, reverseString(“world”)); // Expected: “dlrow”
console.log(“Reversed ‘JavaScript’:”, reverseString(“JavaScript”)); // Expected: “tpircSavaJ”
console.log(“Reversed ‘hello’ (method):”, reverseStringMethod(“hello”)); - Explanation:
- This exercise combines string indexing with loop iteration.
- Strings can be treated like arrays of characters for indexing purposes.
- The += operator is a shorthand for reversedStr = reversedStr + str[i].
- The alternative method split(”).reverse().join(”) is a powerful one-liner:
- split(”) converts the string into an array of individual characters.
- reverse() reverses the order of elements in that array.
- join(”) joins the characters back into a single string.
Exercise 19: Find the Largest Number in an Array
- Skills Highlighted: Array iteration, conditional logic, comparison, maintaining a max value.
- Problem Statement: Create a function findLargestNumber that takes an array of numbers as an argument and returns the largest number in that array. Assume the array is not empty.
- Step-by-step Solution:
- Define findLargestNumber with a numbers array parameter.
- Initialize a variable largest with the first element of the array (numbers[0]).
- Loop through the rest of the array (starting from index 1).
- Inside the loop, if the current number numbers[i] is greater than largest, update largest to numbers[i].
- Return largest after the loop finishes.
- Code:
// Exercise 19: Find the Largest Number in an Array
function findLargestNumber(numbers) {
if (numbers.length === 0) {
return undefined; // Or throw an error, or return a specific value
}
let largest = numbers[0]; // Assume the first element is the largest initially
for (let i = 1; i < numbers.length; i++) { // Start from the second element
if (numbers[i] > largest) {
largest = numbers[i]; // Update largest if current number is greater
}
}
return largest;
}
console.log(“Largest in [3, 8, 1, 12, 5]:”, findLargestNumber([3, 8, 1, 12, 5])); // Expected: 12
console.log(“Largest in [100, 20, 30]:”, findLargestNumber([100, 20, 30])); // Expected: 100
console.log(“Largest in [7]:”, findLargestNumber([7])); // Expected: 7
console.log(“Largest in []:”, findLargestNumber([])); // Expected: undefined (due to added check) - Explanation:
- This problem requires iterating through an array and keeping track of the maximum value encountered so far.
- The initial largest value is set to the first element to ensure it has a starting point for comparison.
- The loop then compares largest with every subsequent element, updating it if a larger number is found.
- A check for an empty array is added for robustness, preventing an error if numbers[0] is accessed on an empty array.
Exercise 20: Calculate the Sum of Array Elements
- Skills Highlighted: Array iteration, aggregation (summation), for loop or forEach.
- Problem Statement: Create a function calculateSum that takes an array of numbers as an argument and returns the sum of all its elements.
- Step-by-step Solution:
- Define calculateSum with a numbers array parameter.
- Initialize a variable totalSum to 0.
- Loop through the numbers array.
- In each iteration, add the current number to totalSum.
- Return totalSum after the loop.
- Code:
// Exercise 20: Calculate the Sum of Array Elements
function calculateSum(numbers) {
let totalSum = 0; // Initialize sum to zero
for (let i = 0; i < numbers.length; i++) {
totalSum += numbers[i]; // Add current number to totalSum
}
return totalSum;
}
// Alternative using for…of loop
function calculateSumForOf(numbers) {
let totalSum = 0;
for (let num of numbers) {
totalSum += num;
}
return totalSum;
}
// Alternative using reduce() (more advanced)
function calculateSumReduce(numbers) {
return numbers.reduce((accumulator, currentValue) => accumulator + currentValue, 0);
}
console.log(“Sum of [1, 2, 3, 4, 5]:”, calculateSum([1, 2, 3, 4, 5])); // Expected: 15
console.log(“Sum of [10, 20, 30]:”, calculateSumForOf([10, 20, 30])); // Expected: 60
console.log(“Sum of []:”, calculateSum([])); // Expected: 0
console.log(“Sum of [7, 8, 9] (reduce):”, calculateSumReduce([7, 8, 9])); // Expected: 24 - Explanation:
- This is a classic aggregation problem. You need a variable to accumulate the sum, initialized to 0.
- Each element is added to this accumulator.
- The reduce() method (introduced briefly) is a powerful array method for performing aggregations. It takes a callback function and an initial value. The callback takes an accumulator and the currentValue, and returns the new accumulator value.
Section 3: Advanced Intermediate Concepts (Exercises 21-30)
Exercise 21: Array Method: filter()
- Skills Highlighted: filter() method, callback functions, conditional logic, array immutability.
- Problem Statement: Given an array of numbers data = [10, 25, 30, 45, 50, 65], use the filter() method to create a new array containing only numbers greater than 40.
- Step-by-step Solution:
- Declare the data array.
- Call data.filter().
- Provide a callback function that takes number as an argument.
- Inside the callback, return true if number > 40, otherwise false.
- Store the result in a new variable and print it.
- Code:
// Exercise 21: Array Method: filter()
let data = [10, 25, 30, 45, 50, 65, 5, 80];
// Filter numbers greater than 40
let greaterThan40 = data.filter(function(number) {
return number > 40;
});
console.log(“Numbers greater than 40:”, greaterThan40); // Expected: [45, 50, 65, 80]
// Using arrow function syntax
let evenNumbers = data.filter(number => number % 2 === 0);
console.log(“Even numbers:”, evenNumbers); // Expected: [10, 30, 50, 80]
// Filtering objects in an array
let products = [
{ name: “Laptop”, price: 1200 },
{ name: “Mouse”, price: 25 },
{ name: “Keyboard”, price: 75 },
{ name: “Monitor”, price: 300 }
];
let expensiveProducts = products.filter(product => product.price > 100);
console.log(“Expensive products (> $100):”, expensiveProducts);
// Expected: [{ name: “Laptop”, price: 1200 }, { name: “Monitor”, price: 300 }] - Explanation:
- The filter() method creates a new array containing all elements for which the provided callback function returns true.
- It does not modify the original array (it’s “immutable” in this sense).
- The callback function for filter() typically takes the current element as an argument and must return a boolean value.
Exercise 22: Array Method: map()
- Skills Highlighted: map() method, callback functions, transforming arrays, array immutability.
- Problem Statement: Given an array of numbers prices = [10, 20, 30], use the map() method to create a new array where each price is doubled.
- Step-by-step Solution:
- Declare the prices array.
- Call prices.map().
- Provide a callback function that takes price as an argument.
- Inside the callback, return price * 2.
- Store the result in a new variable and print it.
- Code:
// Exercise 22: Array Method: map()
let prices = [10, 20, 30, 45];
// Double each price
let doubledPrices = prices.map(function(price) {
return price * 2;
});
console.log(“Doubled prices:”, doubledPrices); // Expected: [20, 40, 60, 90]
// Using arrow function syntax
let pricesWithTax = prices.map(price => price * 1.05); // Add 5% tax
console.log(“Prices with 5% tax:”, pricesWithTax);
// Mapping an array of objects to get specific properties
let users = [
{ id: 1, name: “Alice”, email: “alice@example.com” },
{ id: 2, name: “Bob”, email: “bob@example.com” },
{ id: 3, name: “Charlie”, email: “charlie@example.com” }
];
let userNames = users.map(user => user.name);
console.log(“User names:”, userNames); // Expected: [“Alice”, “Bob”, “Charlie”] - Explanation:
- The map() method creates a new array by calling a provided function on every element in the original array.
- It’s used for transforming elements from one form to another.
- Like filter(), map() does not modify the original array.
- The callback function for map() takes the current element and returns the new value for that element in the new array.
Exercise 23: Array Method: reduce()
- Skills Highlighted: reduce() method, callback functions, accumulator, aggregation.
- Problem Statement: Given an array of numbers items = [5, 10, 15], use the reduce() method to calculate the sum of all elements. (You already did this with a loop, now do it with reduce).
- Step-by-step Solution:
- Declare the items array.
- Call items.reduce().
- Provide a callback function that takes accumulator and currentValue as arguments.
- Inside the callback, return accumulator + currentValue.
- Provide an initial value for the accumulator (0 in this case) as the second argument to reduce().
- Store the result and print it.
- Code:
// Exercise 23: Array Method: reduce()
let items = [5, 10, 15, 20];
// Summing all numbers in an array
let sum = items.reduce(function(accumulator, currentValue) {
console.log(`Accumulator: ${accumulator}, Current Value: ${currentValue}`);
return accumulator + currentValue;
}, 0); // 0 is the initial value for the accumulator
console.log(“Sum of items:”, sum); // Expected: 50
// Using arrow function syntax (common)
let product = items.reduce((acc, val) => acc * val, 1); // Initial value for product is 1
console.log(“Product of items:”, product); // Expected: 5 * 10 * 15 * 20 = 15000
// Reducing an array of objects to a single value
let cart = [
{ item: “Shirt”, price: 25 },
{ item: “Jeans”, price: 60 },
{ item: “Socks”, price: 10 }
];
let totalCartPrice = cart.reduce((total, currentItem) => total + currentItem.price, 0);
console.log(“Total cart price:”, totalCartPrice); // Expected: 95 - Explanation:
- The reduce() method executes a “reducer” callback function on each element of the array, resulting in a single output value.
- accumulator: The value resulting from the previous call to callbackFn. On the first call, it’s the initialValue (or the first element if no initialValue is provided).
- currentValue: The value of the current element.
- initialValue (optional): A value to use as the first argument to the first call of the callbackFn. If not provided, the first element of the array is used as the initialValue and the currentValue starts from the second element. It’s often safer to provide an initialValue.
Exercise 24: Closures
- Skills Highlighted: Closures, nested functions, lexical environment, let vs var (briefly).
- Problem Statement: Create a function makeCounter() that returns another function. The inner function, when called, should increment a counter variable that is private to the makeCounter function’s scope and return the new count. Demonstrate how this counter persists across multiple calls to the returned function.
- Step-by-step Solution:
- Define makeCounter().
- Inside makeCounter, declare let count = 0;.
- Return an anonymous function from makeCounter().
- Inside the returned function, increment count and return count.
- Call makeCounter() to get an instance of the counter function (e.g., let counter1 = makeCounter();).
- Call counter1() multiple times and observe the output.
- Create another counter instance (let counter2 = makeCounter();) to show they are independent.
- Code:
// Exercise 24: Closures
function makeCounter() {
let count = 0; // ‘count’ is in the outer function’s scope
return function() { // This inner function forms a closure
count++; // It “remembers” and can access ‘count’ from its lexical environment
return count;
};
}
let counter1 = makeCounter(); // counter1 is now the inner function returned by makeCounter()
console.log(“Counter 1:”);
console.log(counter1()); // Expected: 1
console.log(counter1()); // Expected: 2
console.log(counter1()); // Expected: 3
let counter2 = makeCounter(); // A new, independent counter
console.log(“\nCounter 2:”);
console.log(counter2()); // Expected: 1 (starts fresh)
console.log(counter1()); // Expected: 4 (counter1 continues independently) - Explanation:
- A closure is a function bundled together with its lexical environment (the scope in which it was declared).
- The inner function return function() { … } “closes over” the count variable from its outer makeCounter function’s scope.
- Even after makeCounter() has finished executing, the count variable remains accessible and mutable by the returned inner function.
- Each call to makeCounter() creates a new, independent count variable and a new inner function that closes over that specific count. This is why counter1 and counter2 operate independently.
Exercise 25: Basic Object-Oriented Programming (Constructor Function / Class)
- Skills Highlighted: Constructor functions, this keyword, new keyword, instances, basic object-oriented concepts.
- Problem Statement: Create a Car constructor function (or ES6 Class) that takes make and model as arguments. Instances of Car should have these properties. Add a method displayInfo() to the Car prototype (or class) that prints “This is a [make] [model]”. Create two Car instances and call their displayInfo method.
- Step-by-step Solution (Constructor Function):
- Define function Car(make, model) { … }.
- Inside, use this.make = make; and this.model = model;.
- Add Car.prototype.displayInfo = function() { … };.
- Create instances using new Car(“Toyota”, “Camry”).
- Call instance.displayInfo().
- Code:
// Exercise 25: Basic Object-Oriented Programming (Constructor Function)
// Using a Constructor Function (traditional ES5 way)
function Car(make, model) {
this.make = make;
this.model = model;
}
// Add a method to the Car’s prototype
// This ensures all instances share the same method, saving memory
Car.prototype.displayInfo = function() {
console.log(`This is a ${this.make} ${this.model}.`);
};
// Create instances
let car1 = new Car(“Toyota”, “Camry”);
let car2 = new Car(“Honda”, “Civic”);
car1.displayInfo(); // Expected: “This is a Toyota Camry.”
car2.displayInfo(); // Expected: “This is a Honda Civic.”
// — Using ES6 Class Syntax (modern way, syntactic sugar over prototypes) —
class Motorcycle {
constructor(brand, type) {
this.brand = brand;
this.type = type;
}
displayDetails() {
console.log(`This is a ${this.brand} ${this.type} motorcycle.`);
}
}
let moto1 = new Motorcycle(“Harley-Davidson”, “Sportster”);
let moto2 = new Motorcycle(“Kawasaki”, “Ninja”);
moto1.displayDetails();
moto2.displayDetails(); - Explanation:
- Constructor Functions (ES5): Functions used with the new keyword to create objects. this inside a constructor refers to the new object being created.
- prototype: In JavaScript, every function and object has a prototype property. Methods added to Car.prototype are inherited by all Car instances, rather than being duplicated on each instance. This is more memory-efficient.
- Classes (ES6): The class keyword provides a cleaner, more familiar syntax for creating constructor functions and managing their prototypes. It’s syntactic sugar over the prototype-based inheritance. constructor is a special method for initialization, and other methods are automatically added to the prototype.
- new keyword: Creates a new empty object, sets this to point to it, calls the constructor, and then returns the new object.
Exercise 26: Asynchronous JavaScript – Callbacks (Simulated)
- Skills Highlighted: Asynchronous operations, callbacks, setTimeout().
- Problem Statement: Simulate an asynchronous operation. Create a function fetchUserData(userId, callback) that, after a simulated delay of 2 seconds (using setTimeout), calls the callback function with a user object { id: userId, name: “User ” + userId }. Demonstrate its usage.
- Step-by-step Solution:
- Define fetchUserData(userId, callback).
- Inside, use setTimeout(() => { … }, 2000).
- Inside setTimeout, create the user object.
- Call callback(user).
- Call fetchUserData with a userId and an anonymous function as the callback, which prints the user data.
- Code:
// Exercise 26: Asynchronous JavaScript – Callbacks (Simulated)
function fetchUserData(userId, callback) {
console.log(`Fetching data for user ID: ${userId}…`);
// Simulate an asynchronous operation (e.g., network request)
setTimeout(() => {
const user = {
id: userId,
name: `User ${userId}`,
email: `user${userId}@example.com`
};
console.log(`Data for user ${userId} received.`);
callback(user); // Execute the callback with the fetched data
}, 2000); // Simulate a 2-second delay
}
// How to use it:
console.log(“Starting data fetch for User 1.”);
fetchUserData(1, function(user) {
console.log(“Processing fetched user data:”);
console.log(“User Name:”, user.name);
console.log(“User Email:”, user.email);
});
console.log(“\nStarting data fetch for User 2.”);
fetchUserData(2, (user) => { // Using arrow function for callback
console.log(“Processing fetched user data for User 2:”);
console.log(“User ID:”, user.id);
});
console.log(“Requests initiated. This message appears first because the fetch is async.”);
// The “Requests initiated…” message will appear immediately,
// before the “Data received” messages, demonstrating asynchronicity. - Explanation:
- Asynchronous JavaScript: Code that doesn’t execute immediately or sequentially. Operations like network requests, reading files, or timers are asynchronous.
- setTimeout(callback, delay): A built-in browser/Node.js function that executes a callback function after a specified delay (in milliseconds). It doesn’t block the execution of the rest of the code.
- Callbacks: Functions that are passed as arguments to other functions and are executed later, typically when an asynchronous operation completes. This is a common pattern for handling results of async operations.
- Notice how “Requests initiated…” prints before the user data, because setTimeout schedules the callback for later but doesn’t pause the main script execution.
Exercise 27: Asynchronous JavaScript – Promises (Basic)
- Skills Highlighted: Promises (Promise, then(), catch()), asynchronous operations, improved error handling.
- Problem Statement: Rewrite the fetchUserData function from Exercise 26 to return a Promise instead of taking a callback. The promise should resolve with the user object after the 2-second delay. Demonstrate using .then() to handle success and .catch() for errors (simulate an error for a specific userId).
- Step-by-step Solution:
- Define fetchUserData(userId) to return a new Promise((resolve, reject) => { … }).
- Inside setTimeout:
- If userId is, say, 0, call reject(“User not found”).
- Otherwise, create the user object and call resolve(user).
- Call fetchUserData(1).then(user => { … }).catch(error => { … });.
- Call fetchUserData(0) to test the error path.
- Code:
// Exercise 27: Asynchronous JavaScript – Promises (Basic)
function fetchUserDataPromise(userId) {
console.log(`(Promise) Fetching data for user ID: ${userId}…`);
return new Promise((resolve, reject) => { // A Promise takes a function with resolve and reject
setTimeout(() => {
if (userId === 0) {
reject(“User with ID 0 not found.”); // Simulate an error
return;
}
const user = {
id: userId,
name: `Promise User ${userId}`,
status: “active”
};
console.log(`(Promise) Data for user ${userId} received.`);
resolve(user); // Resolve the promise with the user data
}, 2000);
});
}
// Using the Promise:
console.log(“— Fetching User 1 (Success Case) —“);
fetchUserDataPromise(1)
.then((user) => { // .then() is called when the promise resolves
console.log(“Success! User 1 Data:”, user);
console.log(`Resolved User 1 Name: ${user.name}`);
})
.catch((error) => { // .catch() is called when the promise rejects
console.error(“Error fetching User 1:”, error);
});
console.log(“\n— Fetching User 0 (Error Case) —“);
fetchUserDataPromise(0)
.then((user) => {
console.log(“Success! User 0 Data:”, user); // This block will NOT execute
})
.catch((error) => {
console.error(“Error fetching User 0:”, error); // This block WILL execute
});
console.log(“Promise requests initiated. This message appears first.”); - Explanation:
- A Promise is an object representing the eventual completion or failure of an asynchronous operation.
- It can be in one of three states:
- pending: Initial state, neither fulfilled nor rejected.
- fulfilled (resolved): The operation completed successfully.
- rejected: The operation failed.
- You create a new promise with new Promise((resolve, reject) => { … }).
- Call resolve(value) when the async operation succeeds.
- Call reject(error) when the async operation fails.
- .then(onFulfilled, onRejected): Attaches callbacks for the resolution and/or rejection of the Promise. onRejected is often separated into a .catch() block for readability.
- .catch(onRejected): Specifically handles errors (rejections).
Exercise 28: Recursion – Factorial Calculation
- Skills Highlighted: Recursion, base case, recursive step.
- Problem Statement: Write a recursive function factorial(n) that calculates the factorial of a given non-negative integer n. The factorial of n is the product of all positive integers less than or equal to n (e.g., 5! = 5 * 4 * 3 * 2 * 1 = 120). Assume n >= 0.
- Step-by-step Solution:
- Define factorial(n).
- Base Case: If n is 0 or 1, return 1 (because 0! = 1 and 1! = 1). This is crucial to stop the recursion.
- Recursive Step: Otherwise, return n * factorial(n – 1). This calls the function itself with a smaller input.
- Code:
// Exercise 28: Recursion – Factorial Calculation
function factorial(n) {
// Base case: When to stop the recursion
if (n === 0 || n === 1) {
return 1;
}
// Recursive step: Call the function itself with a smaller problem
else {
return n * factorial(n – 1);
}
}
console.log(“Factorial of 0:”, factorial(0)); // Expected: 1
console.log(“Factorial of 1:”, factorial(1)); // Expected: 1
console.log(“Factorial of 5:”, factorial(5)); // Expected: 120 (5 * 4 * 3 * 2 * 1)
console.log(“Factorial of 7:”, factorial(7)); // Expected: 5040
// console.log(“Factorial of -1:”, factorial(-1)); // This would lead to infinite recursion without proper handling - Explanation:
- Recursion: A function that calls itself.
- Base Case: The condition that stops the recursion. Without a base case, a recursive function would run indefinitely, leading to a stack overflow error.
- Recursive Step: The part where the function calls itself with a modified (usually smaller) input, moving towards the base case.
- In factorial(5):
- 5 * factorial(4)
- 5 * (4 * factorial(3))
- 5 * (4 * (3 * factorial(2)))
- 5 * (4 * (3 * (2 * factorial(1))))
- 5 * (4 * (3 * (2 * 1))) –> Calculation starts unwinding back up
Exercise 29: Higher-Order Function – map() with Objects
- Skills Highlighted: Higher-order functions, map(), objects, transforming arrays of objects.
- Problem Statement: Given an array of product objects products = [{ name: “Laptop”, price: 1200 }, { name: “Keyboard”, price: 75 }]. Use the map() method to create a new array where each product object has an additional property priceWithTax (calculated as price * 1.15 for 15% tax).
- Step-by-step Solution:
- Declare the products array.
- Use products.map().
- In the callback, take product as an argument.
- Return a new object using the spread operator (…product) to copy existing properties, then add priceWithTax.
- Print the new array.
- Code:
// Exercise 29: Higher-Order Function – map() with Objects
let products = [
{ id: 1, name: “Laptop”, price: 1200, category: “Electronics” },
{ id: 2, name: “Mouse”, price: 25, category: “Electronics” },
{ id: 3, name: “Notebook”, price: 15, category: “Stationery” },
{ id: 4, name: “Desk Chair”, price: 250, category: “Furniture” }
];
// Add a priceWithTax property to each product
const TAX_RATE = 0.15;
let productsWithTax = products.map(product => {
return {
…product, // Copies all existing properties from the original product object
priceWithTax: product.price * (1 + TAX_RATE) // Adds the new property
};
});
console.log(“Products with Tax:”, productsWithTax);
/* Expected Output Structure (approx):
[
{ id: 1, name: “Laptop”, price: 1200, category: “Electronics”, priceWithTax: 1380 },
{ id: 2, name: “Mouse”, price: 25, category: “Electronics”, priceWithTax: 28.75 },
…
]
*/
// Transform products into a simplified list for display
let productTitles = products.map(product => `${product.name} ($${product.price})`);
console.log(“\nProduct Titles:”, productTitles); // Expected: [“Laptop ($1200)”, “Mouse ($25)”, …] - Explanation:
- This demonstrates a powerful use of map(): transforming an array of objects into a new array of objects, potentially adding or modifying properties.
- The spread syntax (…product) is crucial here. It allows you to quickly create a new object that includes all properties of the product object, making it easy to then add or override specific properties without mutating the original object. This promotes immutability, which is a good practice in modern JavaScript.
Exercise 30: Chaining Array Methods
- Skills Highlighted: Chaining filter(), map(), and reduce(), functional programming concepts.
- Problem Statement: Given an array of transaction objects transactions = [{ id: 1, amount: 100, type: ‘credit’ }, { id: 2, amount: 50, type: ‘debit’ }, { id: 3, amount: 200, type: ‘credit’ }, { id: 4, amount: 30, type: ‘debit’ }].
- Filter out only “credit” transactions.
- From the filtered transactions, extract only their amount.
- Calculate the sum of these credit amounts.
Perform all these steps in a single chain of array methods.
- Step-by-step Solution:
- Declare transactions.
- Start with transactions.filter(…).
- Chain .map(…) to the result of filter.
- Chain .reduce(…) to the result of map.
- Print the final sum.
- Code:
// Exercise 30: Chaining Array Methods
let transactions = [
{ id: 1, amount: 100, type: ‘credit’, date: ‘2023-01-01’ },
{ id: 2, amount: 50, type: ‘debit’, date: ‘2023-01-02’ },
{ id: 3, amount: 200, type: ‘credit’, date: ‘2023-01-03’ },
{ id: 4, amount: 30, type: ‘debit’, date: ‘2023-01-04’ },
{ id: 5, amount: 150, type: ‘credit’, date: ‘2023-01-05’ }
];
// Calculate the total amount of all credit transactions
let totalCreditAmount = transactions
.filter(transaction => transaction.type === ‘credit’) // Step 1: Filter credit transactions
.map(creditTransaction => creditTransaction.amount) // Step 2: Extract amounts
.reduce((sum, amount) => sum + amount, 0); // Step 3: Sum the amounts
console.log(“Transactions:”, transactions);
console.log(“Total Credit Amount:”, totalCreditAmount); // Expected: 100 + 200 + 150 = 450
// Another example: Get names of users older than 25
let people = [
{ name: “Alice”, age: 20 },
{ name: “Bob”, age: 30 },
{ name: “Charlie”, age: 25 },
{ name: “Diana”, age: 35 }
];
let namesOfAdults = people
.filter(person => person.age > 25)
.map(adult => adult.name);
console.log(“\nNames of people older than 25:”, namesOfAdults); // Expected: [“Bob”, “Diana”] - Explanation:
- Method Chaining: A common and elegant pattern in JavaScript (and many other languages) where methods are called sequentially on the result of the previous method.
- filter() returns a new array, which map() then operates on. map() also returns a new array, which reduce() then operates on.
- This approach is highly readable and declarative, clearly showing the transformation pipeline of the data. It’s a cornerstone of functional programming in JavaScript.
