Skip to content

Go Conditionals: If-Else and Switch

Overview

This guide covers if-else statements, switch statements, and advanced conditional patterns, building upon our understanding of loops and data types.

Key Points

  • Clean and readable conditional syntax
  • Powerful switch statements with multiple patterns
  • Short variable declarations in conditions
  • Type switches for interface handling
  • Fallthrough control in switch cases

Conditional Logic

Conditional statements are the decision-making backbone of any program. Go provides constructs for handling different execution paths.

Conditional Constructs in Go

graph TD
    A[Go Conditionals] --> B[If-Else]
    A --> C[Switch]
    B --> B1[Basic If]
    B --> B2[If-Else]
    B --> B3[If-Else If]
    B --> B4[Short Declaration]
    C --> C1[Expression Switch]
    C --> C2[Type Switch]
    C --> C3[No Expression]
    C --> C4[Fallthrough]
    style A fill:#555,stroke:#333,stroke-width:2px,color:#000

If-Else Statements

Remember

In golang, the if else expression is not passed to a AST tree with open brackets ((, )) like we use in C, C++, Java, Python, etc.

The if-else family provides straightforward conditional execution based on boolean expressions.

Basic If Statement

Simple If Statements

basic_if.go
package main

import "fmt"

func main() {
    temperature := 25

    if temperature > 20 {
        fmt.Println("It's a warm day!")
    }

    // Output: It's a warm day!
}

Boolean Conditions

The condition must evaluate to a boolean value (true or false).

multiple_conditions.go
package main

import "fmt"

func main() {
    age := 25
    hasLicense := true

    if age >= 18 && hasLicense {
        fmt.Println("You can drive!")
    }

    // Output: You can drive!
}
comparisons.go
package main

import "fmt"

func main() {
    score := 85

    if score >= 90 {
        fmt.Println("Grade: A")
    }

    if score >= 80 && score < 90 {
        fmt.Println("Grade: B")
    }

    if score < 60 {
        fmt.Println("Grade: F")
    }

    // Output: Grade: B
}

If-Else Statement

If-Else Patterns

if_else.go
package main

import "fmt"

func main() {
    number := 7

    if number%2 == 0 {
        fmt.Printf("%d is even\n", number)
    } else {
        fmt.Printf("%d is odd\n", number)
    }

    // Output: 7 is odd
}
if_else_if.go
package main

import "fmt"

func main() {
    grade := 85

    if grade >= 90 {
        fmt.Println("Excellent!")
    } else if grade >= 80 {
        fmt.Println("Good job!")
    } else if grade >= 70 {
        fmt.Println("Not bad!")
    } else {
        fmt.Println("Keep trying!")
    }

    // Output: Good job!
}
complex_conditions.go
package main

import "fmt"

func main() {
    age := 25
    income := 50000
    creditScore := 720

    if age >= 18 && income >= 30000 && creditScore >= 650 {
        fmt.Println("Loan approved!")
    } else if age < 18 {
        fmt.Println("Must be 18 or older")
    } else if income < 30000 {
        fmt.Println("Insufficient income")
    } else {
        fmt.Println("Credit score too low")
    }

    // Output: Loan approved!
}

Short Variable Declaration in If

Go allows you to declare and initialize variables within if statements, creating a limited scope.

Short Declaration in If

short_declaration.go
package main

import "fmt"

func main() {
    // Variable declared in if statement
    if x := 10; x > 5 {
        fmt.Printf("x is %d and greater than 5\n", x)
    }
    // x is not accessible here, x have a limited functional scope

    // Output: x is 10 and greater than 5
}

Scope Limitation

Variables declared in if statements are only accessible within the if-else block.

error_handling.go
package main

import (
    "fmt"
    "strconv"
)

func main() {
    input := "123"

    if num, err := strconv.Atoi(input); err != nil {
        fmt.Printf("Error converting '%s': %v\n", input, err)
    } else {
        fmt.Printf("Successfully converted to %d\n", num)
    }

    // Output: Successfully converted to 123
}

Scope Limitation

The error handling pattern of go will be understood in a future write-up, now just understand the part of the code that it returns some errors, if the statement is not validated.

strconv.Atoi attempts to parse the string as a base-10 integer. The call returns two values: the integer (num) and an error (err). The if checks the error, and If err != nil the conversion failed, so the first branch runs and prints an error message. If err == nil the conversion succeeded, so the else branch runs and prints the integer value. The value passed out of Atoi() will be stored to num. Also, the num value is available inside if/else conditional scope.

function_call.go
package main

import "fmt"

func getTemperature() int {
    return 25
}

func main() {
    // Call function and use result in condition
    if temp := getTemperature(); temp > 20 {
        fmt.Printf("Temperature is %d°C - Nice weather!\n", temp)
    } else {
        fmt.Printf("Temperature is %d°C - A bit cold\n", temp)
    }

    // Output: Temperature is 25°C - Nice weather!
}

Switch Statements

Switch statements provide a way to handle multiple conditions and are more readable than long if-else chains.

Expression Switch

Basic Switch Statements

simple_switch.go
package main

import "fmt"

func main() {
    day := "Monday"

    switch day {
        case "Monday":
            fmt.Println("Start of the work week")
        case "Friday":
            fmt.Println("TGIF!")
        case "Saturday", "Sunday":
            fmt.Println("Weekend!")
        default:
            fmt.Println("Regular weekday")
    }

    // Output: Start of the work week
}
switch_expression.go
package main

import "fmt"

func main() {
    score := 85

    switch {
        case score >= 90:
            fmt.Println("Grade: A")
        case score >= 80:
            fmt.Println("Grade: B")
        case score >= 70:
            fmt.Println("Grade: C")
        case score >= 60:
            fmt.Println("Grade: D")
        default:
            fmt.Println("Grade: F")
    }

    // Output: Grade: B
}
switch_short_declaration.go
package main

import (
    "fmt"
    "time"
)

func main() {
    switch hour := time.Now().Hour(); {
        case hour < 12:
            fmt.Println("Good morning!")
        case hour < 17:
            fmt.Println("Good afternoon!")
        case hour < 21:
            fmt.Println("Good evening!")
        default:
            fmt.Println("Good night!")
    }
}

Advanced Switch Patterns

Advanced Switch Usage

multiple_values.go
package main

import "fmt"

func main() {
    char := 'a'

    switch char {
        case 'a', 'e', 'i', 'o', 'u':
            fmt.Printf("'%c' is a vowel\n", char)
        case 'y':
            fmt.Printf("'%c' is sometimes a vowel\n", char)
        default:
            fmt.Printf("'%c' is a consonant\n", char)
    }

    // Output: 'a' is a vowel
}
fallthrough.go
package main

import "fmt"

func main() {
    number := 2

    switch number {
        case 1:
            fmt.Println("One")
            fallthrough
        case 2:
            fmt.Println("Two or after One")
            fallthrough
        case 3:
            fmt.Println("Three or after Two")
        default:
            fmt.Println("Other number")
    }

    // Output:
    // Two or after One
    // Three or after Two
}
function_results.go
package main

import "fmt"

func classify(n int) string {
    if n%2 == 0 {
        return "even"
    }
    return "odd"
}

func main() {
    number := 7

    switch classify(number) {
        case "even":
            fmt.Printf("%d is an even number\n", number)
        case "odd":
            fmt.Printf("%d is an odd number\n", number)
        default:
            fmt.Printf("Unknown classification for %d\n", number)
    }

    // Output: 7 is an odd number
}

Breaking out of a Switch (Labels)

Sometimes you need to break out of a loop from within a switch.

Break out of Switch

switch_break.go
func main() {
Loop:
    for i := 0; i < 5; i++ {
        switch i {
        case 2:
            fmt.Println("Breaking the whole loop at 2")
            break Loop // Breaks the 'for' loop, not just the switch
        default:
            fmt.Println(i)
        }
    }
}

Type Switch

Type switches are a great feature for working with interfaces and determining the actual type of a value.

Type Switch Patterns

type_switch.go
package main

import "fmt"

func describe(i interface{}) {
    fmt.Printf("Value: %v, ", i)

    switch v := i.(type) {
    case int:
        fmt.Printf("Type: int, Double: %d\n", v*2)
    case string:
        fmt.Printf("Type: string, Length: %d\n", len(v))
    case bool:
        fmt.Printf("Type: bool, Negated: %t\n", !v)
    default:
        fmt.Printf("Type: %T (unknown)\n", v)
    }
}

func main() {
    describe(42)
    describe("hello")
    describe(true)
    describe(3.14)

    // Output:
    // Value: 42, Type: int, Double: 84
    // Value: hello, Type: string, Length: 5
    // Value: true, Type: bool, Negated: false
    // Value: 3.14, Type: float64 (unknown)
}
multiple_types.go
package main

import "fmt"

func processValue(i interface{}) {
    fmt.Printf("Processing: %v -> ", i)

    switch v := i.(type) {
    case int, int32, int64:
        fmt.Printf("Integer type: %T\n", v)
    case float32, float64:
        fmt.Printf("Float type: %T\n", v)
    case string:
        fmt.Printf("String with %d characters\n", len(v))
    case []int:
        fmt.Printf("Integer slice with %d elements\n", len(v))
    case nil:
        fmt.Println("Nil value")
    default:
        fmt.Printf("Unknown type: %T\n", v)
    }
}

func main() {
    processValue(42)
    processValue(3.14)
    processValue("Go")
    processValue([]int{1, 2, 3})
    processValue(nil)
}

Advanced Conditional Patterns

Conditional Expressions and Operators

Logical Operators and Complex Conditions

logical_operators.go
package main

import "fmt"

func main() {
    age := 25
    hasJob := true
    creditScore := 750

    // AND operator
    if age >= 18 && hasJob && creditScore >= 700 {
        fmt.Println("Eligible for premium loan")
    }

    // OR operator
    if age < 18 || creditScore < 600 {
        fmt.Println("Not eligible for loan")
    } else {
        fmt.Println("Basic loan eligibility met")
    }

    // NOT operator
    if !(age < 18) {
        fmt.Println("Adult")
    }

    // Output:
    // Eligible for premium loan
    // Basic loan eligibility met
    // Adult
}
short_circuit.go
package main

import "fmt"

func expensiveCheck() bool {
    fmt.Println("Expensive check called")
    return true
}

func main() {
    condition := false

    // expensiveCheck() won't be called due to short-circuit
    if condition && expensiveCheck() {
        fmt.Println("Both conditions true")
    }

    // expensiveCheck() will be called
    if !condition || expensiveCheck() {
        fmt.Println("At least one condition true")
    }

    // Output:
    // Expensive check called
    // At least one condition true
}

Nested Conditionals

Nested Conditional Patterns

nested_conditionals.go
package main

import "fmt"

func main() {
    weather := "sunny"
    temperature := 25

    if weather == "sunny" {
        if temperature > 20 {
            fmt.Println("Perfect day for outdoor activities!")
        } else {
            fmt.Println("Sunny but a bit cold")
        }
    } else if weather == "rainy" {
        if temperature > 15 {
            fmt.Println("Warm rain - good for plants")
        } else {
            fmt.Println("Cold and rainy - stay inside")
        }
    } else {
        fmt.Println("Weather conditions unclear")
    }

    // Output: Perfect day for outdoor activities!
}
guard_clauses.go
package main

import "fmt"

func processUser(name string, age int, email string) {
    // Guard clauses for early returns
    if name == "" {
        fmt.Println("Error: Name cannot be empty")
        return
    }

    if age < 0 || age > 150 {
        fmt.Println("Error: Invalid age")
        return
    }

    if email == "" {
        fmt.Println("Error: Email cannot be empty")
        return
    }

    // Main processing logic
    fmt.Printf("Processing user: %s, age %d, email %s\n", name, age, email)
}

func main() {
    processUser("Alice", 25, "alice@example.com")
    processUser("", 30, "bob@example.com")
    processUser("Charlie", -5, "charlie@example.com")
}

Best Practices

Conditional Best Practices

  1. Prefer Switch Over Long If-Else Chains

    // Good: Use switch for multiple discrete values
    switch status {
    case "pending", "processing":
        handleInProgress()
    case "completed":
        handleCompleted()
    case "failed":
        handleFailed()
    }
    
    // Avoid: Long if-else chains
    if status == "pending" || status == "processing" {
        handleInProgress()
    } else if status == "completed" {
        handleCompleted()
    } else if status == "failed" {
        handleFailed()
    }
    

  2. Use Short Variable Declaration for Error Handling

    1
    2
    3
    4
    5
    if result, err := someOperation(); err != nil {
        return err
    } else {
        return result
    }
    

  3. Leverage Type Switches for Interface Handling

    1
    2
    3
    4
    5
    6
    7
    8
    switch v := value.(type) {
    case string:
        return processString(v)
    case int:
        return processInt(v)
    default:
        return processUnknown(v)
    }
    

  4. Use Guard Clauses for Early Returns

    func validate(input string) error {
        if input == "" {
            return errors.New("input cannot be empty")
        }
        if len(input) > 100 {
            return errors.New("input too long")
        }
        // Continue with main logic
        return nil
    }
    

Common Pitfalls

  • Missing Default Cases: Always consider adding default cases in switch statements
  • Complex Conditions: Break down complex boolean expressions for readability
  • Deep Nesting: Avoid deeply nested conditionals; use guard clauses instead
  • Fallthrough Confusion: Be explicit about fallthrough behavior in switch statements

Performance Considerations

Optimization Tips

performance_comparison.go
package main

import "fmt"

func processWithSwitch(value int) string {
    // Generally faster for many discrete values
    switch value {
    case 1, 2, 3:
        return "low"
    case 4, 5, 6:
        return "medium"
    case 7, 8, 9:
        return "high"
    default:
        return "unknown"
    }
}

func processWithIfElse(value int) string {
    // Better for range conditions
    if value >= 1 && value <= 3 {
        return "low"
    } else if value >= 4 && value <= 6 {
        return "medium"
    } else if value >= 7 && value <= 9 {
        return "high"
    } else {
        return "unknown"
    }
}

func main() {
    fmt.Println(processWithSwitch(5))
    fmt.Println(processWithIfElse(5))
}
condition_ordering.go
package main

import "fmt"

func checkConditions(value int) string {
    // Order conditions by likelihood (most common first)
    if value >= 0 && value <= 100 {  // Most common case
        return "normal range"
    } else if value < 0 {            // Less common
        return "negative"
    } else {                         // Least common
        return "above range"
    }
}

func main() {
    fmt.Println(checkConditions(50))
}

Quick Reference

Key Takeaways

  1. If-Else: Use for boolean conditions and ranges
  2. Switch: Prefer for discrete values and type checking
  3. Short Declaration: Leverage for scoped variables in conditions
  4. Type Switch: Essential for interface type determination
  5. Guard Clauses: Use for early returns and validation
  6. Performance: Consider condition ordering and switch vs if-else trade-offs

Remember

"Choose the right conditional construct for the job. Switch for discrete values, if-else for ranges and complex boolean logic, and type switches for interface handling."