Operators & Control Flow: Making Decisions and Repeating Work
So far your programs have run straight down the page, one line after another. Real programs—and real tests—need to make choices (“if the login succeeded, check the dashboard; otherwise record a failure”) and repeat work (“for each row in the results table, verify the price”). This module gives you both powers. You will learn the operators that let Java calculate and compare, then the control-flow statements—if/else, switch, and the three kinds of loop—that let a program branch and repeat. These are the structures that turn a static script into something that reacts to what it finds, which is the heart of every automated test.
Learning objectives
By the end of this lesson you will be able to:
- Use arithmetic, relational, logical, and assignment operators correctly.
- Reason about operator precedence and use parentheses to make intent clear.
- Write
if,else if, andelsedecisions, and use the ternary operator for short choices. - Use a
switchstatement (and the modern switch expression) for multi-way branching. - Repeat work with
for,while, anddo-whileloops. - Control loops with
breakandcontinue, and combine them with nested loops. - Connect these structures to real test logic, such as iterating over elements on a page.
Operators
An operator is a symbol that performs an operation on one or more values (called operands) and produces a result. Java groups them into a few families.
Arithmetic operators
These do maths: + (add), - (subtract), * (multiply), / (divide), and % (modulus—the remainder after division).
int a = 17, b = 5;
System.out.println(a + b); // 22
System.out.println(a - b); // 12
System.out.println(a * b); // 85
System.out.println(a / b); // 3 (integer division: the remainder is discarded)
System.out.println(a % b); // 2 (17 divided by 5 leaves a remainder of 2)
The modulus operator % is more useful than it first looks. number % 2 == 0 is the standard way to test whether a number is even. In testing you might use it to do something on every third row, or to split work across parallel threads.
Relational (comparison) operators
These compare two values and always produce a boolean (true or false): == (equal to), != (not equal to), >, <, >=, <=.
int score = 72;
System.out.println(score >= 50); // true (did they pass?)
System.out.println(score == 100); // false
System.out.println(score != 72); // false
Note the double equals == for comparison. A single = means assignment (“put this value in the box”), which is a different thing entirely—mixing them up is a classic bug.
Logical operators
These combine boolean values: && (AND—true only if both sides are true), || (OR—true if either side is true), and ! (NOT—flips true to false).
boolean loggedIn = true;
boolean isAdmin = false;
System.out.println(loggedIn && isAdmin); // false (needs both)
System.out.println(loggedIn || isAdmin); // true (either will do)
System.out.println(!isAdmin); // true (flips false to true)
Both && and || are “short-circuiting”: Java stops evaluating as soon as the answer is certain. In a && b, if a is false, b is never checked. This is not just an optimisation—it is a safety tool. You can write if (element != null && element.isDisplayed()) knowing the second check is skipped when the element is null, avoiding a crash.
Assignment operators
Beyond the basic =, Java has shorthand combinations: +=, -=, *=, /=, and the increment/decrement operators ++ and --.
int total = 10;
total += 5; // same as total = total + 5; -> 15
total -= 3; // 12
total++; // 13 (increment by 1)
total--; // 12 (decrement by 1)
You will see ++ constantly as the step in for loops below.
Operator precedence: the maths you already know
You learned precedence years ago in arithmetic—“multiply before you add”. Java follows the same idea. In 2 + 3 * 4, the multiplication happens first, giving 14, not 20. The rough order is: ! and ++/-- first, then * / %, then + -, then the relational operators, then &&, then ||, and assignment last. But here is the professional’s secret: do not memorise the full table. When precedence is not blindingly obvious, add parentheses. (price * quantity) > budget is clearer than relying on the rules, and the compiler and your future self both thank you. Parentheses cost nothing and remove all doubt.
The if / else if / else statement
The if statement runs a block of code only when a condition is true. Add else if for more conditions and else for the fallback when none matched. Conditions go in parentheses; the conditional code goes in braces.
int score = 72;
if (score >= 90) {
System.out.println("Grade: A");
} else if (score >= 75) {
System.out.println("Grade: B");
} else if (score >= 50) {
System.out.println("Grade: C");
} else {
System.out.println("Grade: Fail");
}
// Prints "Grade: C" because 72 is >= 50 but not >= 75
Java checks the conditions top to bottom and runs the first one that is true, then skips the rest. Order matters: if you put score >= 50 first, a score of 95 would wrongly print “C”. In a test, this is exactly how you might branch on an HTTP status code or a page state.
Common mistakes with conditions
- Using
=instead of==.if (x = 5)is assignment, not comparison. Withintthis won’t even compile, which is lucky—but the habit of writing==for comparison is essential. - Comparing Strings with
==. For text, always use.equals(): writeif (status.equals("PASS")), notif (status == "PASS"). Using==compares memory references, not the actual characters, and gives unreliable results. - The dangling-brace trap. If you omit braces, only the next single line is governed by the
if. Always use braces, even for one line—it prevents a whole class of bugs when you add a second line later. - A stray semicolon:
if (x > 0);ends the statement immediately, so the block that follows always runs. Watch for that misplaced;.
The ternary operator
For a simple either/or choice, the ternary operator ? : is a compact shorthand for an if/else that produces a value. Read it as “condition ? value-if-true : value-if-false”.
int score = 72;
String result = (score >= 50) ? "PASS" : "FAIL";
System.out.println(result); // PASS
// Equivalent to:
// String result;
// if (score >= 50) { result = "PASS"; } else { result = "FAIL"; }
Ternaries are great for short assignments but become unreadable when nested. If you find yourself stacking them, switch back to a normal if/else.
The switch statement
When you are comparing one variable against many fixed values, a chain of else ifs gets noisy. A switch is cleaner. Each case is a value to match; break stops execution falling through to the next case; default handles anything unmatched.
String browser = "firefox";
switch (browser) {
case "chrome":
System.out.println("Launching Chrome");
break;
case "firefox":
System.out.println("Launching Firefox");
break;
case "edge":
System.out.println("Launching Edge");
break;
default:
System.out.println("Unknown browser: " + browser);
}
// Prints "Launching Firefox"
Forgetting break causes fall-through: execution continues into the next case, often a bug. Modern Java (14+) offers a tidier switch expression with arrow syntax that needs no break and can return a value:
String message = switch (browser) {
case "chrome" -> "Launching Chrome";
case "firefox" -> "Launching Firefox";
case "edge" -> "Launching Edge";
default -> "Unknown browser: " + browser;
};
System.out.println(message);
This pattern is common in test frameworks for turning a configuration string into the right browser driver.
Loops: repeating work
A loop repeats a block of code. This is where automation earns its name—you rarely verify just one thing; you verify every row, every link, every field. Java has three loop forms.
The for loop
Use a for loop when you know how many times to repeat (or you are counting through a range). It has three parts in the parentheses: initialise; condition; update.
for (int i = 0; i < 5; i++) {
System.out.println("Iteration number " + i);
}
// Prints 0, 1, 2, 3, 4
Read it as: start i at 0; keep going while i < 5; after each pass, increase i by 1. Counting from 0 feels odd at first but is the universal convention—and it matches how lists and arrays are numbered (next module), so embrace it early.
The while loop
Use a while loop when you do not know the count in advance—you simply repeat as long as a condition holds. The condition is checked before each pass, so the body might run zero times.
int attempts = 0;
boolean pageLoaded = false;
while (!pageLoaded && attempts < 3) {
System.out.println("Checking if page loaded, attempt " + attempts);
// ... imagine a real check here that might set pageLoaded = true
attempts++;
}
Something inside the loop must eventually make the condition false—here, attempts++. Forget that, and you get an infinite loop that never ends.
The do-while loop
A do-while is a while that checks its condition at the bottom, so the body always runs at least once. Use it when you must do something before you can test whether to continue, like prompting a user until they give valid input.
int number;
Scanner scanner = new Scanner(System.in);
do {
System.out.print("Enter a number between 1 and 10: ");
number = scanner.nextInt();
} while (number < 1 || number > 10);
System.out.println("Thanks, you entered " + number);
break and continue
Two statements fine-tune loop behaviour. break exits the loop entirely, right now. continue skips the rest of the current pass and jumps to the next one.
for (int i = 1; i <= 10; i++) {
if (i == 7) {
break; // stop the loop completely when we reach 7
}
if (i % 2 == 0) {
continue; // skip even numbers, go to next i
}
System.out.println(i);
}
// Prints 1, 3, 5 (stops before 7, skips evens)
In testing, break is handy for “stop as soon as you find the matching row”, and continue for “skip the rows that do not apply”.
Nested loops
A loop can contain another loop. The inner loop runs completely for each pass of the outer loop. This is how you work through a grid, a table of rows and columns, or—in Selenium—each cell of a results table.
for (int row = 1; row <= 3; row++) {
for (int col = 1; col <= 2; col++) {
System.out.println("Row " + row + ", Column " + col);
}
}
// Row 1, Column 1 / Row 1, Column 2 / Row 2, Column 1 ... etc.
Tutor tip: loops are the engine of data-driven testing
Almost every powerful testing pattern is a loop in disguise. “Run this login test with ten different username/password combinations” is a loop over a list of data. “Check that every product on the page has a price” is a loop over the elements Selenium found. “Keep polling until the spinner disappears” is a while loop with a timeout. When you later meet data-driven tests and explicit waits, you will recognise the control-flow structures from this very lesson underneath them. Master loops now and that advanced material will feel familiar rather than foreign.
Hands-on exercise
Create a class ControlFlowPractice and solve each part in main:
- FizzBuzz (the classic). Loop from 1 to 30. Print “Fizz” if the number is divisible by 3, “Buzz” if divisible by 5, “FizzBuzz” if divisible by both, otherwise the number itself. (Hint: use
%and check the “both” case first.) - Grader. Given an
int score, useif/else if/elseto print a letter grade (A/B/C/Fail), then produce the same PASS/FAIL result in one line using the ternary operator. - Browser switch. Given a
String browser, use aswitchexpression to produce a launch message for chrome, firefox, and edge, with a sensibledefault. - Retry loop. Simulate retrying an action up to 3 times using a
whileloop and a counter, stopping early withbreakif a booleansuccessbecomes true. - Multiplication grid. Use nested
forloops to print the multiplication tables from 1 to 5.
Knowledge check
- What does the modulus operator
%return, and how do you test whether a number is even? - What is the difference between
=and==? - What does “short-circuiting” mean for
&&and||, and why is it useful? - In
2 + 3 * 4, what is the result and why? How would you force addition first? - Why should you compare Strings with
.equals()rather than==? - What happens if you forget
breakin a traditionalswitchcase? - Which loop guarantees its body runs at least once, and why?
- What is the difference between
breakandcontinue?
Show answers
%returns the remainder of a division; a number is even whennumber % 2 == 0.=assigns a value to a variable;==compares two values and returns a boolean.- Evaluation stops as soon as the result is certain (if the left of
&&is false, or the left of||is true). It is useful for guarding against errors, e.g.element != null && element.isDisplayed(). - It is
14, because*has higher precedence than+. Force addition first with parentheses:(2 + 3) * 4gives20. ==compares object references, not the text itself, so it can return false for equal strings;.equals()compares the actual characters.- Execution “falls through” into the following case(s), running code you did not intend—usually a bug.
- The
do-whileloop, because it checks its condition at the bottom, after running the body once. breakexits the loop entirely;continueskips the rest of the current iteration and moves to the next one.
Key takeaways
- Operators come in families: arithmetic (
+ - * / %), relational (== != > <…), logical (&& || !), and assignment (= += ++). - When precedence is unclear, use parentheses rather than memorising the table.
if/else if/elserun the first true branch; the ternary? :is a compact value-producing shorthand;switchhandles many fixed values cleanly.- Use
forwhen you know the count,whilewhen you do not, anddo-whilewhen the body must run at least once. breakexits a loop;continueskips to the next pass; nested loops walk grids and tables—the backbone of data-driven testing.
Go deeper
These extras build directly on the operators and control flow you just met, and they turn up constantly in real test code.
Labelled break for nested loops. A plain break leaves only the innermost loop. When you have found your match in a grid and want to exit both loops at once, label the outer loop and break to that label:
search:
for (int row = 0; row < grid.length; row++) {
for (int col = 0; col < grid[row].length; col++) {
if (grid[row][col].equals("target")) {
break search; // leaves BOTH loops at once
}
}
}
- Bitwise vs logical. A single
&or|also works on booleans, but unlike&&and||it does not short-circuit—both sides always run. Keep to the double forms in conditions so a guard likeelement != null && element.isDisplayed()stays safe. - Modulus with negatives. In Java the result of
%takes the sign of the left operand, so-7 % 3is-1, not2. If you lean on%to wrap an index around, guard against negative inputs first. - A switch that returns a value. The arrow switch expression can compute a result directly, and
yieldlets a multi-line case hand one back—useful for mapping a config string to a value with no temporary variable.