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

  1. Read the problem statement and try to solve it yourself first.
  2. Check the hints only if you are stuck.
  3. Compare your solution with the provided one -- there are many valid approaches.
  4. Run the code in your browser console or Node.js.
  5. 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 the text property 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 its done property.
  • filter(t => t.done).length counts 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 (not i = 0) avoids a redundant comparison.
  • Math.max(...arr) spreads the array into individual arguments. Caution: this throws a RangeError for 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 total at 0 (the additive identity) so an empty array correctly returns 0.
  • The safeSum version checks typeof val === "number" to skip strings and nulls, and !Number.isNaN(val) to skip NaN.

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 if check 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) + 1 handles the first occurrence (when freq[item] is undefined, || 0 defaults 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 result already includes it. If not, push it. Time: O(n^2) due to includes being O(n) on each call.
  • Set method: A Set only holds unique values. Spreading it back to an array gives duplicates removed. Time: O(n).
  • indexOf method: arr.indexOf(item) === index is 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 unshift shifts 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) and second (second largest).
  • When we find a number larger than first, the old first becomes the new second.
  • When we find a number between first and second (and not equal to first), it becomes the new second.
  • 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

ProblemKey techniquesTime complexity
Todo listpush, find, findIndex, spliceO(n) for search
Max/minSingle-pass comparison, Math.max/minO(n)
SumAccumulator patternO(n)
Count occurrencesCounter variable, frequency mapO(n)
Remove duplicatesSet, includes, indexOfO(n) with Set
ReverseTwo-pointer swapO(n)
Merge sortedTwo-pointer mergeO(n + m)
Second largestTwo-variable trackingO(n)

Key takeaways

  1. Most array problems follow a loop + accumulator or loop + condition pattern.
  2. Two-pointer techniques (reverse, merge) are powerful and efficient.
  3. Always handle edge cases: empty arrays, single elements, all-same values.
  4. Know the time complexity of your approach -- interviewers care about this.
  5. 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:

  1. What is the input and expected output?
  2. What is your step-by-step approach in plain English?
  3. What edge cases did you handle?
  4. What is the time and space complexity?

Navigation: <- 1.21.e -- Basic Iteration . 1.21.g -- Multidimensional Arrays ->