Episode 1 — Fundamentals / 1.21 — Arrays
1.21.f -- Practice Problems
In one sentence: Hands-on coding exercises covering array creation, CRUD operations, searching, sorting helpers, and classic interview patterns -- each with problem statement, hints, step-by-step solution, and explanation.
Navigation: <- 1.21.e -- Basic Iteration . 1.21.g -- Multidimensional Arrays ->
How to use these problems
- Read the problem statement and try to solve it yourself first.
- Check the hints only if you are stuck.
- Compare your solution with the provided one -- there are many valid approaches.
- Run the code in your browser console or Node.js.
- Challenge yourself with the bonus tasks at the end of each problem.
Problem 1: Todo List Manager
Problem: Build a simple todo list using an array. Implement functions to:
- Add a task
- Remove a task by name
- Display all tasks
- Mark a task as complete
Hints:
- Store tasks as objects:
{ text: "...", done: false } - Use
findIndex()to locate a task by name - Use
splice()to remove by index
Solution
const todos = [];
// Add a task
function addTask(text) {
todos.push({ text: text, done: false });
console.log(`Added: "${text}"`);
}
// Remove a task by name
function removeTask(text) {
const index = todos.findIndex(task => task.text === text);
if (index === -1) {
console.log(`Task "${text}" not found`);
return;
}
todos.splice(index, 1);
console.log(`Removed: "${text}"`);
}
// Display all tasks
function displayTasks() {
if (todos.length === 0) {
console.log("No tasks!");
return;
}
console.log("\n=== TODO LIST ===");
for (const [i, task] of todos.entries()) {
const status = task.done ? "[x]" : "[ ]";
console.log(`${i + 1}. ${status} ${task.text}`);
}
console.log(`Total: ${todos.length} | Done: ${todos.filter(t => t.done).length}\n`);
}
// Mark a task as complete
function completeTask(text) {
const task = todos.find(t => t.text === text);
if (!task) {
console.log(`Task "${text}" not found`);
return;
}
task.done = true;
console.log(`Completed: "${text}"`);
}
// Test it
addTask("Buy groceries");
addTask("Walk the dog");
addTask("Write report");
addTask("Call dentist");
displayTasks();
completeTask("Walk the dog");
completeTask("Call dentist");
displayTasks();
removeTask("Call dentist");
displayTasks();
Explanation:
push()adds tasks to the end of the array.findIndex()searches for a task by matching thetextproperty and returns its index (or -1).splice(index, 1)removes exactly one element at the given index.find()returns the task object itself so we can modify itsdoneproperty.filter(t => t.done).lengthcounts completed tasks.
Bonus: Add an editTask(oldText, newText) function and an undoComplete(text) function.
Problem 2: Max/Min Finder
Problem: Write a function that finds the maximum and minimum values in an array of numbers.
- First, implement it manually using a loop.
- Then, implement it using
Math.max()/Math.min().
Hints:
- Initialize min and max to the first element, not to 0 or Infinity.
Math.max(...arr)spreads the array as arguments.
Solution
// Method 1: Manual with loop
function findMinMaxManual(arr) {
if (arr.length === 0) {
return { min: undefined, max: undefined };
}
let min = arr[0];
let max = arr[0];
for (let i = 1; i < arr.length; i++) {
if (arr[i] < min) {
min = arr[i];
}
if (arr[i] > max) {
max = arr[i];
}
}
return { min, max };
}
// Method 2: Using Math.max / Math.min
function findMinMaxBuiltIn(arr) {
if (arr.length === 0) {
return { min: undefined, max: undefined };
}
return {
min: Math.min(...arr),
max: Math.max(...arr)
};
}
// Test
const numbers = [34, 7, 23, 92, 1, 56, 78, 12, 45, 3];
console.log(findMinMaxManual(numbers));
// { min: 1, max: 92 }
console.log(findMinMaxBuiltIn(numbers));
// { min: 1, max: 92 }
// Edge cases
console.log(findMinMaxManual([42])); // { min: 42, max: 42 }
console.log(findMinMaxManual([-5, -1, -8])); // { min: -8, max: -1 }
console.log(findMinMaxManual([])); // { min: undefined, max: undefined }
Explanation:
- The manual method starts with
arr[0]and compares every subsequent element. - Starting the loop at
i = 1(noti = 0) avoids a redundant comparison. Math.max(...arr)spreads the array into individual arguments. Caution: this throws aRangeErrorfor very large arrays (> ~100,000 elements) because of the call stack limit.
Bonus: Modify the manual version to also return the indices of the min and max values.
Problem 3: Sum of All Elements
Problem: Write a function that calculates the sum of all numbers in an array.
Hints:
- Initialize an accumulator to 0.
- Handle edge cases: empty array, non-number values.
Solution
// Basic sum
function sum(arr) {
let total = 0;
for (let i = 0; i < arr.length; i++) {
total += arr[i];
}
return total;
}
// Defensive sum (skips non-numbers)
function safeSum(arr) {
let total = 0;
for (const val of arr) {
if (typeof val === "number" && !Number.isNaN(val)) {
total += val;
}
}
return total;
}
// Test
console.log(sum([1, 2, 3, 4, 5])); // 15
console.log(sum([10, -5, 3.5, 1.5])); // 10
console.log(sum([])); // 0
console.log(safeSum([1, "two", 3, null, 5, NaN])); // 9
Explanation:
- We start
totalat 0 (the additive identity) so an empty array correctly returns 0. - The
safeSumversion checkstypeof val === "number"to skip strings and nulls, and!Number.isNaN(val)to skipNaN.
Bonus: Write an average(arr) function that uses sum() and handles empty arrays.
Problem 4: Count Occurrences of a Value
Problem: Write a function that counts how many times a given value appears in an array.
Hints:
- A simple loop with an
ifcheck and counter. - Consider strict equality (
===).
Solution
function countOccurrences(arr, target) {
let count = 0;
for (const item of arr) {
if (item === target) {
count++;
}
}
return count;
}
// Count all occurrences in the array (frequency map)
function frequencyMap(arr) {
const freq = {};
for (const item of arr) {
freq[item] = (freq[item] || 0) + 1;
}
return freq;
}
// Test
const data = [1, 3, 2, 1, 4, 1, 3, 2, 1];
console.log(countOccurrences(data, 1)); // 4
console.log(countOccurrences(data, 3)); // 2
console.log(countOccurrences(data, 5)); // 0
const words = ["cat", "dog", "cat", "bird", "dog", "cat"];
console.log(frequencyMap(words));
// { cat: 3, dog: 2, bird: 1 }
Explanation:
- The loop checks each element with strict equality (
===). - The frequency map pattern uses an object where keys are array values and values are counts.
(freq[item] || 0) + 1handles the first occurrence (whenfreq[item]isundefined,|| 0defaults it to 0).
Bonus: Write a mostFrequent(arr) function that returns the value that appears most often.
Problem 5: Remove Duplicates from Array
Problem: Write a function that returns a new array with duplicate values removed.
Hints:
- Use a loop and check if the value is already in the result.
- There is also a one-liner using
Set.
Solution
// Method 1: Manual with loop
function removeDuplicatesManual(arr) {
const result = [];
for (const item of arr) {
if (!result.includes(item)) {
result.push(item);
}
}
return result;
}
// Method 2: Using Set (cleanest)
function removeDuplicatesSet(arr) {
return [...new Set(arr)];
}
// Method 3: Using indexOf
function removeDuplicatesIndexOf(arr) {
return arr.filter((item, index) => arr.indexOf(item) === index);
}
// Test
const nums = [1, 3, 2, 1, 4, 3, 2, 5, 1];
console.log(removeDuplicatesManual(nums)); // [1, 3, 2, 4, 5]
console.log(removeDuplicatesSet(nums)); // [1, 3, 2, 4, 5]
console.log(removeDuplicatesIndexOf(nums)); // [1, 3, 2, 4, 5]
// Original unchanged
console.log(nums); // [1, 3, 2, 1, 4, 3, 2, 5, 1]
const strings = ["apple", "banana", "apple", "cherry", "banana"];
console.log(removeDuplicatesSet(strings)); // ["apple", "banana", "cherry"]
Explanation:
- Manual method: For each item, check if
resultalreadyincludesit. If not, push it. Time: O(n^2) due toincludesbeing O(n) on each call. - Set method: A
Setonly holds unique values. Spreading it back to an array gives duplicates removed. Time: O(n). - indexOf method:
arr.indexOf(item) === indexis true only for the first occurrence of each value.
Bonus: Remove duplicates from an array of objects by a specific property (e.g., id).
Problem 6: Reverse Array Without Using .reverse()
Problem: Write a function that reverses an array without using the built-in reverse() method.
Hints:
- Swap elements from both ends, moving toward the center.
- You only need to iterate halfway.
Solution
// Method 1: Swap in place (mutates)
function reverseInPlace(arr) {
let left = 0;
let right = arr.length - 1;
while (left < right) {
// Swap
[arr[left], arr[right]] = [arr[right], arr[left]];
left++;
right--;
}
return arr;
}
// Method 2: Build new array (non-mutating)
function reverseNew(arr) {
const result = [];
for (let i = arr.length - 1; i >= 0; i--) {
result.push(arr[i]);
}
return result;
}
// Method 3: Unshift approach (non-mutating, but O(n^2))
function reverseUnshift(arr) {
const result = [];
for (const item of arr) {
result.unshift(item);
}
return result;
}
// Test
console.log(reverseInPlace([1, 2, 3, 4, 5])); // [5, 4, 3, 2, 1]
console.log(reverseNew([1, 2, 3, 4, 5])); // [5, 4, 3, 2, 1]
console.log(reverseUnshift([1, 2, 3, 4, 5])); // [5, 4, 3, 2, 1]
// Edge cases
console.log(reverseInPlace([])); // []
console.log(reverseInPlace([42])); // [42]
console.log(reverseNew(["a", "b"])); // ["b", "a"]
Explanation:
- Swap in place: Two pointers start at both ends and move inward, swapping at each step. Only
Math.floor(n/2)swaps needed. Time: O(n), Space: O(1). - Build new array: Iterate backwards and push each element. Time: O(n), Space: O(n).
- Unshift approach: For each item, insert at the front. Simpler logic but O(n^2) because
unshiftshifts all existing elements.
Bonus: Reverse a string using the same technique (hint: split, reverse, join).
Problem 7: Merge Two Sorted Arrays
Problem: Given two arrays that are each sorted in ascending order, merge them into one sorted array.
Hints:
- Use two pointers, one for each array.
- Compare elements at both pointers and push the smaller one.
- Handle leftover elements after one array is exhausted.
Solution
function mergeSorted(arr1, arr2) {
const result = [];
let i = 0; // pointer for arr1
let j = 0; // pointer for arr2
// Compare elements from both arrays
while (i < arr1.length && j < arr2.length) {
if (arr1[i] <= arr2[j]) {
result.push(arr1[i]);
i++;
} else {
result.push(arr2[j]);
j++;
}
}
// Add remaining elements from arr1 (if any)
while (i < arr1.length) {
result.push(arr1[i]);
i++;
}
// Add remaining elements from arr2 (if any)
while (j < arr2.length) {
result.push(arr2[j]);
j++;
}
return result;
}
// Test
console.log(mergeSorted([1, 3, 5, 7], [2, 4, 6, 8]));
// [1, 2, 3, 4, 5, 6, 7, 8]
console.log(mergeSorted([1, 5, 9], [2, 3, 4, 10, 11]));
// [1, 2, 3, 4, 5, 9, 10, 11]
console.log(mergeSorted([], [1, 2, 3]));
// [1, 2, 3]
console.log(mergeSorted([1, 1, 1], [1, 1]));
// [1, 1, 1, 1, 1]
Step-by-step trace for mergeSorted([1, 3, 5], [2, 4, 6]):
Step 1: i=0, j=0 | Compare 1 vs 2 | Push 1 | result: [1]
Step 2: i=1, j=0 | Compare 3 vs 2 | Push 2 | result: [1, 2]
Step 3: i=1, j=1 | Compare 3 vs 4 | Push 3 | result: [1, 2, 3]
Step 4: i=2, j=1 | Compare 5 vs 4 | Push 4 | result: [1, 2, 3, 4]
Step 5: i=2, j=2 | Compare 5 vs 6 | Push 5 | result: [1, 2, 3, 4, 5]
Step 6: i=3 (done), j=2 | Remaining: Push 6 | result: [1, 2, 3, 4, 5, 6]
Explanation:
- Two pointers advance independently through each array.
- At each step, the smaller element is chosen and added to the result.
- After one array is exhausted, the remaining elements from the other are appended.
- Time: O(n + m) where n and m are the lengths of the two arrays.
Bonus: Can you do this in place (modifying arr1 to contain the merged result)?
Problem 8: Second Largest Element
Problem: Find the second largest element in an array of numbers.
Hints:
- Track both the largest and second largest as you iterate.
- Handle edge cases: duplicate max values, arrays with fewer than 2 elements.
Solution
function secondLargest(arr) {
if (arr.length < 2) {
return undefined; // not enough elements
}
let first = -Infinity;
let second = -Infinity;
for (const num of arr) {
if (num > first) {
second = first; // demote current first to second
first = num; // new first
} else if (num > second && num !== first) {
second = num; // new second (but not equal to first)
}
}
return second === -Infinity ? undefined : second;
}
// Test
console.log(secondLargest([5, 2, 8, 1, 9, 3])); // 8
console.log(secondLargest([10, 10, 10])); // undefined (all same)
console.log(secondLargest([7, 3])); // 3
console.log(secondLargest([42])); // undefined
console.log(secondLargest([-1, -5, -2, -8])); // -2
// Alternative: sort and find
function secondLargestSort(arr) {
const unique = [...new Set(arr)];
if (unique.length < 2) return undefined;
unique.sort((a, b) => b - a); // descending
return unique[1];
}
console.log(secondLargestSort([5, 2, 8, 1, 9, 3])); // 8
Explanation:
- We maintain two variables:
first(largest) andsecond(second largest). - When we find a number larger than
first, the oldfirstbecomes the newsecond. - When we find a number between
firstandsecond(and not equal tofirst), it becomes the newsecond. - Time: O(n) with a single pass. The sort-based approach is O(n log n).
Bonus: Find the kth largest element (generalize for any k).
Summary of Problem Patterns
| Problem | Key techniques | Time complexity |
|---|---|---|
| Todo list | push, find, findIndex, splice | O(n) for search |
| Max/min | Single-pass comparison, Math.max/min | O(n) |
| Sum | Accumulator pattern | O(n) |
| Count occurrences | Counter variable, frequency map | O(n) |
| Remove duplicates | Set, includes, indexOf | O(n) with Set |
| Reverse | Two-pointer swap | O(n) |
| Merge sorted | Two-pointer merge | O(n + m) |
| Second largest | Two-variable tracking | O(n) |
Key takeaways
- Most array problems follow a loop + accumulator or loop + condition pattern.
- Two-pointer techniques (reverse, merge) are powerful and efficient.
- Always handle edge cases: empty arrays, single elements, all-same values.
- Know the time complexity of your approach -- interviewers care about this.
- Practice both mutating (in-place) and non-mutating (new array) solutions.
Explain-It Challenge
Pick any two problems above and explain your solution to someone without showing code:
- What is the input and expected output?
- What is your step-by-step approach in plain English?
- What edge cases did you handle?
- What is the time and space complexity?
Navigation: <- 1.21.e -- Basic Iteration . 1.21.g -- Multidimensional Arrays ->