Go Cheatsheet

Control Flow

Use this Go reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.

if / else

No parentheses around the condition; braces are mandatory.

if x > 0 {
    fmt.Println("positive")
} else if x < 0 {
    fmt.Println("negative")
} else {
    fmt.Println("zero")
}

Initialization statement

A short statement before the condition; the variable is scoped to the if/else block.

if v, ok := m[key]; ok {
    fmt.Println("found:", v)
} else {
    fmt.Println("not found")
}

if err := doSomething(); err != nil {
    return err
}

for — the only loop

Go has one loop keyword: for. It covers all looping patterns.

C-style

for i := 0; i < 10; i++ {
    fmt.Println(i)
}

While-style

n := 1
for n < 100 {
    n *= 2
}

Infinite loop

for {
    // break to exit
}

Range loop

Iterates over arrays, slices, maps, strings, and channels.

nums := []int{10, 20, 30}

for i, v := range nums {      // index + value
    fmt.Println(i, v)
}

for i := range nums {          // index only
}

for _, v := range nums {       // value only
}

// string: iterates over runes
for i, r := range "héllo" {
    fmt.Printf("%d %c\n", i, r)
}

// map
m := map[string]int{"a": 1, "b": 2}
for k, v := range m {
    fmt.Println(k, v)
}

// channel — reads until closed
for msg := range ch {
    fmt.Println(msg)
}

break and continue

for i := 0; i < 10; i++ {
    if i == 3 {
        continue  // skip to next iteration
    }
    if i == 7 {
        break     // exit loop
    }
    fmt.Println(i)
}

Labeled break / continue

outer:
for i := 0; i < 3; i++ {
    for j := 0; j < 3; j++ {
        if i+j == 3 {
            break outer  // exits the outer loop
        }
    }
}

switch

No fall-through by default; no break needed. Cases can hold multiple values.

switch x {
case 1:
    fmt.Println("one")
case 2, 3:
    fmt.Println("two or three")
default:
    fmt.Println("other")
}

Initializer statement

switch os := runtime.GOOS; os {
case "linux":
    fmt.Println("Linux")
case "darwin":
    fmt.Println("macOS")
}

Expression-less switch (replaces if-else chains)

switch {
case x < 0:
    fmt.Println("negative")
case x == 0:
    fmt.Println("zero")
default:
    fmt.Println("positive")
}

Type switch

Used to branch on an interface's dynamic type.

func describe(i interface{}) string {
    switch v := i.(type) {
    case int:
        return fmt.Sprintf("int: %d", v)
    case string:
        return fmt.Sprintf("string: %s", v)
    case bool:
        return fmt.Sprintf("bool: %t", v)
    default:
        return fmt.Sprintf("unknown: %T", v)
    }
}

fallthrough

Explicitly falls through to the next case (transfers control, not condition re-evaluated).

switch x {
case 1:
    fmt.Println("one")
    fallthrough
case 2:
    fmt.Println("two or continued from one")
}

goto

Jumps to a labeled statement within the same function. Rarely used; cannot jump over variable declarations.

i := 0
loop:
if i < 5 {
    fmt.Println(i)
    i++
    goto loop
}

defer

A deferred call runs when the surrounding function returns (LIFO order). Arguments are evaluated immediately.

func readFile(path string) error {
    f, err := os.Open(path)
    if err != nil {
        return err
    }
    defer f.Close()  // runs when readFile returns

    // use f...
    return nil
}

Multiple defers (LIFO)

func demo() {
    defer fmt.Println("third")
    defer fmt.Println("second")
    defer fmt.Println("first")
}
// prints: first, second, third

Defer with a closure (captures variables by reference)

func double(x int) (result int) {
    defer func() {
        result *= 2  // modifies named return value
    }()
    result = x
    return
}

panic and recover

panic unwinds the call stack; recover can catch it inside a deferred function.

func safeDiv(a, b int) (result int, err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("recovered: %v", r)
        }
    }()
    result = a / b  // panics if b == 0
    return
}

// panic with a value
panic("something went wrong")
panic(errors.New("bad state"))

Use panic/recover only for truly exceptional situations; prefer returning error for normal error handling.

select

Waits on multiple channel operations; picks one that is ready (random if multiple are ready).

select {
case msg := <-ch1:
    fmt.Println("ch1:", msg)
case msg := <-ch2:
    fmt.Println("ch2:", msg)
case ch3 <- "value":
    fmt.Println("sent to ch3")
default:
    fmt.Println("no channel ready")  // non-blocking
}

Timeout pattern

select {
case result := <-work:
    fmt.Println(result)
case <-time.After(2 * time.Second):
    fmt.Println("timed out")
}

Loop until done

for {
    select {
    case v := <-data:
        process(v)
    case <-done:
        return
    }
}