Go Basics

Program Structure

Minimal executable — every Go program starts here
package main

import "fmt"

func main() {
    fmt.Println("hello, world")
}

package main declares an executable. func main() is the entry point. No return value, no arguments — the OS exit code comes from os.Exit().

Variables & Declarations

Short declaration vs explicit declaration
// Short declaration — type inferred, only inside functions
name := "Evan"
count := 42
ratio := 3.14

// Explicit declaration — required at package level
var version string = "1.0.0"
var debug bool  // zero value: false

// Multiple declaration block
var (
    host = "localhost"
    port = 8080
)

:= declares and assigns in one step. The compiler infers the type from the right-hand side. You cannot use := outside a function body — package-level variables require var.

Constants

Constants are compile-time values
const Pi = 3.14159265
const (
    StatusOK       = 200
    StatusNotFound = 404
)

// iota — auto-incrementing constant generator
const (
    Read    = 1 << iota  // 1
    Write                // 2
    Execute              // 4
)

Constants must be expressible at compile time. iota resets to 0 in each const block and increments by 1 per line — ideal for bitmasks and enumerations.

Imports

Grouped imports with aliases
import (
    "fmt"
    "os"
    "strings"

    "github.com/spf13/cobra"
    log "github.com/sirupsen/logrus"  // alias import
    _ "github.com/lib/pq"             // blank import — side effects only
)

The blank import _ runs the package’s init() function without making its exports available. Common for database drivers that register themselves.

Control Flow

if/else with init statement
if err := doSomething(); err != nil {
    log.Fatal(err)
}
// err is not accessible here — scoped to the if block

The init statement (err := …​) scopes the variable to the if/else chain. This keeps error variables from leaking into the outer scope.

switch — no fallthrough by default
switch runtime.GOOS {
case "linux":
    fmt.Println("Linux")
case "darwin":
    fmt.Println("macOS")
default:
    fmt.Println("other")
}

// Type switch
switch v := i.(type) {
case int:
    fmt.Printf("integer: %d\n", v)
case string:
    fmt.Printf("string: %q\n", v)
}

Go switches do not fall through by default — each case breaks automatically. Use fallthrough explicitly if needed. Type switches use .(type) to branch on the concrete type of an interface value.

for — the only loop keyword
// C-style
for i := 0; i < 10; i++ {
    fmt.Println(i)
}

// while-style
for count > 0 {
    count--
}

// infinite
for {
    // break or return to exit
}

// range over slice
for i, v := range []string{"a", "b", "c"} {
    fmt.Printf("%d: %s\n", i, v)
}

for is Go’s only loop construct. range iterates slices, maps, strings, and channels. Use _ to discard index or value: for _, v := range items.

Printing & Formatting

fmt verbs
name := "Evan"
age := 30
pi := 3.14159

fmt.Printf("Name: %s, Age: %d\n", name, age)   // string, integer
fmt.Printf("Pi: %.2f\n", pi)                     // float with precision
fmt.Printf("Type: %T\n", pi)                     // type name
fmt.Printf("Value: %v\n", pi)                    // default format
fmt.Printf("Struct: %+v\n", config)              // struct with field names
fmt.Printf("Go syntax: %#v\n", config)           // Go-syntax representation

%v is the catch-all verb. %+v adds field names for structs. %#v produces Go-syntax output useful for debugging.

Zero Values

Every type has a zero value — no uninitialized memory
var i int       // 0
var f float64   // 0.0
var b bool      // false
var s string    // "" (empty string)
var p *int      // nil
var sl []int    // nil (but len/cap work: both return 0)
var m map[string]int  // nil (reads return zero value, writes panic)

Go does not have "undefined" or "null" in the JavaScript/Python sense. Every variable has a predictable zero value. A nil map can be read from safely but panics on write — always use make() before writing to a map.