Loops in Java — exercises with vowels, grades and the loop that once froze on me

When I wrote my first nested loop, it froze. It wasn’t an infinite loop from forgetting the condition — I had that part under control — it was that I updated the outer loop’s counter inside the inner loop, so the outer one never moved forward. I had to force-close the terminal. If something similar happens with these exercises, it doesn’t mean you’re bad at this, it means nested loops have that exact trap and you need to see it once to stop falling into it.

Here are three loop exercises in Java, from easier to trickier. The last one uses a loop inside another loop, so take your time with it.


Loops in Java exercises — Basic level

Exercise 1 — Vowel counter

Write a program that asks for a word or sentence and counts how many vowels it has, also showing how many times each one appears (a, e, i, o, u).

Expected output with the word programming:

=== VOWEL COUNTER ===
Word or sentence: programming
--- Result ---
Total vowels: 3
a: 1
e: 0
i: 1
o: 1
u: 0

The program should not distinguish between uppercase and lowercase: “Programming” and “programming” should give the same result.

Looping through the word with for (int i = 0; i <= word.length(); i++) instead of i < word.length(). With <= the loop tries to read a position that doesn’t exist (the length equals the first index that’s out of range) and the program crashes with StringIndexOutOfBoundsException. A String‘s indices go from 0 to length - 1, just like an array.

💡 Hints:

  • Go through the word one character at a time with a for and word.charAt(i)
  • Convert each character to lowercase with Character.toLowerCase(c) before comparing it, so you don’t have to check both versions
  • Use five int counters, one per vowel, and add them all up at the end for the total — or increment a general counter alongside each vowel’s counter

Loops in Java exercises — Intermediate level

Exercise 2 — Grades with a sentinel value

Write a program that keeps asking for grades one at a time and adds them up, until the user types -1, which is the signal that they’re done. At the end it should show how many grades were entered, the average, the highest grade and the lowest.

Expected output for a session with grades 6, 8, 4 and 9:

=== COURSE GRADES ===
Enter grades one at a time. Type -1 to finish.
Grade: 6
Grade: 8
Grade: 4
Grade: 9
Grade: -1
--- Result ---
Grades entered: 4
Average: 6.75
Highest grade: 9.0
Lowest grade: 4.0

If the user types -1 on the very first grade, without entering any, the program should warn that there’s no data and should not try to calculate the average, since dividing by 0 grades doesn’t make sense.

This is the classic case where a do-while fits better than a regular while, because you need to read the grade before you can check whether it’s the -1 sentinel — you can’t check the exit condition without having already read a value.

💡 Hints:

  • Use a do-while that keeps asking for grades while the last one entered isn’t -1
  • For the highest and lowest grade, you don’t need to store every grade in an array: two variables, highest and lowest, updated each time a better or worse grade comes in, are enough
  • Initialize highest and lowest with the first real grade entered, not with 0, because if every grade is low, 0 would never get updated as the minimum

Loops in Java exercises — Final challenge

Exercise 3 — Prime numbers in a range

Write a program that asks for two integers, a start and an end, and shows every prime number in that range (both ends included). A number is prime if it’s only divisible by 1 and by itself, and by convention 1 doesn’t count as prime.

Expected output for the range 10 to 30:

=== PRIME NUMBERS IN A RANGE ===
From: 10
To: 30
--- Primes found ---
11
13
17
19
23
29
Total: 6 primes

If the start number is greater than the end number, the program should warn that the range is invalid and shouldn’t try to search for anything.

To check whether a number n is prime, you don’t need to test every divisor up to n - 1. Going up to the square root of n is enough, because if n had a divisor larger than its square root, it would necessarily also have one smaller, and you’d already have found it. With numbers this small the difference isn’t noticeable, but it’s why real-world prime searches don’t loop through the whole number.

💡 Hints:

  • The outer loop goes through each number in the range with a for; the inner loop checks whether that number has any divisor
  • In the inner loop, as soon as you find a divisor you can cut with break — you already know it’s not prime, checking further is wasted work
  • A handy way to know, after the inner loop, whether the number turned out to be prime is a boolean isPrime that starts as true and only becomes false if a divisor is found

What’s wrong here?

Before the solutions, three common mistakes from this part of the syllabus. Can you spot the problem in each one?

Mistake 1

int i = 0;
while (i < 5) {
    System.out.println("Round " + i);
}
// Freezes forever

i++ is missing inside the loop. The condition i < 5 never stops being true because i never changes, so the program enters an infinite loop. It’s exactly the mistake I made with my first nested loop, only here it’s easier to spot:

int i = 0;
while (i < 5) {
    System.out.println("Round " + i);
    i++;    // ← without this, i is always 0
}

Mistake 2

for (int i = 0; i < 10; i++) {
    if (i == 5) {
        break;
    }
    System.out.println(i);
}
// Does it print up to 9, or stop earlier?

This isn’t a compile or runtime error, it’s an error of intent: if the goal was to just skip 5 and keep going with 6, 7, 8 and 9, break is the wrong choice — break cuts the entire loop, it doesn’t skip one round. What’s needed here is continue, which does skip only the current round and moves on to the next one:

for (int i = 0; i < 10; i++) {
    if (i == 5) {
        continue;    // ← skips 5, but continues with 6, 7, 8, 9
    }
    System.out.println(i);
}

Mistake 3

for (int i = 0; i < 3; i++) {
    for (int i = 0; i < 3; i++) {    // ← same variable name
        System.out.println(i);
    }
}
// Doesn't compile

Java won’t let you declare two variables with the same name in nested loops if one is inside the other, because the inner i would be “shadowing” the outer i within its own scope, and Java forbids that outright rather than letting you get confused. The fix is to use different names, usually i for the outer loop and j for the inner one:

for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 3; j++) {    // ← different name
        System.out.println(i + "," + j);
    }
}

Commented solutions

Solution Exercise 1

import java.util.Scanner;

public class VowelCounter {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        System.out.println("=== VOWEL COUNTER ===");
        System.out.print("Word or sentence: ");
        String text = sc.nextLine();

        // One counter per vowel — all start at 0
        int countA = 0, countE = 0, countI = 0, countO = 0, countU = 0;

        // We go through the text one character at a time
        for (int i = 0; i < text.length(); i++) {
            // toLowerCase means we don't have to check upper and lower case separately
            char c = Character.toLowerCase(text.charAt(i));

            // switch works on a char the same way it does on an int or a String
            switch (c) {
                case 'a':
                    countA++;
                    break;
                case 'e':
                    countE++;
                    break;
                case 'i':
                    countI++;
                    break;
                case 'o':
                    countO++;
                    break;
                case 'u':
                    countU++;
                    break;
                // if it's not a vowel, we do nothing — no need for a default
            }
        }

        // The total is the sum of the five individual counters
        int total = countA + countE + countI + countO + countU;

        System.out.println();
        System.out.println("--- Result ---");
        System.out.println("Total vowels: " + total);
        System.out.println("a: " + countA);
        System.out.println("e: " + countE);
        System.out.println("i: " + countI);
        System.out.println("o: " + countO);
        System.out.println("u: " + countU);

        sc.close();
    }
}

Solution Exercise 2

import java.util.Scanner;

public class GradesSentinel {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        System.out.println("=== COURSE GRADES ===");
        System.out.println("Enter grades one at a time. Type -1 to finish.");

        double sum = 0;
        int count = 0;
        double highest = 0;
        double lowest = 0;
        double grade;

        // do-while because we need to read the first grade before we can
        // check whether it's the -1 sentinel
        do {
            System.out.print("Grade: ");
            grade = sc.nextDouble();

            // If it's the sentinel, we don't process it as a real grade
            if (grade != -1) {
                sum += grade;

                // The first real grade initializes highest and lowest;
                // the following ones only update if they beat the stored value
                if (count == 0) {
                    highest = grade;
                    lowest = grade;
                } else {
                    if (grade > highest) {
                        highest = grade;
                    }
                    if (grade < lowest) {
                        lowest = grade;
                    }
                }
                count++;
            }
        } while (grade != -1);

        System.out.println();
        System.out.println("--- Result ---");

        // With no grades, calculating the average would divide by 0 — we avoid that
        if (count == 0) {
            System.out.println("No grade was entered");
        } else {
            double average = sum / count;
            System.out.println("Grades entered: " + count);
            System.out.printf("Average: %.2f%n", average);
            System.out.println("Highest grade: " + highest);
            System.out.println("Lowest grade: " + lowest);
        }

        sc.close();
    }
}

Solution Exercise 3

import java.util.Scanner;

public class PrimesInRange {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        System.out.println("=== PRIME NUMBERS IN A RANGE ===");
        System.out.print("From: ");
        int start = sc.nextInt();
        System.out.print("To: ");
        int end = sc.nextInt();

        // We cut right here if the range doesn't make sense
        if (start > end) {
            System.out.println("✗ The start can't be greater than the end");
            sc.close();
            return;
        }

        System.out.println();
        System.out.println("--- Primes found ---");
        int totalPrimes = 0;

        // Outer loop: goes through every number in the range
        for (int number = start; number <= end; number++) {
            // 1 doesn't count as prime by convention, so we skip it
            if (number < 2) {
                continue;
            }

            boolean isPrime = true;

            // Inner loop: looks for a divisor of "number"
            // Going up to its square root is enough — if there's no divisor
            // up to there, there won't be one beyond it either
            for (int divisor = 2; divisor <= Math.sqrt(number); divisor++) {
                if (number % divisor == 0) {
                    isPrime = false;
                    break;    // we already know it's not prime, no need to keep checking
                }
            }

            if (isPrime) {
                System.out.println(number);
                totalPrimes++;
            }
        }

        System.out.println("Total: " + totalPrimes + " primes");

        sc.close();
    }
}

Look closely at the break in the inner loop: as soon as a divisor is found, there’s no need to keep testing the following ones — the number isn’t prime and checking further would be wasted work. It’s the same idea as the break in Exercise 2 of control flow, back when you’d already found the book you were looking for.


See it with Python Tutor

Select Java from the dropdown and paste this into pythontutor.com:

public class Demo {
    public static void main(String[] args) {
        int totalPrimes = 0;
        for (int number = 10; number <= 15; number++) {
            boolean isPrime = true;
            for (int divisor = 2; divisor <= Math.sqrt(number); divisor++) {
                if (number % divisor == 0) {
                    isPrime = false;
                    break;
                }
            }
            if (isPrime) {
                totalPrimes++;
            }
        }
        System.out.println("Primes found: " + totalPrimes);
    }
}

Step through it and watch how the two loops behave: on every pass of the outer loop (number), the inner loop (divisor) starts over from scratch. Pay special attention to number = 12: as soon as divisor reaches 2, 12 % 2 == 0 is true, isPrime becomes false, and the break cuts the inner loop right away — it never gets to test divisor = 3. It’s the same mechanism that froze on my first attempt, except here you can watch it unfold step by step instead of suffering through a frozen terminal.


Summary

  • A loop always needs a way to end: check that the control variable changes inside the loop, or you’ll freeze like I did
  • for is the natural choice when you know in advance how many rounds you’re going to run; while and do-while fit better when it depends on a condition you don’t control from the start
  • do-while is the right pick when you need to run the loop body before you can check the exit condition — like reading a value to find out if it’s the sentinel
  • break cuts the entire loop; continue only skips the current round and moves on — mixing them up is a mistake of intent, not of syntax
  • In nested loops, use different variable names (i, j) for each level — Java won’t even let you compile if you repeat the name
  • Update the right loop’s counter: when nesting loops, it’s easy to accidentally touch the outer variable inside the inner loop

Cheat sheet — loops in Java

// ============================================
// CHEAT SHEET — Loops in Java
// Sergio Learns · sergiolearns.com
// ============================================

// FOR — when you know how many rounds you'll run
for (int i = 0; i < n; i++) {
    ...
}

// WHILE — repeats while the condition is true
while (condition) {
    ...
    // something inside the loop must be able to make condition false
}

// DO-WHILE — the body ALWAYS runs at least once,
// the condition is checked at the end
do {
    ...
} while (condition);

// BREAK — cuts the entire loop right away
for (int i = 0; i < 10; i++) {
    if (i == 5) {
        break;      // exits the for, never reaches i = 6, 7, 8, 9
    }
}

// CONTINUE — skips only the current round, the loop goes on
for (int i = 0; i < 10; i++) {
    if (i == 5) {
        continue;   // skips 5, continues with 6, 7, 8, 9
    }
}

// NESTED LOOPS — different variable names at each level
for (int i = 0; i < rows; i++) {
    for (int j = 0; j < columns; j++) {
        ...
    }
}

// SENTINEL VALUE — repeat until a stop signal arrives
double value;
do {
    value = sc.nextDouble();
    if (value != -1) {
        // process value
    }
} while (value != -1);

// LOOPING THROUGH A STRING CHARACTER BY CHARACTER
for (int i = 0; i < text.length(); i++) {
    char c = text.charAt(i);
    ...
}

// ACCUMULATOR AND COUNTER — the most common pattern inside a loop
double sum = 0;
int count = 0;
// inside the loop: sum += value; count++;
double average = sum / count;   // ← check count != 0 first

// OPTIMIZING A DIVISOR SEARCH
for (int d = 2; d <= Math.sqrt(number); d++) {
    if (number % d == 0) {
        break;   // found — no need to keep going
    }
}
Keep goingAll Java (en) articles

Sigue aprendiendo

Leave a Reply

Your email address will not be published. Required fields are marked *