AlgoInsight

Free lesson · Java Basic To Advance

If Else

Easy7 min readjava

Branching the Path of Execution

Module 2 covered the values your program works with. Module 3 covers control flow — how the program decides which code to run based on those values. Every non-trivial program has decisions: "if the user is logged in, show the dashboard; otherwise, show the login page." Java has several mechanisms for expressing decisions, and this Topic covers the most fundamental one: if and else.

This looks simple. It mostly is. But the subtleties — what counts as a boolean condition, when to use else if versus separate if statements, when to prefer the ternary operator versus a full if-else block — distinguish junior from senior Java code. We'll cover all of them.

I'm Olena, leading Module 3. Control flow is one of those topics where the syntax is easy but the style takes years to develop. We'll touch on both throughout this module.

The Basic if Statement

The simplest form:

if (condition) {
    // runs only if condition is true
}

The condition must be a boolean expression — something that evaluates to true or false. This is different from Python or JavaScript where truthy values count. In Java, if (5) doesn't compile. Neither does if (someObject). The condition has to be an actual boolean.

int score = 85;

if (score >= 60) {
    System.out.println("Passed");
}

// Compile errors — Java doesn't do truthy/falsy
// if (score) { ... }              // error: int is not boolean
// if (someObject) { ... }         // error: Object is not boolean
// if (someList) { ... }           // error: List is not boolean

This strictness prevents a common class of bugs. In JavaScript, if (count) is false when count is 0, which is sometimes intended and sometimes a bug. In Java, you have to write if (count > 0) or if (count != 0) explicitly. The intent is always clear.

For null checks, you write the comparison explicitly:

if (user != null) {
    System.out.println(user.getName());
}

This pattern — null check before method call — is one of the most common idioms in Java. It prevents NullPointerException, which we covered in Topic 2.5 as Java's most common runtime error.

The if-else Statement

When you want one path on true and another on false:

if (score >= 60) {
    System.out.println("Passed");
} else {
    System.out.println("Failed");
}

Exactly one of the two blocks runs. Never both, never neither.

The braces around each block are optional when the block contains a single statement:

// Legal but discouraged
if (score >= 60)
    System.out.println("Passed");
else
    System.out.println("Failed");

Don't do this. Brace-less if statements are a source of bugs. Adding a second line later silently breaks the logic:

if (score >= 60)
    System.out.println("Passed");
    awardCertificate();   // ⚠️ this runs UNCONDITIONALLY — the if doesn't include it

Almost every Java style guide — Google, Oracle, Twitter — mandates braces always, even for single-statement blocks. The maintenance cost of always writing braces is zero. The cost of the bug pattern above is real. Always braces.

Interactive animation: Without braces the if owns only its first line, so awardCertificate() runs for everyone - braces make it explicit.

The if-else if-else Chain

For more than two paths:

if (score >= 90) {
    grade = "A";
} else if (score >= 80) {
    grade = "B";
} else if (score >= 70) {
    grade = "C";
} else if (score >= 60) {
    grade = "D";
} else {
    grade = "F";
}

The chain evaluates top to bottom. The first matching condition runs its block, and the rest are skipped. The final else catches anything that didn't match — it's optional but often included as a safety net.

The order of conditions matters when ranges overlap. The above works because each else if implicitly excludes the previous conditions. If you wrote them as separate if statements:

// ⚠️ Wrong — every condition is checked, last one wins
if (score >= 90) grade = "A";
if (score >= 80) grade = "B";   // overwrites the A
if (score >= 70) grade = "C";   // overwrites the B
// ... etc.

With a score of 95, all four conditions are true and grade ends up as "D" — clearly wrong. The else if chain prevents this.

For more than 3-4 branches comparing against constant values, consider a switch statement (Topic 3.2) or switch expression (Module 25). Long if-else chains comparing the same variable against constants are usually clearer as a switch.

Interactive animation: Four separate ifs grade a score of 95 as D - an else if chain stops at the first match.

The Ternary Operator

For simple conditional assignments, the ternary operator is more concise (covered in Topic 2.5):

String result = (score >= 60) ? "Passed" : "Failed";

// Equivalent to:
String result;
if (score >= 60) {
    result = "Passed";
} else {
    result = "Failed";
}

The ternary works when:

Don't nest ternaries — they become unreadable fast:

// Hard to read
String grade = score >= 90 ? "A" : score >= 80 ? "B" : score >= 70 ? "C" : "F";

// Better — use if-else chain or switch

Nested if Statements

You can put if statements inside if statements:

if (user != null) {
    if (user.isActive()) {
        if (user.hasPermission("admin")) {
            // do admin thing
        }
    }
}

This works but gets ugly quickly. Three levels of nesting is roughly the limit before code becomes hard to follow. Common refactorings:

Combine conditions with &&:

if (user != null && user.isActive() && user.hasPermission("admin")) {
    // do admin thing
}

Short-circuit evaluation (covered in Topic 2.5) makes this safe — user.isActive() only runs if user != null.

Use early returns to flatten nesting:

public void doAdminThing(User user) {
    if (user == null) return;
    if (!user.isActive()) return;
    if (!user.hasPermission("admin")) return;

    // do admin thing
}

This is sometimes called "guard clauses." Each failure case exits early. The happy path is at the bottom, unindented. Many senior engineers prefer this style because the success case is unambiguous — you don't have to track which combination of conditions led to the current code path.

graph TD
    A[Start] --> B{score >= 60?}
    B -->|true| C[Print Passed]
    B -->|false| D[Print Failed]
    C --> E[End]
    D --> E

    style B fill:#7c3aed,color:#fff
    style C fill:#10b981,color:#fff
    style D fill:#dc2626,color:#fff
This diagram shows the flow of a basic if-else statement. Execution starts at the top, reaches the condition check (in this case, whether score is at least 60). If the condition is true, the program follows the green path and prints "Passed". If false, it follows the red path and prints "Failed". Either way, execution continues at the same point after the if-else block. Exactly one branch runs — never both, never neither.

Interactive animation: Nested ifs against guard clauses - the same checks, with the actual work left unindented at the bottom.

The "Dangling Else" Problem

A subtle parsing question: which if does an else belong to?

if (a)
    if (b)
        doSomething();
    else
        doSomethingElse();

The else binds to the nearest if, which is the if (b). So this is:

if (a) {
    if (b) {
        doSomething();
    } else {
        doSomethingElse();
    }
}

If you wanted the else to bind to if (a), you need explicit braces:

if (a) {
    if (b) doSomething();
} else {
    doSomethingElse();
}

This is the classic "dangling else" problem, present in C, C++, Java, and most C-family languages. The fix: always use braces. The bug doesn't appear when you write braces because they make the structure explicit.

Pattern Matching for Conditions (Java 16+)

Modern Java has pattern matching for instanceof, which lets you combine type checking and casting in a single condition:

Object obj = getValue();

if (obj instanceof String s) {
    System.out.println(s.length());
}

if (obj instanceof Integer i && i > 100) {
    System.out.println("Big number");
}

We covered this in Topic 2.4 (casting). It's worth re-emphasizing here because it's one of the most common modern uses of if. The pattern obj instanceof Type variable checks the type and binds the variable in one step.

Combined with the switch pattern matching (Module 25), this lets Java handle the kinds of structural cases that you'd need separate instanceof chains for in older code.

graph LR
    A[Conditional Logic] --> B[Single condition<br/>if/else]
    A --> C[Multiple conditions<br/>if/else if/else]
    A --> D[Many discrete values<br/>switch statement]
    A --> E[Simple ternary expression<br/>?:]
    A --> F[Type-based dispatch<br/>instanceof pattern]

    style B fill:#10b981,color:#fff
    style C fill:#3b82f6,color:#fff
    style D fill:#7c3aed,color:#fff
    style E fill:#f59e0b,color:#fff
    style F fill:#06b6d4,color:#fff
This diagram organizes the ways Java handles conditional logic. For a single condition with two outcomes, use if-else. For multiple related conditions, use an if-else-if chain. For many discrete values of one variable, use a switch statement, covered in Topic 3.2. For simple conditional assignments, the ternary operator is concise. For type-based dispatch — different behavior based on an object's actual type — modern Java offers pattern matching with instanceof. Each form has its niche. Knowing when to reach for which makes for cleaner code.

Interactive animation: The old instanceof-then-cast beside Java 16 pattern matching - check the type and bind the variable in one step.

Common Pitfalls

Warning: Using = instead of ==. if (x = 5) is assignment, not comparison. It only compiles if x is boolean (and then assigns 5, which doesn't make sense). For non-boolean types, Java's compiler catches this. For booleans, the compiler doesn't — if (flag = true) is legal Java and assigns true to flag. This is one reason to prefer if (flag) over if (flag == true).

Omitting braces. if (x) doA(); is legal but bug-prone. Always use braces, even for single-statement blocks.

Comparing booleans with ==. if (isActive == true) is verbose and unnecessary. Write if (isActive). For the negation, if (!isActive) is cleaner than if (isActive == false).

Forgetting the dangling else binds to the nearest if. Without braces, else always matches the closest if. Use braces to be explicit.

Nesting too deeply. More than 3 levels of nested if statements signals that the code needs refactoring. Use early returns, combined conditions, or extract methods.

Why This Matters in Interviews

Interview: If-else questions are rare as standalone interview questions — they're too basic. But the style of your if-else usage is constantly observed. Senior code uses guard clauses, avoids deep nesting, prefers braces, combines conditions sensibly. Junior code has 5-level-deep nesting and missing braces.

Topic 3.9 covers the full Interview Lens for control flow questions, with specific output-prediction puzzles and refactoring scenarios.

Try It

Exercise: Refactor this deeply-nested code using guard clauses:

public String processOrder(Order order) {
    if (order != null) {
        if (order.isValid()) {
            if (order.getCustomer() != null) {
                if (order.getCustomer().isActive()) {
                    return "Processing order " + order.getId();
                } else {
                    return "Customer inactive";
                }
            } else {
                return "No customer";
            }
        } else {
            return "Invalid order";
        }
    } else {
        return "Null order";
    }
}

The refactored version should have no nesting beyond one level. Compare the two — which is easier to read? Which is easier to add a new condition to?

Back to Java Basic To Advance