Episode 1 — Fundamentals / 1.19 — Conditionals and Loops

1.19.f -- Practice Problems

In one sentence: Hands-on coding problems that combine conditionals, loops, break/continue, and nested iteration -- each with a problem statement, hints, step-by-step walkthrough, complete solution, and explanation.

Navigation: <-- 1.19.e -- Break and Continue . 1.19 Overview


How to use this material

  1. Read the problem statement and try to solve it yourself for at least 10 minutes.
  2. If stuck, read the hints (without looking at the solution).
  3. Compare your approach with the step-by-step walkthrough.
  4. Study the code and explanation to understand the pattern.
  5. Modify the solution (different input, edge cases) to deepen understanding.

Problem 1 -- Prime Number Check

Problem statement

Write a function isPrime(n) that returns true if n is a prime number and false otherwise. A prime number is greater than 1 and divisible only by 1 and itself.

Hints

  1. Numbers less than 2 are not prime.
  2. You only need to check divisors up to the square root of n. If n = a * b and both a and b are greater than the square root, then a * b > n -- contradiction.
  3. 2 is the only even prime.

Step-by-step walkthrough

  1. Handle edge cases: if n < 2, return false.
  2. Handle n === 2 (prime) and even numbers > 2 (not prime) as quick checks.
  3. Loop from 3 to Math.sqrt(n), stepping by 2 (skip even divisors).
  4. If any divisor divides n evenly, return false.
  5. If the loop finishes without finding a divisor, return true.

Solution

function isPrime(n) {
  if (n < 2) return false;
  if (n === 2) return true;
  if (n % 2 === 0) return false;

  for (let i = 3; i <= Math.sqrt(n); i += 2) {
    if (n % i === 0) return false;
  }

  return true;
}

// Test
console.log(isPrime(1));   // false
console.log(isPrime(2));   // true
console.log(isPrime(17));  // true
console.log(isPrime(18));  // false
console.log(isPrime(97));  // true

Explanation

  • We eliminate obvious non-primes early (< 2, even numbers).
  • Checking up to Math.sqrt(n) reduces the work from O(n) to O(sqrt(n)).
  • Stepping by 2 (only odd divisors) cuts the remaining work in half.
  • The loop uses an early return false (which is a break + return pattern) the moment a divisor is found.

Problem 2 -- Multiplication Table

Problem statement

Print a multiplication table from 1 to n (given as input), formatted in aligned columns.

Hints

  1. Use two nested loops: outer for rows, inner for columns.
  2. Use String.padStart() to align numbers.

Step-by-step walkthrough

  1. Outer loop: iterate i from 1 to n.
  2. Inner loop: iterate j from 1 to n.
  3. For each cell, compute i * j and pad it for alignment.
  4. After the inner loop, print the row.

Solution

function multiplicationTable(n) {
  // Header row
  let header = "    ";
  for (let j = 1; j <= n; j++) {
    header += String(j).padStart(5);
  }
  console.log(header);
  console.log("    " + "-".repeat(n * 5));

  // Data rows
  for (let i = 1; i <= n; i++) {
    let row = String(i).padStart(3) + " |";
    for (let j = 1; j <= n; j++) {
      row += String(i * j).padStart(5);
    }
    console.log(row);
  }
}

multiplicationTable(5);

Output

         1    2    3    4    5
    -------------------------
  1 |    1    2    3    4    5
  2 |    2    4    6    8   10
  3 |    3    6    9   12   15
  4 |    4    8   12   16   20
  5 |    5   10   15   20   25

Explanation

  • The outer loop handles each row (multiplier i).
  • The inner loop computes each product for that row.
  • padStart(5) ensures columns align even when numbers have different digit counts.
  • This is O(n^2) -- each cell is computed once.

Problem 3 -- Pattern Printing

3a. Right triangle

*
**
***
****
*****
function rightTriangle(rows) {
  for (let i = 1; i <= rows; i++) {
    let line = "";
    for (let j = 0; j < i; j++) {
      line += "*";
    }
    console.log(line);
  }
}

rightTriangle(5);

3b. Inverted triangle

*****
****
***
**
*
function invertedTriangle(rows) {
  for (let i = rows; i >= 1; i--) {
    let line = "";
    for (let j = 0; j < i; j++) {
      line += "*";
    }
    console.log(line);
  }
}

invertedTriangle(5);

3c. Centered pyramid

    *
   ***
  *****
 *******
*********
function pyramid(rows) {
  for (let i = 1; i <= rows; i++) {
    const spaces = " ".repeat(rows - i);
    const stars = "*".repeat(2 * i - 1);
    console.log(spaces + stars);
  }
}

pyramid(5);

3d. Diamond

    *
   ***
  *****
 *******
*********
 *******
  *****
   ***
    *
function diamond(n) {
  // Top half (including middle row)
  for (let i = 1; i <= n; i++) {
    const spaces = " ".repeat(n - i);
    const stars = "*".repeat(2 * i - 1);
    console.log(spaces + stars);
  }

  // Bottom half
  for (let i = n - 1; i >= 1; i--) {
    const spaces = " ".repeat(n - i);
    const stars = "*".repeat(2 * i - 1);
    console.log(spaces + stars);
  }
}

diamond(5);

Explanation

  • Right triangle: Row i has i stars. Inner loop builds the string.
  • Inverted: Same idea, but count down from rows to 1.
  • Pyramid: Row i needs (rows - i) leading spaces and (2*i - 1) stars.
  • Diamond: A pyramid on top, then the same pattern in reverse (minus the middle row to avoid duplication).
  • Using " ".repeat() and "*".repeat() is cleaner than inner loops for building strings.

Problem 4 -- FizzBuzz

Problem statement

Print numbers from 1 to n. For multiples of 3 print "Fizz", for multiples of 5 print "Buzz", for multiples of both print "FizzBuzz".

Hints

  1. Check divisibility by both 3 and 5 first (15), then by 3, then by 5.
  2. Order matters in if-else chains -- most specific condition first.

Solution

function fizzBuzz(n) {
  for (let i = 1; i <= n; i++) {
    if (i % 3 === 0 && i % 5 === 0) {
      console.log("FizzBuzz");
    } else if (i % 3 === 0) {
      console.log("Fizz");
    } else if (i % 5 === 0) {
      console.log("Buzz");
    } else {
      console.log(i);
    }
  }
}

fizzBuzz(20);

Alternative: string-building approach

function fizzBuzzAlt(n) {
  for (let i = 1; i <= n; i++) {
    let output = "";
    if (i % 3 === 0) output += "Fizz";
    if (i % 5 === 0) output += "Buzz";
    console.log(output || i);
  }
}

Explanation

  • The string-building approach is more extensible -- if you add "Jazz" for multiples of 7, you just add one more if line.
  • output || i prints the number when output is an empty string (falsy).
  • This is one of the most common interview screening questions. Master both approaches.

Problem 5 -- Fibonacci Sequence Generator

Problem statement

Generate the first n numbers of the Fibonacci sequence: 0, 1, 1, 2, 3, 5, 8, 13, 21, ...

Each number is the sum of the two preceding ones.

Hints

  1. Start with a = 0 and b = 1.
  2. On each iteration, compute next = a + b, then shift: a = b, b = next.

Solution

function fibonacci(n) {
  if (n <= 0) return [];
  if (n === 1) return [0];

  const result = [0, 1];

  for (let i = 2; i < n; i++) {
    const next = result[i - 1] + result[i - 2];
    result.push(next);
  }

  return result;
}

console.log(fibonacci(10));
// [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

Alternative: without storing the full array

function printFibonacci(n) {
  let a = 0;
  let b = 1;

  for (let i = 0; i < n; i++) {
    console.log(a);
    const next = a + b;
    a = b;
    b = next;
  }
}

printFibonacci(10);
// 0, 1, 1, 2, 3, 5, 8, 13, 21, 34

Explanation

  • The array approach stores all values (useful if you need them later).
  • The two-variable approach uses O(1) space (just a and b).
  • The variable swap pattern (a = b; b = next) is a fundamental technique.
  • This is O(n) time -- each Fibonacci number is computed once.

Problem 6 -- Sum of Digits

Problem statement

Given a positive integer, calculate the sum of its digits. Example: digitSum(1234) returns 10 (1+2+3+4).

Hints

  1. Use % 10 to extract the last digit.
  2. Use Math.floor(n / 10) to remove the last digit.
  3. Repeat until n becomes 0.

Solution

function digitSum(n) {
  n = Math.abs(n);  // handle negative numbers
  let sum = 0;

  while (n > 0) {
    sum += n % 10;        // extract last digit
    n = Math.floor(n / 10); // remove last digit
  }

  return sum;
}

console.log(digitSum(1234));   // 10
console.log(digitSum(9999));   // 36
console.log(digitSum(100));    // 1
console.log(digitSum(0));      // 0

Alternative: string approach

function digitSumStr(n) {
  return String(Math.abs(n))
    .split("")
    .reduce((sum, digit) => sum + Number(digit), 0);
}

Explanation

  • n % 10 gives the ones digit (e.g., 1234 % 10 = 4).
  • Math.floor(n / 10) removes the ones digit (e.g., 1234 -> 123).
  • The while loop continues until all digits are extracted (n becomes 0).
  • The string approach is shorter but allocates an array; the math approach is pure computation.

Problem 7 -- Reverse a Number

Problem statement

Reverse the digits of an integer. Example: reverseNumber(1234) returns 4321. Handle negative numbers.

Hints

  1. Same digit-extraction technique as Sum of Digits.
  2. Build the reversed number: reversed = reversed * 10 + digit.

Solution

function reverseNumber(n) {
  const sign = n < 0 ? -1 : 1;
  n = Math.abs(n);

  let reversed = 0;

  while (n > 0) {
    const digit = n % 10;
    reversed = reversed * 10 + digit;
    n = Math.floor(n / 10);
  }

  return reversed * sign;
}

console.log(reverseNumber(1234));   // 4321
console.log(reverseNumber(-567));   // -765
console.log(reverseNumber(1000));   // 1  (leading zeros dropped)
console.log(reverseNumber(0));      // 0

Step-by-step trace for reverseNumber(1234)

Iterationndigitreversed
Start1234--0
112340*10 + 4 = 4
21234*10 + 3 = 43
31243*10 + 2 = 432
401432*10 + 1 = 4321

Explanation

  • reversed * 10 shifts existing digits left (like adding a zero at the end).
  • Adding the new digit places it in the ones position.
  • The sign is preserved separately and applied at the end.

Problem 8 -- Palindrome Check

Problem statement

Check if a number is a palindrome (reads the same forwards and backwards). Example: isPalindrome(121) returns true.

Hints

  1. Negative numbers are not palindromes (the - sign).
  2. Reverse the number (Problem 7) and compare.
  3. Or convert to string and compare with its reverse.

Solution (numeric approach)

function isPalindrome(n) {
  if (n < 0) return false;

  const original = n;
  let reversed = 0;

  while (n > 0) {
    reversed = reversed * 10 + (n % 10);
    n = Math.floor(n / 10);
  }

  return original === reversed;
}

console.log(isPalindrome(121));    // true
console.log(isPalindrome(123));    // false
console.log(isPalindrome(1221));   // true
console.log(isPalindrome(-121));   // false
console.log(isPalindrome(0));      // true

Solution (string approach)

function isPalindromeStr(n) {
  const str = String(n);
  let left = 0;
  let right = str.length - 1;

  while (left < right) {
    if (str[left] !== str[right]) return false;
    left++;
    right--;
  }

  return true;
}

Explanation

  • The numeric approach reverses the entire number and compares. No string allocation.
  • The string approach uses two pointers moving inward -- it can short-circuit (return false early) on the first mismatch.
  • Both are O(d) where d is the number of digits.
  • The string approach also works for string palindromes (e.g., "racecar") with minor modification.

Problem 9 -- Count Primes Up to N (Bonus)

Problem statement

Count how many prime numbers are less than or equal to n.

Solution (using our isPrime from Problem 1)

function countPrimes(n) {
  let count = 0;

  for (let i = 2; i <= n; i++) {
    if (isPrime(i)) count++;
  }

  return count;
}

console.log(countPrimes(10));   // 4  (2, 3, 5, 7)
console.log(countPrimes(30));   // 10
console.log(countPrimes(100));  // 25

Advanced: Sieve of Eratosthenes

function sieveOfEratosthenes(n) {
  const isPrime = new Array(n + 1).fill(true);
  isPrime[0] = isPrime[1] = false;

  for (let i = 2; i <= Math.sqrt(n); i++) {
    if (isPrime[i]) {
      for (let j = i * i; j <= n; j += i) {
        isPrime[j] = false;  // mark multiples as not prime
      }
    }
  }

  const primes = [];
  for (let i = 2; i <= n; i++) {
    if (isPrime[i]) primes.push(i);
  }

  return primes;
}

console.log(sieveOfEratosthenes(30));
// [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]

Explanation

  • The sieve is much faster for large n -- O(n log log n) vs O(n * sqrt(n)).
  • It marks all multiples of each prime starting from i*i (smaller multiples were already marked by smaller primes).
  • This is a classic example of nested loops with a continue-like optimization (the if (isPrime[i]) guard).

Summary of patterns used

PatternProblems
Guard clause / early returnPrime check, palindrome
while loop with math operationsDigit sum, reverse number, palindrome
Nested for loopsMultiplication table, patterns, sieve
String building in a loopPatterns, FizzBuzz
Two-pointer techniquePalindrome (string approach)
break for early exitPrime check (inner loop)

Navigation: <-- 1.19.e -- Break and Continue . 1.19 Overview