Skip to content
SparkTime, home
Java From Zero to Job-Ready
Lessons
1 · Java Foundations
2 · Object-Oriented Java
3 · Core Java
4 · Collections and Generics
5 · Functional Java
6 · The JVM and Concurrency
7 · Tools and Craft
8 · Databases and JDBC
9 · Web and Spring
10 · Persistence with JPA
11 · Secure, Tested, Production-Ready
12 · Interviews and Readiness
Lesson 3 of 41

Lesson 3 · Java Foundations

Decisions, Loops, Methods and Scope

How to make a Java program choose and repeat: if, switch (old statement and modern expression), the loop forms, and how to organise code into methods. Includes the pass-by-value rule that interviewers love and the scope rules that cause mysterious compile errors.

On this page

Key takeaways

  • if, switch and the conditional operator choose between paths. A modern switch expression with arrows returns a value, cannot fall through, and must cover every case.
  • Use for when you know the count, enhanced for to visit every element, and while when you repeat until a condition changes. Check loop boundaries carefully to avoid off-by-one errors.
  • Methods name a piece of work so you can reuse and test it. Small methods with one clear purpose and a descriptive name are the main tool for keeping programs readable.
  • Java is always pass by value. For an object, the value passed is the reference, so a method can change the object's contents but cannot make the caller's variable point somewhere else.
  • A variable exists only within the block where it is declared. Declare it in the smallest scope that works.

A program that only ever runs top to bottom, doing the same thing every time, is a calculator with one button. Useful software does things like "if the user is an admin, show the dashboard", "for every order in the list, add up the total" and "keep asking until the password is valid". These need two capabilities: choosing between paths and repeating work. And once a program can do many things, you need a way to organise them so that you can understand it a month later. That is what methods are for.

What you will learn

#
  • How if, else, the conditional operator and switch choose a path, including modern switch expressions.
  • The four loop forms and how to avoid off-by-one errors.
  • How to write methods, and how Java passes arguments.
  • How scope decides where a variable exists.
  • How to break a problem into small methods.

Making decisions

#

if, else if, else

#
Java
// File: Main.javapublic class Main {    static String grade(int score) {        if (score < 0 || score > 100) {            return "invalid";        } else if (score >= 75) {            return "distinction";        } else if (score >= 40) {            return "pass";        } else {            return "fail";        }    }    public static void main(String[] args) {        int[] scores = {92, 75, 40, 39, 101};        for (int score : scores) {            System.out.println(score + " -> " + grade(score));        }    }}
Code
92 -> distinction75 -> distinction40 -> pass39 -> fail101 -> invalid

The conditions are checked in order, and the first one that is true wins, so the order matters. The check for invalid input comes first because otherwise 101 would be called a distinction. Always start with the cases that rule others out. Remember the rule from the previous article: the condition must be a boolean.

A compact form for a simple choice between two values is the conditional operator: String label = (count == 1) ? "item" : "items";. Use it for short, clear choices, and use if when the branches do more.

switch: the older statement

#

When you compare one value against many constants, switch is tidier than a chain of if. Here is the older style that you will find in a great deal of existing code, including a classic bug:

Java
// File: Main.javapublic class Main {    static void describeOld(int day) {        String type = "";        switch (day) {            case 6:            case 7:                type = "weekend";                break;            case 1:                type = "start of week";            case 2:                type = type + " midweek";                break;            default:                type = "weekday";        }        System.out.println(day + ": " + type);    }    public static void main(String[] args) {        describeOld(7);        describeOld(1);        describeOld(3);    }}
Code
7: weekend1: start of week midweek3: weekday

Look at day 1: it printed start of week midweek. There is no break after case 1, so execution falls through into case 2. Sometimes fall-through is deliberate (as with case 6 and case 7 sharing one result), but forgetting a break is a common source of bugs, and the code gives no warning.

switch expressions: the modern form

#

JEP 361 (final in Java 14) added the switch expression, designed to remove these problems. It uses -> instead of :, so there is no fall-through, and it produces a value. When many branches simply assign to one variable or return a value, the JEP notes, the old form is roundabout, repetitive and error-prone.

Java
// File: Main.javapublic class Main {    static String describe(int day) {        return switch (day) {            case 6, 7 -> "weekend";            case 1 -> "start of week";            case 2, 3, 4, 5 -> "weekday";            default -> throw new IllegalArgumentException("not a day: " + day);        };    }    static int lengthOfMonth(int month, boolean leapYear) {        return switch (month) {            case 2 -> {                if (leapYear) {                    yield 29;                }                yield 28;            }            case 4, 6, 9, 11 -> 30;            default -> 31;        };    }    public static void main(String[] args) {        System.out.println(describe(7));        System.out.println(describe(1));        System.out.println(lengthOfMonth(2, true) + " " + lengthOfMonth(2, false) + " " + lengthOfMonth(4, false));        try {            describe(9);        } catch (IllegalArgumentException e) {            System.out.println("caught: " + e.getMessage());        }    }}
Code
weekendstart of week29 28 30caught: not a day: 9

Three things to notice. First, several constants can share one branch: case 6, 7 ->. Second, when a branch needs more than one statement, you use a block and yield the result (the yield keyword is to switch expressions what return is to methods). Third, a switch expression must be exhaustive: for every possible value some branch must match. For an int, that means a default. The compiler checks this, so you cannot forget a case silently. (For enums and sealed types, the compiler can verify exhaustiveness without a default, which we use in article 9.)

Prefer the arrow form in new code. Keep the older form in mind so you can read and safely change code written before Java 14.

Repeating work: loops

#

A loop repeats a block while a condition holds. Java has four forms.

Java
// File: Main.javapublic class Main {    public static void main(String[] args) {        // for: use when you know how many times        int sum = 0;        for (int i = 1; i <= 5; i++) {            sum += i;        }        System.out.println("sum 1..5 = " + sum);        // enhanced for: visit every element        String[] fruits = {"mango", "apple", "banana"};        for (String fruit : fruits) {            System.out.print(fruit.length() + " ");        }        System.out.println();        // while: repeat until a condition changes        int n = 100;        int halvings = 0;        while (n > 1) {            n = n / 2;            halvings++;        }        System.out.println("halvings = " + halvings);        // do-while: body runs at least once        int attempts = 0;        do {            attempts++;        } while (attempts < 3);        System.out.println("attempts = " + attempts);        // break and continue        for (int i = 1; i <= 10; i++) {            if (i % 2 == 0) {                continue;       // skip even numbers            }            if (i > 7) {                break;          // stop the loop entirely            }            System.out.print(i + " ");        }        System.out.println();    }}
Code
sum 1..5 = 155 5 6halvings = 6attempts = 31 3 5 7

Loop

Use it when

Notes

for (init; condition; update)

You know the count, or you need the index

All three parts are optional, but a missing condition means "forever"

for (Type x : collection)

You want every element, in order, and do not need the index

Works on arrays and anything iterable (article 11)

while (condition)

You repeat until something changes, and may run zero times

The body must change something that affects the condition

do { } while (condition)

The body must run at least once

Rare, but natural for "ask, then validate"

break leaves the loop, and continue skips to the next iteration.

Thinking about loops safely

#

Most loop bugs come from boundaries. Off-by-one errors are the classic: should the loop run while i < length or i <= length? Arrays have valid indexes from 0 to length - 1, so the correct loop is i < length. A habit that prevents many bugs is to check your loop by hand with the smallest cases: what happens with an empty array? With one element? The other classic bug is an infinite loop, where the body never changes anything that affects the condition. If your program hangs, this is the first suspect.

Prefer the enhanced for when you do not need the index. It cannot be wrong about the boundaries, because there are none for you to get wrong.

Methods: naming a piece of work

#

You have already used methods: main, grade, describe. A method is a named block of code that can take inputs (parameters) and give back a result (a return value). Methods exist for four reasons:

  1. Reuse: write the logic once, call it from many places.
  2. Naming: isLeapYear(year) tells the reader what the code means. A comment cannot keep up with code, but a good name can.
  3. Testing: a method with clear inputs and outputs can be tested on its own (article 25).
  4. Managing complexity: you only have to hold one small piece in your head at a time.
Java
static double average(int[] values) {      // return type, name, parameters    int total = 0;    for (int v : values) {        total += v;    }    return (double) total / values.length;}

The parts of the signature are the return type (double), the name (average) and the parameter list (int[] values). void means "returns nothing". For now we write static methods, which belong to the class and are called directly. In article 4 we will meet methods that belong to objects.

Guidelines that serve you for the whole of your career:

  • One method, one job. If you need "and" to describe it, split it.
  • A name that says what, not how: calculateTotal, not loopOverItems.
  • Few parameters. More than three or four is a hint that something should be an object.
  • Return early for invalid or trivial cases, as in the grade method, instead of nesting deeply.

Variable arguments

#

A method can take a variable number of arguments of the same type using ...: static int sum(int... numbers). Inside the method, numbers is an array. Use it sparingly, for utilities such as String.format.

Pass by value: what really happens to arguments

#

Interviewers like this one, and the truth is simple: Java is always pass by value. The method receives a copy of whatever the caller passed. The Oracle tutorial states it directly: primitive arguments are passed by value, and reference arguments are also passed by value, so when the method returns, the passed-in reference still references the same object as before, but the values of the object's fields can be changed in the method.

Java
// File: Main.javaimport java.util.Arrays;public class Main {    static void changePrimitive(int x) {        x = 99;                        // changes only the local copy    }    static void changeContents(int[] data) {        data[0] = 99;                  // follows the reference: changes the shared array    }    static void reassign(int[] data) {        data = new int[] {7, 7, 7};    // changes only the local copy of the reference    }    public static void main(String[] args) {        int number = 1;        changePrimitive(number);        System.out.println(number);        int[] array = {1, 2, 3};        changeContents(array);        System.out.println(Arrays.toString(array));        reassign(array);        System.out.println(Arrays.toString(array));    }}
Code
1[99, 2, 3][99, 2, 3]

The number stays 1. The array's contents did change, because the method received a copy of the reference, which still points to the same array. And reassign had no effect on the caller, because it replaced only its own copy of the reference. When someone says "Java passes objects by reference", this is what they are loosely describing. The accurate description is that Java passes the reference by value.

Scope: where a variable exists

#

A variable can be used only in the part of the program where it is in scope: from its declaration to the end of the block ({ ... }) that contains it. Parameters are scoped to their method. A loop variable declared in the for header exists only in that loop.

Java
// File: Main.javapublic class Main {    public static void main(String[] args) {        for (int i = 0; i < 3; i++) {            int squared = i * i;        }        System.out.println(squared);    }}
Code
Main.java:7: error: cannot find symbol        System.out.println(squared);                           ^  symbol:   variable squared  location: class Main1 error

Neither i nor squared exists after the loop. The compiler reports "cannot find symbol", which is its way of saying that the name is not in scope here. The remedy is to declare the variable in the scope where you need it. Equally important is the reverse principle: declare variables in the narrowest scope that works. A variable that lives only inside the loop cannot be accidentally used or changed elsewhere.

Java does not let an inner variable reuse the name of a local variable from an enclosing block (that is an error), but a parameter or local variable can shadow a field, which we meet in article 4.

Putting it together: a small program built from small methods

#

Here is a program that reads a list of daily steps and reports statistics. Each piece of work is a method with one purpose, and main reads like a description of the task.

Java
// File: Main.javapublic class Main {    static int max(int[] values) {        int best = values[0];        for (int v : values) {            if (v > best) {                best = v;            }        }        return best;    }    static double average(int[] values) {        long total = 0;        for (int v : values) {            total += v;        }        return (double) total / values.length;    }    static int countAbove(int[] values, double threshold) {        int count = 0;        for (int v : values) {            if (v > threshold) {                count++;            }        }        return count;    }    static String describe(double average) {        return switch ((int) (average / 5000)) {            case 0 -> "low activity";            case 1 -> "moderate activity";            default -> "high activity";        };    }    public static void main(String[] args) {        int[] steps = {4200, 8100, 6500, 12000, 3000, 9800, 7100};        double avg = average(steps);        System.out.println("days: " + steps.length);        System.out.println("max: " + max(steps));        System.out.printf("average: %.1f%n", avg);        System.out.println("days above average: " + countAbove(steps, avg));        System.out.println(describe(avg));    }}
Code
days: 7max: 12000average: 7242.9days above average: 3moderate activity

Notice the design choices: average accumulates in a long to avoid overflow (article 2), the methods do not print (so they can be reused and tested), and main is the only method that talks to the outside world. System.out.printf formats output, and %n is the platform-independent line break. This habit of separating computation from input and output is the seed of the layered architecture you will use in the projects.

One limitation: max crashes with an exception if the array is empty. Handling that sensibly is an exception-design question, which is the topic of article 10.

Common mistakes

#
  • Forgetting break in an old-style switch, and falling through.
  • Putting conditions in the wrong order in an if/else if chain.
  • Off-by-one errors in loop boundaries.
  • An infinite loop, because the loop body never changes the condition.
  • Using = instead of == in a condition. For int variables the compiler stops you, because the result is not a boolean.
  • Believing Java passes objects by reference, and expecting a reassigned parameter to change the caller's variable.
  • Declaring a variable too early or in too wide a scope, then reusing it by accident.
  • Writing one giant main. Split it into methods.

Exercises

#

Exercise 1

Predict

#

What does this print? int total = 0; for (int i = 0; i < 5; i++) { if (i == 3) continue; total += i; } System.out.println(total);

Exercise 2: Write a method

#

Write static boolean isLeapYear(int year). A year is a leap year if it is divisible by 4, except years divisible by 100, unless the year is also divisible by 400. Test it on 1900, 2000, 2024 and 2026.

Exercise 3: Convert old to modern

#

Rewrite this old-style switch statement as a switch expression and explain what bug risk disappears: switch (level) { case 1: label = "low"; break; case 2: case 3: label = "medium"; break; default: label = "high"; }

Exercise 4: Debug

#

This method should return the sum of the numbers 1 to n, but it returns the wrong value. Find the bug. static int sumTo(int n) { int total = 0; for (int i = 1; i < n; i++) { total += i; } return total; }

Exercise 5: Reason about pass by value

#

A method static void swap(int a, int b) swaps its two parameters. After calling swap(x, y) the caller's x and y are unchanged. Explain why, and show how to achieve a swap of two values in an int[] array.

Show solutionHide solution

It prints 7. The loop visits i = 0, 1, 2, 3, 4; for i == 3, continue skips the addition. The total is 0 + 1 + 2 + 4 = 7.

Exercise 2

Show solutionHide solution
Java
static boolean isLeapYear(int year) {    return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;}

Results: 1900 is not a leap year (divisible by 100 but not by 400), 2000 is (divisible by 400), 2024 is (divisible by 4, not by 100), and 2026 is not. The expression's parentheses matter: && binds more tightly than ||, but the parentheses make the intent obvious. Test the boundary cases (centuries) as well as the ordinary ones.

Exercise 3

Show solutionHide solution
Java
String label = switch (level) {    case 1 -> "low";    case 2, 3 -> "medium";    default -> "high";};

The risk that disappears is fall-through: there is no break to forget, because each arrow branch is independent. The result is also assigned in one place, so it is impossible to forget to assign label on some path, and the compiler checks that every case is covered.

Exercise 4

Show solutionHide solution

The loop condition i < n stops before n, so n itself is never added. It should be i <= n. For n = 5 the method returns 10 (1 + 2 + 3 + 4) instead of 15. This is a classic off-by-one error. Check loops with a small input by hand, such as n = 1, which returns 0 with the buggy version and should return 1.

Exercise 5

Show solutionHide solution

a and b inside swap are copies of the caller's values. Swapping the copies leaves the originals untouched. To swap values in a way the caller can see, pass something the method can modify through a reference, such as an array:

Java
static void swap(int[] values, int i, int j) {    int temp = values[i];    values[i] = values[j];    values[j] = temp;}

The method receives a copy of the reference to the array, but both the caller's reference and the copy point to the same array, so the change is visible. A common alternative is to return a new pair of values, or to avoid mutation altogether.

Where to go next

#

Sources & further reading

  1. openjdk.org
  2. docs.oracle.com
  • Guide · Technology

    Java Essentials for DSA

  • Guide · Technology

    SQL for Beginners: Learn to Query a Database With Real Examples

  • Guide · Career

    A LinkedIn Profile That Gets Recruiter Messages