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.
Table of Contents
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
forandword.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
intcounters, 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-whilethat 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,
highestandlowest, updated each time a better or worse grade comes in, are enough - Initialize
highestandlowestwith the first real grade entered, not with0, because if every grade is low,0would 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 isPrimethat starts astrueand only becomesfalseif 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
foris the natural choice when you know in advance how many rounds you’re going to run;whileanddo-whilefit better when it depends on a condition you don’t control from the startdo-whileis 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 sentinelbreakcuts the entire loop;continueonly 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