Control Flow

If-Else Statements

Make decisions in C++ with if, else if, else, nested conditions, logical operators, constexpr if, and C++17 if-with-initialiser.

If-Else Statements

Decision-making is at the heart of every program. C++ if/else statements evaluate a condition and choose a branch of code to execute. Mastering conditionals — including edge cases like dangling else and short-circuit evaluation — is essential before moving to loops and functions.


Basic if Statement

cpp
#include <iostream>
using namespace std;

int main() {
    int temperature = 35;

    if (temperature > 30) {
        cout << "It's hot outside.\n";
    }

    // Without braces — only the NEXT statement is in the if block
    // Avoid this style: it's error-prone
    if (temperature > 0)
        cout << "Above freezing.\n";

    return 0;
}
// Output:
// It's hot outside.
// Above freezing.

Note: Always use curly braces {} even for single-statement if blocks. The "dangling else" bug and accidental multi-statement conditions are common pitfalls when braces are omitted.


if-else

cpp
#include <iostream>
using namespace std;

int main() {
    int score = 72;

    if (score >= 90) {
        cout << "Excellent!\n";
    } else {
        cout << "Keep practising.\n";
    }
    // Output: Keep practising.

    // Boolean expression
    bool isLoggedIn = true;
    if (isLoggedIn) {
        cout << "Welcome back!\n";
    } else {
        cout << "Please log in.\n";
    }

    return 0;
}

if-else if-else Chain

cpp
#include <iostream>
using namespace std;

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

int main() {
    cout << getGrade(95) << "\n";   // A
    cout << getGrade(83) << "\n";   // B
    cout << getGrade(71) << "\n";   // C
    cout << getGrade(55) << "\n";   // F

    return 0;
}

Only the first matching branch executes — once found, the rest of the chain is skipped.


Nested if Statements

cpp
#include <iostream>
using namespace std;

int main() {
    int age = 20;
    bool hasID = true;
    bool isMember = false;

    if (age >= 18) {
        if (hasID) {
            if (isMember) {
                cout << "Member discount applied.\n";
            } else {
                cout << "Full price — consider membership!\n";
            }
        } else {
            cout << "ID required for entry.\n";
        }
    } else {
        cout << "Under 18 — access denied.\n";
    }
    // Output: Full price — consider membership!

    return 0;
}

Note: Deeply nested conditions are hard to read. Use logical operators (&&, ||) or early returns to flatten the nesting.


Combining Conditions with Logical Operators

cpp
#include <iostream>
using namespace std;

int main() {
    int age = 20;
    bool hasTicket = true;

    // AND — both conditions must be true
    if (age >= 18 && hasTicket) {
        cout << "Entry granted.\n";
    }

    // OR — at least one condition must be true
    bool isAdmin  = false;
    bool isOwner  = true;
    if (isAdmin || isOwner) {
        cout << "Access to settings.\n";
    }

    // NOT — invert a condition
    bool isWeekend = false;
    if (!isWeekend) {
        cout << "Working day.\n";
    }

    // Complex condition — use parentheses for clarity
    int score = 85;
    string role = "student";
    if ((score >= 80 && role == "student") || role == "teacher") {
        cout << "Eligible for award.\n";
    }

    return 0;
}

if with Initialiser (C++17)

C++17 allows an initialisation statement inside the if condition — the variable is scoped to the if-else block:

cpp
#include <iostream>
#include <map>
using namespace std;

int main() {
    map<string, int> scores = {{"Alice", 92}, {"Bob", 78}};

    // C++17: initialise and test in one line
    if (auto it = scores.find("Alice"); it != scores.end()) {
        cout << "Alice's score: " << it->second << "\n";  // 92
    }
    // 'it' is not accessible here — scoped to the if block

    if (int result = scores.count("Charlie"); result == 0) {
        cout << "Charlie not found.\n";
    }

    return 0;
}

constexpr if (C++17)

if constexpr is evaluated at compile time — the other branch is not compiled at all:

cpp
#include <iostream>
#include <type_traits>
using namespace std;

template <typename T>
void printType(T value) {
    if constexpr (is_integral_v<T>) {
        cout << "Integer: " << value << "\n";
    } else if constexpr (is_floating_point_v<T>) {
        cout << "Float: " << value << "\n";
    } else {
        cout << "Other type\n";
    }
}

int main() {
    printType(42);      // Integer: 42
    printType(3.14);    // Float: 3.14
    printType("hello"); // Other type
    return 0;
}

This is essential in template programming — it lets you write type-specific logic without specialisation.


Common Mistakes

cpp
// 1. Assignment instead of comparison (= vs ==)
int x = 5;
if (x = 10) {    // Always true — assigns 10 to x!
    cout << "This always runs!\n";
}
// Fix: if (x == 10)

// 2. Dangling else — which 'if' does the 'else' belong to?
if (x > 0)
    if (x > 10)
        cout << "Large positive\n";
else              // This belongs to the INNER if!
    cout << "Confusing!\n";
// Fix: Always use braces

// 3. Floating-point equality
double a = 0.1 + 0.2;
if (a == 0.3) {        // May be false due to float precision!
    cout << "Equal\n";
}
// Fix: compare with an epsilon
const double EPS = 1e-9;
if (abs(a - 0.3) < EPS) {
    cout << "Equal (within epsilon)\n";
}

Practical Example: Loan Eligibility Checker

cpp
#include <iostream>
#include <string>
using namespace std;

struct Applicant {
    string name;
    int    age;
    double annualIncome;
    int    creditScore;
    bool   hasExistingDebt;
};

string checkLoanEligibility(const Applicant& a) {
    // Age requirement
    if (a.age < 21 || a.age > 70) {
        return "REJECTED: Age must be between 21 and 70.";
    }

    // Income requirement
    if (a.annualIncome < 300000) {
        return "REJECTED: Minimum annual income is ₹3,00,000.";
    }

    // Credit score
    if (a.creditScore < 600) {
        return "REJECTED: Credit score below minimum (600).";
    }

    // Existing debt check
    if (a.hasExistingDebt && a.creditScore < 750) {
        return "REJECTED: Existing debt requires credit score ≥ 750.";
    }

    // Determine loan tier
    if (a.creditScore >= 800 && a.annualIncome >= 1000000) {
        return "APPROVED: Premium tier — up to ₹50,00,000 at 8.5%";
    } else if (a.creditScore >= 700) {
        return "APPROVED: Standard tier — up to ₹20,00,000 at 10.5%";
    } else {
        return "APPROVED: Basic tier — up to ₹5,00,000 at 13%";
    }
}

int main() {
    Applicant applicants[] = {
        {"Alice",   28, 1500000, 810, false},
        {"Bob",     22, 250000,  700, false},
        {"Charlie", 45, 800000,  680, true},
        {"Diana",   35, 600000,  750, true},
    };

    for (const auto& a : applicants) {
        cout << a.name << ": " << checkLoanEligibility(a) << "\n";
    }
    // Alice:   APPROVED: Premium tier — up to ₹50,00,000 at 8.5%
    // Bob:     REJECTED: Minimum annual income is ₹3,00,000.
    // Charlie: REJECTED: Existing debt requires credit score ≥ 750.
    // Diana:   APPROVED: Standard tier — up to ₹20,00,000 at 10.5%

    return 0;
}

Summary

  • if/else if/else — evaluates conditions top to bottom, executes the first matching branch
  • Always use curly braces — they prevent the dangling-else bug and accidental scope errors
  • Combine conditions with && (AND), || (OR), ! (NOT) — short-circuit evaluation stops early
  • Never use = when you mean == in conditions — the compiler may warn but it compiles
  • Never compare floats with == — use an epsilon comparison instead
  • C++17 if with initialiser scopes a variable to the if-else block — cleaner than declaring it before
  • if constexpr evaluates at compile time — the dead branch is eliminated, enabling efficient template code

Next, we'll look at the switch statement — a cleaner way to branch on discrete integer or enum values.