Coding Practice / Module 2 · Numbers / Problems 11–20

Module 2: Number & Numerics Practice Problems

The classic number-theory drills that show up in almost every first coding interview: primes, factorials, Fibonacci, GCD/LCM, and a few named curiosities like Armstrong and perfect numbers. Every problem here works the number apart digit by digit or divisor by divisor — the same handful of loop shapes, reused ten times. Solved and commented in both JavaScript and Python.

Module 2 of 12 Problems 11–20 JS + Python ~45–60 Min

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

  • Test primality efficiently by only checking divisors up to the square root
  • Peel digits off a number one at a time using % 10 and integer division
  • Compute Fibonacci and factorial iteratively, without recursion (that comes in Module 5)

1. Problems 11–20

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

P11

Check if a Number Is Prime

29 is prime; 30 isn't (it's divisible by 2, 3, 5, 6, 10, 15).

Approach: a number is prime if nothing between 2 and its square root divides it evenly — you never need to check past the square root, because any factor pair has one member below it and one above.

JavaScript
is-prime.js
function isPrime(n) {
  if (n < 2) return false; // 0, 1 and negatives are not prime
  for (let i = 2; i * i <= n; i++) { // only need to check up to sqrt(n)
    if (n % i === 0) return false; // found a divisor -> not prime
  }
  return true;
}

console.log(isPrime(29)); // true
console.log(isPrime(30)); // false
Python
is_prime.py
def is_prime(n: int) -> bool:
    if n < 2:  # 0, 1 and negatives are not prime
        return False
    i = 2
    while i * i <= n:  # only need to check up to sqrt(n)
        if n % i == 0:  # found a divisor -> not prime
            return False
        i += 1
    return True

print(is_prime(29))  # True
print(is_prime(30))  # False
P12

Check if a Number Is a Palindrome

12321 reads the same forwards and backwards; 12345 doesn't.

Approach: convert the number to a string and reuse the same "walk it backwards" idea from Module 1's string reversal — then compare original to reversed.

JavaScript
is-numeric-palindrome.js
function isNumericPalindrome(n) {
  const str = String(n);
  let reversed = "";
  for (let i = str.length - 1; i >= 0; i--) {
    reversed += str[i];
  }
  return str === reversed;
}

console.log(isNumericPalindrome(12321)); // true
console.log(isNumericPalindrome(12345)); // false
Python
is_numeric_palindrome.py
def is_numeric_palindrome(n: int) -> bool:
    s = str(n)
    reversed_s = ""
    for i in range(len(s) - 1, -1, -1):
        reversed_s += s[i]
    return s == reversed_s

print(is_numeric_palindrome(12321))  # True
print(is_numeric_palindrome(12345))  # False
P13

Factorial (Iterative)

5! = 5 × 4 × 3 × 2 × 1 = 120.

Approach: keep a running product starting at 1, and multiply it by every whole number from 2 up to n.

JavaScript
factorial.js
function factorial(n) {
  if (n < 0) throw new Error("Factorial is undefined for negative numbers");
  let result = 1;
  for (let i = 2; i <= n; i++) {
    result *= i; // multiply the running product by each number up to n
  }
  return result;
}

console.log(factorial(5)); // 120
console.log(factorial(0)); // 1  (by definition)
Python
factorial.py
def factorial(n: int) -> int:
    if n < 0:
        raise ValueError("Factorial is undefined for negative numbers")
    result = 1
    for i in range(2, n + 1):
        result *= i  # multiply the running product by each number up to n
    return result

print(factorial(5))  # 120
print(factorial(0))  # 1  (by definition)
P14

Nth Fibonacci Number (Iterative)

0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55 — the 10th term is 55.

Approach: track only the previous two terms and slide them forward each iteration — no need to store the whole sequence.

JavaScript
fibonacci.js
function fibonacci(n) {
  if (n <= 1) return n; // base cases: fib(0) = 0, fib(1) = 1
  let prev = 0;
  let curr = 1;
  for (let i = 2; i <= n; i++) {
    const next = prev + curr; // each number is the sum of the previous two
    prev = curr;
    curr = next;
  }
  return curr;
}

console.log(fibonacci(10)); // 55
Python
fibonacci.py
def fibonacci(n: int) -> int:
    if n <= 1:  # base cases: fib(0) = 0, fib(1) = 1
        return n
    prev, curr = 0, 1
    for _ in range(2, n + 1):
        prev, curr = curr, prev + curr  # each number is the sum of the previous two
    return curr

print(fibonacci(10))  # 55
P15

Armstrong Number Check

153 = 1³ + 5³ + 3³ — a number equal to its own digits raised to the digit count.

Approach: split the number into its digits, raise each one to the power of how many digits there are in total, sum the results, and compare that sum to the original number.

JavaScript
is-armstrong.js
function isArmstrong(n) {
  const digits = String(n).split("");
  const power = digits.length;
  let sum = 0;
  for (const d of digits) {
    sum += Math.pow(Number(d), power); // raise each digit to the digit count
  }
  return sum === n;
}

console.log(isArmstrong(153)); // true  (1^3 + 5^3 + 3^3 = 153)
console.log(isArmstrong(123)); // false
Python
is_armstrong.py
def is_armstrong(n: int) -> bool:
    digits = str(n)
    power = len(digits)
    total = 0
    for d in digits:
        total += int(d) ** power  # raise each digit to the digit count
    return total == n

print(is_armstrong(153))  # True  (1**3 + 5**3 + 3**3 = 153)
print(is_armstrong(123))  # False
P16

GCD of Two Numbers (Euclidean Algorithm)

gcd(48, 18) = 6.

Approach: the Euclidean algorithm — repeatedly replace the pair (a, b) with (b, a mod b) until b reaches 0. Whatever a is at that point is the greatest common divisor.

JavaScript
gcd.js
function gcd(a, b) {
  while (b !== 0) {
    [a, b] = [b, a % b]; // replace (a, b) with (b, a mod b) until b hits 0
  }
  return Math.abs(a);
}

console.log(gcd(48, 18)); // 6
Python
gcd.py
def gcd(a: int, b: int) -> int:
    while b != 0:
        a, b = b, a % b  # replace (a, b) with (b, a mod b) until b hits 0
    return abs(a)

print(gcd(48, 18))  # 6
# Idiomatic one-liner for real code: math.gcd(48, 18)
P17

LCM of Two Numbers

lcm(4, 6) = 12.

Approach: reuse GCD from the previous problem — the least common multiple is always |a × b| / gcd(a, b), so there's no need to write a second search from scratch.

JavaScript
lcm.js
function gcd(a, b) {
  while (b !== 0) [a, b] = [b, a % b];
  return Math.abs(a);
}

function lcm(a, b) {
  return Math.abs(a * b) / gcd(a, b); // LCM = |a*b| / GCD(a,b)
}

console.log(lcm(4, 6)); // 12
Python
lcm.py
def gcd(a: int, b: int) -> int:
    while b != 0:
        a, b = b, a % b
    return abs(a)

def lcm(a: int, b: int) -> int:
    return abs(a * b) // gcd(a, b)  # LCM = |a*b| / GCD(a,b)

print(lcm(4, 6))  # 12
P18

Reverse the Digits of an Integer

1234 becomes 4321; -56 becomes -65.

Approach: repeatedly pull off the last digit with % 10, append it to a growing result, then drop that digit with integer division by 10 — all without ever converting to a string.

JavaScript
reverse-number.js
function reverseNumber(n) {
  const sign = n < 0 ? -1 : 1;
  n = Math.abs(n);
  let reversed = 0;
  while (n > 0) {
    reversed = reversed * 10 + (n % 10); // peel off the last digit, append it
    n = Math.floor(n / 10);
  }
  return reversed * sign;
}

console.log(reverseNumber(1234)); // 4321
console.log(reverseNumber(-56)); // -65
Python
reverse_number.py
def reverse_number(n: int) -> int:
    sign = -1 if n < 0 else 1
    n = abs(n)
    reversed_n = 0
    while n > 0:
        reversed_n = reversed_n * 10 + (n % 10)  # peel off the last digit, append it
        n //= 10
    return reversed_n * sign

print(reverse_number(1234))  # 4321
print(reverse_number(-56))  # -65
P19

Perfect Number Check

28 = 1 + 2 + 4 + 7 + 14 — a number equal to the sum of its own proper divisors.

Approach: reuse the same square-root trick as the prime check — but instead of stopping at the first divisor, add up every divisor found (and its paired divisor n/i) along the way.

JavaScript
is-perfect-number.js
function isPerfectNumber(n) {
  if (n < 2) return false;
  let sum = 1; // 1 always divides n (for n > 1)
  for (let i = 2; i * i <= n; i++) {
    if (n % i === 0) {
      sum += i;
      if (i !== n / i) sum += n / i; // add the paired divisor too
    }
  }
  return sum === n;
}

console.log(isPerfectNumber(28)); // true  (1 + 2 + 4 + 7 + 14 = 28)
console.log(isPerfectNumber(12)); // false
Python
is_perfect_number.py
def is_perfect_number(n: int) -> bool:
    if n < 2:
        return False
    total = 1  # 1 always divides n (for n > 1)
    i = 2
    while i * i <= n:
        if n % i == 0:
            total += i
            if i != n // i:
                total += n // i  # add the paired divisor too
        i += 1
    return total == n

print(is_perfect_number(28))  # True  (1 + 2 + 4 + 7 + 14 = 28)
print(is_perfect_number(12))  # False
P20

Sum of Digits of a Number

The digits of 12345 add up to 15.

Approach: the same digit-peeling loop from problem 18, minus the reassembly step — just accumulate n % 10 into a running total each pass.

JavaScript
sum-of-digits.js
function sumOfDigits(n) {
  n = Math.abs(n);
  let sum = 0;
  while (n > 0) {
    sum += n % 10; // add the last digit
    n = Math.floor(n / 10); // drop the last digit
  }
  return sum;
}

console.log(sumOfDigits(12345)); // 15
Python
sum_of_digits.py
def sum_of_digits(n: int) -> int:
    n = abs(n)
    total = 0
    while n > 0:
        total += n % 10  # add the last digit
        n //= 10  # drop the last digit
    return total

print(sum_of_digits(12345))  # 15

2. Key Takeaways

  • Any "check every divisor" problem — primality, perfect numbers — only needs to loop up to the square root of n, because factors always pair up around it.
  • Digit-by-digit problems (reversal, digit sum, Armstrong numbers) all lean on the same two operations: n % 10 to read the last digit, and integer division by 10 to drop it.
  • Iterative Fibonacci and factorial only need to remember the last one or two values, not the whole sequence — a pattern worth contrasting with the recursive versions in Module 5.