Coding Practice / Module 3 · Lists / Problems 21–30

Module 3: List & Sequence Practice Problems

The operations underneath almost every real data-processing task: finding extremes, de-duplicating, merging, searching for overlap, and rearranging. Arrays in JavaScript and lists in Python behave almost identically here — the syntax for indexing and slicing is the main thing that shifts between languages. Solved and commented in both.

Module 3 of 12 Problems 21–30 JS + Python ~45–60 Min

By the end of this module, you'll be able to

  • Track a running max/min or a "second best" candidate in a single pass over a list
  • Use a set to de-duplicate or intersect lists in better-than-brute-force time
  • Merge two sorted lists with two pointers, the same core step behind merge sort

1. Problems 21–30

Same format as the previous modules: expand a problem to see the approach and both commented solutions.

P21

Find the Max and Min in a List

In [4, 2, 9, 1, 7], the max is 9 and the min is 1.

Approach: seed both "max so far" and "min so far" with the first element, then update either one whenever a new element beats the current record.

JavaScript
find-max-min.js
function findMaxMin(arr) {
  let max = arr[0];
  let min = arr[0];
  for (const num of arr) {
    if (num > max) max = num;
    if (num < min) min = num;
  }
  return { max, min };
}

console.log(findMaxMin([4, 2, 9, 1, 7])); // { max: 9, min: 1 }
Python
find_max_min.py
def find_max_min(nums: list) -> dict:
    maximum = nums[0]
    minimum = nums[0]
    for num in nums:
        if num > maximum:
            maximum = num
        if num < minimum:
            minimum = num
    return {"max": maximum, "min": minimum}

print(find_max_min([4, 2, 9, 1, 7]))  # {'max': 9, 'min': 1}
P22

Sum and Average of a List

[10, 20, 30] sums to 60, averaging 20.

Approach: accumulate a running total in one pass, then divide by how many elements there are once the loop finishes.

JavaScript
sum-and-average.js
function sumAndAverage(arr) {
  let sum = 0;
  for (const num of arr) {
    sum += num;
  }
  const average = sum / arr.length;
  return { sum, average };
}

console.log(sumAndAverage([10, 20, 30])); // { sum: 60, average: 20 }
Python
sum_and_average.py
def sum_and_average(nums: list) -> dict:
    total = 0
    for num in nums:
        total += num
    average = total / len(nums)
    return {"sum": total, "average": average}

print(sum_and_average([10, 20, 30]))  # {'sum': 60, 'average': 20.0}
P23

Remove Duplicates, Preserving Order

[1, 2, 2, 3, 1, 4] becomes [1, 2, 3, 4].

Approach: keep a set of items already seen. Walk the list once; only copy an item into the result the first time it's seen, and remember it so later repeats get skipped.

JavaScript
remove-duplicates.js
function removeDuplicates(arr) {
  const seen = new Set();
  const result = [];
  for (const item of arr) {
    if (!seen.has(item)) {
      seen.add(item); // remember it so later duplicates get skipped
      result.push(item);
    }
  }
  return result;
}

console.log(removeDuplicates([1, 2, 2, 3, 1, 4])); // [1, 2, 3, 4]
Python
remove_duplicates.py
def remove_duplicates(items: list) -> list:
    seen = set()
    result = []
    for item in items:
        if item not in seen:
            seen.add(item)  # remember it so later duplicates get skipped
            result.append(item)
    return result

print(remove_duplicates([1, 2, 2, 3, 1, 4]))  # [1, 2, 3, 4]
P24

Find the Second Largest Number

In [5, 1, 9, 9, 3], the second largest is 5 (duplicate 9s don't count twice).

Approach: track both "largest so far" and "second so far" in one pass. When a new value beats the largest, the old largest slides down into second place; when a value beats only second (and isn't equal to largest), it replaces second.

JavaScript
second-largest.js
function secondLargest(arr) {
  let largest = -Infinity;
  let second = -Infinity;
  for (const num of arr) {
    if (num > largest) {
      second = largest; // old largest becomes the new second
      largest = num;
    } else if (num > second && num < largest) {
      second = num;
    }
  }
  return second;
}

console.log(secondLargest([5, 1, 9, 9, 3])); // 5
Python
second_largest.py
def second_largest(nums: list) -> float:
    largest = float("-inf")
    second = float("-inf")
    for num in nums:
        if num > largest:
            second = largest  # old largest becomes the new second
            largest = num
        elif second < num < largest:
            second = num
    return second

print(second_largest([5, 1, 9, 9, 3]))  # 5
P25

Reverse a List In Place

[1, 2, 3, 4, 5] becomes [5, 4, 3, 2, 1], with no second list allocated.

Approach: the two-pointer swap from Module 1's palindrome check, applied to elements instead of characters — swap the ends, then move both pointers inward until they meet.

JavaScript
reverse-in-place.js
function reverseInPlace(arr) {
  let left = 0;
  let right = arr.length - 1;
  while (left < right) {
    [arr[left], arr[right]] = [arr[right], arr[left]]; // swap the two ends
    left++;
    right--;
  }
  return arr;
}

console.log(reverseInPlace([1, 2, 3, 4, 5])); // [5, 4, 3, 2, 1]
Python
reverse_in_place.py
def reverse_in_place(items: list) -> list:
    left, right = 0, len(items) - 1
    while left < right:
        items[left], items[right] = items[right], items[left]  # swap the two ends
        left += 1
        right -= 1
    return items

print(reverse_in_place([1, 2, 3, 4, 5]))  # [5, 4, 3, 2, 1]
P26

Element Frequency Count in a List

["a","b","a","c","b","a"] → a: 3, b: 2, c: 1.

Approach: the exact same frequency-map pattern from Module 1's character count, just applied to arbitrary list elements instead of characters.

JavaScript
element-frequency.js
function elementFrequency(arr) {
  const freq = {};
  for (const item of arr) {
    freq[item] = (freq[item] || 0) + 1;
  }
  return freq;
}

console.log(elementFrequency(["a", "b", "a", "c", "b", "a"]));
// { a: 3, b: 2, c: 1 }
Python
element_frequency.py
def element_frequency(items: list) -> dict:
    freq = {}
    for item in items:
        freq[item] = freq.get(item, 0) + 1
    return freq

print(element_frequency(["a", "b", "a", "c", "b", "a"]))
# {'a': 3, 'b': 2, 'c': 1}
P27

Check if a List Is Sorted

[1, 2, 2, 5, 9] is sorted; [1, 5, 2] isn't.

Approach: a list is sorted (ascending) exactly when every element is greater than or equal to the one before it — so a single pass comparing neighbors is enough to disprove it.

JavaScript
is-sorted.js
function isSorted(arr) {
  for (let i = 1; i < arr.length; i++) {
    if (arr[i] < arr[i - 1]) return false; // smaller than its predecessor
  }
  return true;
}

console.log(isSorted([1, 2, 2, 5, 9])); // true
console.log(isSorted([1, 5, 2]));       // false
Python
is_sorted.py
def is_sorted(items: list) -> bool:
    for i in range(1, len(items)):
        if items[i] < items[i - 1]:  # smaller than its predecessor
            return False
    return True

print(is_sorted([1, 2, 2, 5, 9]))  # True
print(is_sorted([1, 5, 2]))        # False
P28

Merge Two Sorted Lists

[1,3,5] + [2,4,6] → [1,2,3,4,5,6], without a re-sort.

Approach: two pointers, one per list. Always take the smaller of the two "current" values, advance that pointer, and repeat — then append whatever's left in the list that still has elements. This is the merge step from merge sort.

JavaScript
merge-sorted.js
function mergeSorted(a, b) {
  const merged = [];
  let i = 0, j = 0;
  while (i < a.length && j < b.length) {
    if (a[i] <= b[j]) {
      merged.push(a[i]); i++;
    } else {
      merged.push(b[j]); j++;
    }
  }
  // one list may still have leftovers once the other is exhausted
  while (i < a.length) merged.push(a[i++]);
  while (j < b.length) merged.push(b[j++]);
  return merged;
}

console.log(mergeSorted([1, 3, 5], [2, 4, 6])); // [1, 2, 3, 4, 5, 6]
Python
merge_sorted.py
def merge_sorted(a: list, b: list) -> list:
    merged = []
    i = j = 0
    while i < len(a) and j < len(b):
        if a[i] <= b[j]:
            merged.append(a[i]); i += 1
        else:
            merged.append(b[j]); j += 1
    # one list may still have leftovers once the other is exhausted
    merged.extend(a[i:])
    merged.extend(b[j:])
    return merged

print(merge_sorted([1, 3, 5], [2, 4, 6]))  # [1, 2, 3, 4, 5, 6]
P29

Intersection of Two Lists

[1,2,2,3] ∩ [2,3,4] → [2, 3].

Approach: turn the second list into a set for fast membership checks, then walk the first list keeping only items that are in that set and haven't already been added to the result.

JavaScript
intersection.js
function intersection(a, b) {
  const setB = new Set(b);
  const result = [];
  for (const item of a) {
    if (setB.has(item) && !result.includes(item)) {
      result.push(item); // keep it once, even if it repeats in `a`
    }
  }
  return result;
}

console.log(intersection([1, 2, 2, 3], [2, 3, 4])); // [2, 3]
Python
intersection.py
def intersection(a: list, b: list) -> list:
    set_b = set(b)
    result = []
    for item in a:
        if item in set_b and item not in result:
            result.append(item)  # keep it once, even if it repeats in `a`
    return result

print(intersection([1, 2, 2, 3], [2, 3, 4]))  # [2, 3]
P30

Rotate a List Left by k Positions

[1,2,3,4,5] rotated left by 2 → [3,4,5,1,2].

Approach: cut the list into two pieces at index k, then swap their order: everything from k onward, followed by everything before k. Reducing k modulo the list length handles rotations larger than the list itself.

JavaScript
rotate-left.js
function rotateLeft(arr, k) {
  const n = arr.length;
  k = k % n; // rotating by n (or a multiple of n) is the same as not rotating
  return arr.slice(k).concat(arr.slice(0, k));
}

console.log(rotateLeft([1, 2, 3, 4, 5], 2)); // [3, 4, 5, 1, 2]
Python
rotate_left.py
def rotate_left(items: list, k: int) -> list:
    n = len(items)
    k = k % n  # rotating by n (or a multiple of n) is the same as not rotating
    return items[k:] + items[:k]

print(rotate_left([1, 2, 3, 4, 5], 2))  # [3, 4, 5, 1, 2]

2. Key Takeaways

  • A single forward pass with one or two "running record" variables (max, min, second-largest, a running sum) solves most single-list problems without ever needing a second array.
  • Sets turn "have I seen this?" and "is this in the other list?" from an O(n) scan into an O(1) lookup — that's what makes de-duplication and intersection fast.
  • Two-pointer techniques (reverse in place, merge two sorted lists) show up constantly once you're comfortable holding two indices in your head at once.