Command Palette

Search for a command to run...

PHASE 2Beginner ~37 min· topic 10 of 10

Topic 2.10

Putting It Together: Control-Flow Problems

In one line

Classic small problems (FizzBuzz, primes, GCD, Fibonacci, digit tricks, binary conversion) solved with only the tools from this phase. Each one shows a reusable pattern: test the special case first, peel digits, stop early, and keep a running result.

Think of it like this

Learning to cook. Knowing what a knife, a pan and an oven do doesn't make a meal; you learn by making a few classic dishes, and each dish teaches a technique you reuse forever. These problems are the classic dishes of programming, and their techniques reappear in every later phase and in the DSA course (/dsa).

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

Edge case
An unusual input at the limits, such as 0, 1, a negative number or an empty array, where code often breaks.
Divisor
A number that divides another exactly. 3 is a divisor of 12 because 12 % 3 == 0.
Prime number
A whole number greater than 1 whose only divisors are 1 and itself: 2, 3, 5, 7, 11...
GCD
Greatest common divisor: the largest number that divides both of two numbers. The GCD of 48 and 18 is 6.
LCM
Least common multiple: the smallest number both divide into. LCM(a, b) = a / GCD(a, b) × b.
Fibonacci sequence
0, 1, 1, 2, 3, 5, 8...: each number is the sum of the two before it.
Armstrong number
A number equal to the sum of its digits each raised to the power of the digit count, like 153 = 1³ + 5³ + 3³.
Binary
Base 2: numbers written with only 0 and 1. 13 in binary is 1101.

Step by step

01FizzBuzz: order of checks

Print 1 to 15, but Fizz for multiples of 3, Buzz for multiples of 5, and FizzBuzz for multiples of both. It's famous because so many people get the order wrong.

The most specific case, divisible by both (that is, by 15), must come first. A switch can't express this cleanly (it needs conditions, not constants), so an else-if ladder is the natural fit.

Main.javawhole filejava
for (int i = 1; i <= 15; i++) {
    if (i % 15 == 0) {
        System.out.println("FizzBuzz");
    } else if (i % 3 == 0) {
        System.out.println("Fizz");
    } else if (i % 5 == 0) {
        System.out.println("Buzz");
    } else {
        System.out.println(i);
    }
}

02Is it prime? Stop at the square root

Naive idea: try every divisor from 2 to n - 1. Better: if n = a × b with a ≤ b, then a ≤ √n. So if nothing up to √n divides n, nothing above does either.

Write the test as d * d <= n instead of d <= Math.sqrt(n): no floating point, no rounding surprises. (For n near Integer.MAX_VALUE, d * d can overflow; use long there.) And break (or return) at the first divisor found.

Main.javawhole filejava
int n = 97;
boolean prime = n > 1;
for (int d = 2; d * d <= n; d++) {
    if (n % d == 0) {
        prime = false;
        break;               // one divisor is enough
    }
}
System.out.println(n + (prime ? " is prime" : " is not prime"));
Is it prime? Stop at the square rootdiagram
Rendering diagram…

03Euclid's GCD: a loop with an unknown count

GCD(48, 18): replace the pair (a, b) by (b, a % b) until b is 0. Then a is the answer. (48, 18) → (18, 12) → (12, 6) → (6, 0), so the GCD is 6.

Why it works: any number that divides both a and b also divides a % b, so the common divisors never change while the numbers shrink fast. The number of steps grows only with the number of digits (logarithmically), which is why it's used in cryptography on numbers hundreds of digits long.

Main.javawhole filejava
int a = 48, b = 18;
while (b != 0) {
    int t = a % b;
    a = b;
    b = t;
}
System.out.println("GCD = " + a);   // 6

04Fibonacci: two variables walking forward

Keep the last two values, prev and curr. Each step, the next value is their sum; then shift both forward. No array needed.

Use long: the 47th Fibonacci number (2,971,215,073) is bigger than Integer.MAX_VALUE, and an int would silently overflow to a negative number. long lasts until the 93rd. Beyond that, BigInteger (Phase 8).

Main.javawhole filejava
long prev = 0, curr = 1;
for (int i = 0; i < 10; i++) {
    System.out.print(prev + " ");
    long next = prev + curr;
    prev = curr;
    curr = next;
}
// 0 1 1 2 3 5 8 13 21 34

05Digits and bases

n % 10 is the last decimal digit and n / 10 removes it. In the same way, n % 2 is the last binary digit and n / 2 removes it. The digits come out last-first, so you either prepend them or reverse at the end.

13 → remainder 1, 6 → 0, 3 → 1, 1 → 1. Reading the remainders backwards gives 1101. Check: Integer.toBinaryString(13) returns "1101".

Main.javawhole filejava
int n = 13;
String bits = "";
while (n > 0) {
    bits = (n % 2) + bits;   // prepend the new digit
    n /= 2;
}
System.out.println(bits);    // 1101

06A checklist for any loop problem

1. Write 3–5 example inputs with their outputs, including 0, 1 and a negative number. 2. Decide what changes each iteration and what stays. 3. Pick the loop (for for a count, while for a condition). 4. Pick the accumulator and its start value. 5. Decide the stop condition, and whether you can stop early. 6. Trace the smallest example on paper. 7. Run every example.

Bugs almost always hide in steps 4 and 5: a product starting at 0, <= instead of <, or a missing early exit.

Try it yourself

  1. 1

    Break FizzBuzz on purpose

    Move the i % 15 == 0 test to the bottom of the ladder. Predict what 15 prints, then run. (Fizz, because 15 % 3 == 0 is checked first.)

  2. 2

    Measure the square-root trick

    In 'Primes up to 50', change the inner test to d < n (no square root). Run it and compare the number of divisions with 95. Then set limit to 10000 for both versions and compare again.

  3. 3

    Find the overflow point

    In 'GCD, LCM and Fibonacci', change 47 to 46 and run. F(46) = 1,836,311,903 still fits in an int. Then try 47 again with long f1, f2.

Code & diagrams

FizzBuzz New tab
Sign in to run this example in your browser.

Expected output

1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
Primes up to 50, and how much work they took New tab
Sign in to run this example in your browser.

Expected output

Primes: 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47
15 primes, 95 divisions
GCD, LCM and Fibonacci New tab

F(47) is 2,971,215,073, which doesn't fit in an int (max 2,147,483,647), so it wraps to a negative number.

Sign in to run this example in your browser.

Expected output

GCD(48, 18) = 6
LCM(48, 18) = 144
Fibonacci: 0 1 1 2 3 5 8 13 21 34 55 89
47th Fibonacci as int: -1323752223 (overflowed)
Digit problems: Armstrong numbers and binary New tab

0 is the edge case: the while loop never runs, so it is handled before the loop.

Sign in to run this example in your browser.

Expected output

3-digit Armstrong numbers: 153 370 371 407
13 in binary: 1101 (check: 1101)
0 in binary: 0 (check: 0)
255 in binary: 11111111 (check: 11111111)

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Start a product at zero

Compute 5! with long fact = 0; instead of 1.

Main.javawhole filejava
public class Main {
    public static void main(String[] args) {
        long fact = 0;
        for (int i = 1; i <= 5; i++) {
            fact *= i;
        }
        System.out.println("5! = " + fact);
    }
}
terminal
$ java Main.java
── what you'll see ──
5! = 0

Break #2

Forget that 0 and 1 aren't prime

Start the prime flag as boolean prime = true; and test n = 1.

Main.javawhole filejava
public class Main {
    public static void main(String[] args) {
        int n = 1;
        boolean prime = true;
        for (int d = 2; d * d <= n; d++) {
            if (n % d == 0) { prime = false; break; }
        }
        System.out.println(n + " prime? " + prime);
    }
}
terminal
$ java Main.java
── what you'll see ──
1 prime? true

Myth vs fact

Myth

Checking divisors up to n / 2 is the efficient prime test.

Fact

Up to √n is enough. For n = 1,000,000 that's 1,000 checks instead of 500,000.

Myth

FizzBuzz is too easy to matter.

Fact

It tests ordering of conditions, the % operator and clean loop structure. Interviewers still use it as a first filter.

Myth

If the program prints the right answer for one input, it's correct.

Fact

Most bugs live at edge cases: 0, 1, negatives, the largest values. Test those on purpose.

Interview problem

The problem

Count the primes below n

Given an int n (up to 5,000,000), return how many prime numbers are strictly less than n. Explain how your solution scales.

You're given

  • n can be 0, 1 or 2 (answer 0).
  • Must finish well under a second for n = 5,000,000.
  • Use only loops, arrays and conditions.

The interviewer follows up

01

Why can the inner marking loop start at i * i?

02

Why does the outer loop stop when i * i >= n?

03

How would you test this?

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    a / gcd * b instead of a * b / gcd avoids intermediate overflow for LCM. Java's Math.multiplyExact and Math.addExact (Java 8) throw ArithmeticException on overflow instead of wrapping, useful when you can't rule it out.

  • ▸

    The JDK already has many of these: Integer.toBinaryString, Integer.bitCount, BigInteger.gcd, BigInteger.isProbablePrime (Miller-Rabin). Know how to write them, then use the library in production.

  • ▸

    % is relatively expensive (tens of cycles for a 32-bit idiv), but the JIT replaces % 10, / 10 and other constant divisors with multiply-and-shift sequences. Division by a variable can't be optimised that way.

  • ▸

    For the sieve, memory access dominates: a boolean[] of 5 million is 5 MB, larger than most L2 caches. Segmented sieves process cache-sized blocks and are several times faster for big n. The DSA course covers sieves and number theory in depth.

Remember this

  1. 1

    Plan before typing. Write a few inputs and their expected outputs, including edge cases (0, 1, negative numbers, the biggest value). Describe the steps in plain words. Then choose the loop: for when you know the count, while when you loop until something happens.

  2. 2

    Order of checks matters (Topic 2.1). In FizzBuzz, test 'divisible by 15' (or both 3 and 5) before 3 and 5 alone, or 15 prints Fizz.

  3. 3

    Stop early when you can (Topic 2.8). To test whether n is prime, you only need to try divisors up to √n, because any factor larger than √n pairs with one smaller than √n. Stop at the first divisor you find. This turns n steps into √n steps: for one million, 1,000 instead of 1,000,000.

  4. 4

    Peel off digits with % 10 and / 10 (Topic 2.5) for digit sums, reversal, palindromes and Armstrong numbers. Repeated division with % 2 and / 2 gives binary digits.

  5. 5

    Keep a running result in an accumulator: a sum starts at 0, a product at 1, a maximum at the first element (or Integer.MIN_VALUE). Choose the type for the largest possible answer: Fibonacci numbers pass int's limit at the 47th term (Topic 1.3).

  6. 6

    Euclid's algorithm for the greatest common divisor, while (b != 0) { int t = a % b; a = b; b = t; }, is over 2,000 years old and still the fastest simple method. It's a perfect example of a loop whose count you don't know in advance.

Explain it without notes

01

Why is it enough to test divisors up to √n when checking whether n is prime?

02

In FizzBuzz, why must the 'divisible by both' check come first?

03

How does Euclid's algorithm work, and why does it finish quickly?

04

What start value should an accumulator have for a sum, a product and a maximum, and why?

Practice

01

Print the sum of all multiples of 3 or 5 below 1000.

02

Print all perfect numbers below 10,000 (a perfect number equals the sum of its divisors excluding itself, like 6 = 1 + 2 + 3).

03

Print the first 10 numbers that are both even and have a digit sum of 10.

04

Count how many steps Euclid's algorithm takes for GCD(1071, 462), and print the GCD.

Trade-offs

  • ↔

    Trial division needs no memory and is best for testing a few numbers; a sieve costs O(n) memory but answers 'all primes below n' far faster.

  • ↔

    Building strings with + in a loop (as in the binary example) is clear but creates a new String each time; for long outputs use StringBuilder (Phase 8).

  • ↔

    int is fast and enough for most counters; long delays overflow; BigInteger never overflows but is much slower. Pick the smallest type that is certainly big enough.

Done when you can

  • Done when you can write FizzBuzz, an is-prime check with √n and early exit, and Euclid's GCD without looking.

  • Done when you can peel digits for sums, reversal, palindromes and binary conversion.

  • Done when you choose accumulator start values and types deliberately, including overflow limits.

  • Done when you test every solution with edge cases (0, 1, negatives, largest values).

  • Done when you can explain the sieve of Eratosthenes and its trade-off against trial division.