πŸ“¦ Chapter 3 of 20

Variables & Constants

Every Go variable has a type, a value, a scope, a lifetime, and a home in memory. Master all declaration forms, understand zero values, and learn how Go decides whether a variable lives on the stack or the heap.

⏱ β€” πŸ“Š Beginner–Intermediate πŸ’¬ 12 interview questions

🎯 Learning Objectives

βœ… After this chapter you will:
  • Use all four Go variable declaration forms correctly
  • Explain type inference and when to use explicit types
  • Understand every zero value and why they exist
  • Define constants and use iota for enumerations
  • Explain variable scope, shadowing, and how to avoid bugs from it
  • Understand what escape analysis is and how it determines stack vs heap allocation
  • Explain variable lifetime and what it means for GC

πŸ“ Variable Declarations

Go has four ways to declare variables. Each has a specific use case.

Form 1: var with explicit type

go
package main

import "fmt"

// Package-level variable (lives for the program's lifetime)
var globalCounter int = 0

func main() {
    // Function-level variable with explicit type
    var name string = "Alice"
    var age  int    = 30
    var isAdmin bool = true

    // Multiple variables in one var block (common for related variables)
    var (
        host string = "localhost"
        port int    = 8080
        tls  bool   = false
    )

    fmt.Printf("name=%s age=%d admin=%v\n", name, age, isAdmin)
    fmt.Printf("host=%s port=%d tls=%v\n", host, port, tls)
}
// When to use: package-level variables, when you want the type visible for clarity
βœ… Output
name=Alice age=30 admin=true host=localhost port=8080 tls=false

Form 2: var with type inference

go
func main() {
    // Go infers the type from the right-hand side
    var name    = "Alice"    // inferred as string
    var age     = 30         // inferred as int
    var pi      = 3.14159    // inferred as float64
    var done    = false      // inferred as bool
    var letter  = 'A'        // inferred as int32 (rune)

    // What type did Go infer? Use %T to find out
    fmt.Printf("%T\n", name)    // string
    fmt.Printf("%T\n", age)     // int
    fmt.Printf("%T\n", pi)      // float64
    fmt.Printf("%T\n", letter)  // int32

    _ = done // suppress "declared and not used" error
}
βœ… Output
string int float64 int32
⚠️ Numeric Type Inference Trap

Integer literals are inferred as int, NOT int64 or int32. Float literals are inferred as float64, NOT float32. If you need a specific numeric type, use explicit declaration: var x int32 = 42

Form 3: Short variable declaration :=

The most common form. Can only be used inside functions.

go
func main() {
    // Short declaration β€” type inferred, cannot be used at package level
    name := "Alice"
    age  := 30
    pi   := 3.14

    // Multiple assignment
    x, y := 10, 20

    // Swap values elegantly
    x, y = y, x  // Note: = not :=  (both already declared)

    fmt.Printf("%s is %d years old\n", name, age)
    fmt.Println("pi =", pi)
    fmt.Printf("x=%d y=%d (after swap)\n", x, y)

    // Re-declaring with := (valid if at least ONE new variable on left)
    val, err := doSomething()  // both new
    val, err = doSomethingElse() // neither new β€” use = not :=
    newVal, err := doThird()    // newVal is new, err is re-used β€” valid :=
    _ = val; _ = newVal; _ = err
}

func doSomething() (int, error)     { return 1, nil }
func doSomethingElse() (int, error) { return 2, nil }
func doThird() (int, error)         { return 3, nil }

Form 4: Blank identifier _

go
package main

import (
    "fmt"
    "strconv"
)

func divide(a, b int) (int, int, error) {
    if b == 0 {
        return 0, 0, fmt.Errorf("division by zero")
    }
    return a / b, a % b, nil
}

func main() {
    // Discard the remainder with _
    quotient, _, err := divide(10, 3)
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    fmt.Println("Quotient:", quotient)  // 3

    // Discard the error (NOT recommended in production β€” shown for illustration)
    n, _ := strconv.Atoi("42")
    fmt.Println(n)  // 42

    // _ can also discard import side effects:
    // import _ "github.com/lib/pq"  // registers PostgreSQL driver
}
βœ… Output
Quotient: 3 42

πŸ”’ Zero Values β€” The Safety Net

In Go, every variable is initialized to its zero value automatically. There is no concept of an uninitialized variable. This is a deliberate safety feature β€” it prevents entire classes of bugs common in C/C++.

TypeZero ValueWhy this zero value?
boolfalseSafest default β€” nothing is active/enabled by default
int, int8, int16, int32, int640Mathematical identity for addition
uint, byte, uint8…0Same as above
float32, float640.0Mathematical identity
complex64, complex128(0+0i)Additive identity in complex numbers
string"" (empty string)Empty, but never nil β€” safe to len() and iterate
rune (int32)0The null character (U+0000)
pointernilPoints to nothing β€” safe nil checks protect against crashes
slicenilnil slice is safe: len(), cap(), range all work on nil slice
mapnilnil map can be read but NOT written β€” must make() before writing
channelnilnil channel: send/receive block forever; close panics
funcnilCalling a nil function panics
interfacenilnil interface has no type and no value
structAll fields zeroedEach field gets its own zero value recursively
arrayAll elements zeroedSame as struct β€” every element gets zero value
go
package main

import "fmt"

type Config struct {
    Host    string  // zero: ""
    Port    int     // zero: 0
    TLS     bool    // zero: false
    Timeout float64 // zero: 0.0
    Tags    []string // zero: nil (but safe to use)
}

func main() {
    var c Config
    // No need to set every field β€” zero values are safe
    fmt.Printf("Host:    %q\n", c.Host)      // ""
    fmt.Printf("Port:    %d\n", c.Port)      // 0
    fmt.Printf("TLS:     %v\n", c.TLS)       // false
    fmt.Printf("Tags:    %v (nil=%v)\n", c.Tags, c.Tags == nil) // [] (nil=true)
    fmt.Printf("len(Tags): %d\n", len(c.Tags)) // 0 β€” safe on nil slice!

    // Zero value makes sync.Mutex immediately usable:
    // var mu sync.Mutex  ← this just works, no New() needed
}
βœ… Output
Host: "" Port: 0 TLS: false Tags: [] (nil=true) len(Tags): 0
🎯 The "Zero Value Useful" Principle

A core Go design principle: types should be immediately usable at their zero value without requiring explicit initialization. sync.Mutex, bytes.Buffer, and sync.WaitGroup are great examples. When designing your own types, always ask: "Is the zero value useful?"

πŸ“Œ Constants & iota

Constants

Constants are values that are computed at compile time and never change at runtime. They can be typed or untyped β€” and untyped constants are more flexible than you might expect.

go
package main

import "fmt"

// Typed constant β€” has a fixed type
const MaxRetries int = 3
const Pi float64 = 3.14159265358979323846

// Untyped constant β€” extremely precise, type inferred at use
const E = 2.71828182845904523536 // untyped floating-point
const Greeting = "Hello"          // untyped string
const Big = 1 << 62               // untyped integer (huge precision)

// Constant block
const (
    StatusOK    = 200
    StatusNotFound = 404
    StatusError    = 500
)

// Constants can be expressions (evaluated at compile time)
const (
    KB = 1024
    MB = 1024 * KB
    GB = 1024 * MB
    TB = 1024 * GB
)

func main() {
    fmt.Printf("1 KB = %d bytes\n", KB)
    fmt.Printf("1 GB = %d bytes\n", GB)
    fmt.Printf("Pi = %v\n", Pi)

    // Untyped constant adapts to context
    var x float32 = E  // E (untyped) assigned to float32
    var y float64 = E  // E (untyped) assigned to float64
    fmt.Printf("float32: %v, float64: %v\n", x, y)
}
βœ… Output
1 KB = 1024 bytes 1 GB = 1073741824 bytes Pi = 3.14159265358979 float32: 2.7182817, float64: 2.718281828459045

iota β€” The Enumerator

iota is a compile-time counter that resets to 0 at each const block and increments by 1 at each constant specification. It's Go's elegant solution for enums.

go
package main

import "fmt"

// Basic iota β€” 0, 1, 2, 3
type Weekday int
const (
    Sunday Weekday = iota  // 0
    Monday                  // 1 (iota is implicitly repeated)
    Tuesday                 // 2
    Wednesday               // 3
    Thursday                // 4
    Friday                  // 5
    Saturday                // 6
)

// iota with arithmetic β€” bit flags (extremely common pattern)
type Permission uint
const (
    Read    Permission = 1 << iota  // 1 << 0 = 1  (0001)
    Write                            // 1 << 1 = 2  (0010)
    Execute                          // 1 << 2 = 4  (0100)
    Admin                            // 1 << 3 = 8  (1000)
)

// Skip a value with _
type Level int
const (
    _        = iota  // 0: unused/discard (sentinel)
    Low  Level = iota  // 1
    Mid                // 2
    High               // 3
)

// iota in expressions
const (
    _  = iota             // 0: skip
    KB = 1 << (10 * iota) // 1 << 10 = 1024
    MB                     // 1 << 20 = 1048576
    GB                     // 1 << 30 = 1073741824
)

func (d Weekday) String() string {
    names := [...]string{"Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"}
    if d < Sunday || d > Saturday {
        return fmt.Sprintf("Weekday(%d)", d)
    }
    return names[d]
}

func main() {
    today := Wednesday
    fmt.Println("Today is", today)  // Today is Wednesday

    // Combining permissions with bitwise OR
    myPerms := Read | Write
    fmt.Printf("Permissions: %04b (%d)\n", myPerms, myPerms)  // 0011 (3)

    // Checking permissions with bitwise AND
    if myPerms&Write != 0 {
        fmt.Println("Has write permission βœ“")
    }
    if myPerms&Execute == 0 {
        fmt.Println("No execute permission βœ—")
    }

    fmt.Printf("1KB = %d, 1MB = %d, 1GB = %d\n", KB, MB, GB)
}
βœ… Output
Today is Wednesday Permissions: 0011 (3) Has write permission βœ“ No execute permission βœ— 1KB = 1024, 1MB = 1048576, 1GB = 1073741824

πŸ”­ Variable Scope

Scope defines where in the source code a variable is visible and accessible. Go uses lexical scoping β€” the scope is determined by the block (pair of {}) in which the variable is declared.

πŸ“Š Scope Levels in Go (outermost β†’ innermost)
Universe scope
└── Package scope (var/const/func declared outside any function)
    └── File scope (import declarations)
        └── Function scope
            └── Block scope (if/for/switch bodies)
                └── Deeper blocks (nested if/for)
go
package main

import "fmt"

var packageLevel = "I am visible throughout this package" // package scope

func scopeDemo() {
    funcLevel := "I am visible in this function" // function scope

    {   // inner block
        blockLevel := "I am only visible in this block"
        fmt.Println(blockLevel)
        fmt.Println(funcLevel)    // can access outer scope
        fmt.Println(packageLevel) // can access package scope
    }
    // fmt.Println(blockLevel)  // ← compile error: undefined: blockLevel

    // if statement creates its own scope
    if x := computeValue(); x > 0 { // x is scoped to this if block!
        fmt.Println("positive:", x)
    }
    // fmt.Println(x)  // ← compile error: undefined: x (out of if scope)

    // for loop variable scope
    for i := 0; i < 3; i++ {
        // i is scoped to the for loop
        _ = i
    }
    // fmt.Println(i)  // ← compile error

    fmt.Println(funcLevel)
}

func computeValue() int { return 42 }

func main() {
    scopeDemo()
}

Variable Shadowing β€” A Common Bug Source

Shadowing occurs when an inner variable declaration uses the same name as an outer one, "hiding" the outer variable. This is legal Go but a common source of bugs.

go
package main

import "fmt"

func shadowingBug() {
    err := doStep1()     // outer err

    if true {
        result, err := doStep2()  // ← THIS SHADOWS outer err!
        // A new 'err' is created inside this block.
        // The outer 'err' is still the one from doStep1.
        fmt.Println(result, err)
    }

    // Bug: you might think err was updated above, but it wasn't
    if err != nil { // ← this checks the ORIGINAL err from doStep1
        fmt.Println("Error from step 1:", err)
    }
}

// The correct way β€” use = not := to update the outer variable:
func correctVersion() {
    var result int
    err := doStep1()

    if true {
        var e error
        result, e = doStep2()  // assign to existing variables
        if e != nil {
            err = e  // explicitly update the outer err
        }
        fmt.Println(result)
    }

    if err != nil {
        fmt.Println("Error:", err)
    }
}

// Another common shadowing trap: err shadowed in a loop
func loopShadowing() {
    var outerErr error

    for i := 0; i < 3; i++ {
        // This := creates a new err each iteration, doesn't update outerErr
        _, err := doSomethingN(i)
        if err != nil {
            outerErr = err  // must explicitly assign to outer
        }
    }

    if outerErr != nil {
        fmt.Println("Loop error:", outerErr)
    }
}

func doStep1() error           { return nil }
func doStep2() (int, error)    { return 0, nil }
func doSomethingN(int) (int, error) { return 0, nil }

func main() {
    shadowingBug()
    correctVersion()
    loopShadowing()
}
🚨 Shadowing with Errors β€” The #1 Gotcha

The most dangerous shadowing pattern: using := in a block when you mean to update the outer err. The code compiles fine. The outer error is silently never updated. Use go vet with the shadow analyser to detect this.

shell
# Install and use the shadow analyser
go install golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow@latest
shadow ./...

🧠 Stack vs Heap β€” Where Variables Live

When you declare a variable in Go, the compiler decides whether it should live on the stack (fast, automatic cleanup) or the heap (slower, GC-managed). This decision is called escape analysis.

The Stack

  • Each goroutine has its own stack (starts at 2–8 KB, grows/shrinks as needed)
  • Stack allocation: simply decrements the stack pointer β€” essentially free
  • Variables on the stack are automatically freed when the function returns
  • No GC involvement
  • Variables on the stack cannot outlive the function that created them

The Heap

  • Shared memory pool managed by the Go runtime
  • Allocation requires the memory allocator to find a free slot β€” slower
  • Variables on the heap live until the GC determines no references exist
  • GC overhead increases with heap size
🧡 Goroutine Stack vs Heap Layout
Stack (goroutine-local)
high addrmain() frame
x = 42
name = ptr→→
foo() frame
a = 10
low addr b = 20
Grows downward, shrinks on return
Heap (GC-managed)
"Alice" (string data)
*User{name:"Bob"}
[]int{1,2,3} (backing)
(free)
GC scans and frees unreachable

Escape Analysis β€” How the Compiler Decides

The Go compiler runs escape analysis to determine where each variable should be allocated. A variable "escapes to the heap" if it can outlive the function that created it.

go
package main

// Case 1: Variable stays on stack β€” doesn't escape
func noEscape() int {
    x := 42    // x is used only within this function
    return x   // returning the VALUE, not a pointer β€” x stays on stack
}

// Case 2: Variable escapes to heap β€” returned pointer
func escapes() *int {
    x := 42     // x must live beyond this function's return
    return &x   // returning a POINTER β€” x escapes to heap
}

// Case 3: Escapes via interface β€” common source of heap allocations
func escapesViaInterface() interface{} {
    x := 42
    return x   // Storing in interface{} causes escape in most cases
}

// Case 4: Escapes via slice/map storage
func escapesViaSlice() {
    s := make([]int, 10)
    for i := range s {
        val := i * 2
        s[i] = val  // val might escape if slice escapes
    }
    _ = s
}

func main() {
    a := noEscape()   // a on stack
    b := escapes()    // *b on heap, b (the pointer) on stack
    _ = a; _ = b
}
shell
# Inspect escape analysis decisions
go build -gcflags="-m" main.go

# Output will tell you what escaped:
# ./main.go:11:2: x escapes to heap
# ./main.go:6:2: x does not escape
# ./main.go:17:2: x escapes to heap (interface conversion)

# More verbose output:
go build -gcflags="-m -m" main.go
πŸ’‘ Performance Insight

Heap allocations are more expensive than stack allocations and create work for the GC. High-performance Go code minimises unnecessary heap allocations. But don't prematurely optimise β€” write clear code first, then use profiling to find real bottlenecks.

Variable Lifetime

Lifetime is the interval during which a variable is guaranteed to exist. The compiler tracks this precisely:

  • Stack variables: Lifetime = duration of the function call that created them
  • Heap variables: Lifetime = from allocation until the GC determines no more references exist (reachability-based)
  • Package-level variables: Lifetime = entire program duration

βœ… Best Practices

  • Prefer := inside functions β€” it's idiomatic and concise
  • Use var at package level or when you want to declare a variable without an initializer
  • Use var when the zero value is a meaningful "empty/not-set" state
  • Never ignore errors β€” _, err := f() is fine if you check err; ignoring both is a bug
  • Keep variable declarations close to their first use β€” don't declare at top of function like in C
  • Name constants clearly β€” MaxRetries not MAX_RETRIES (Go style is MixedCaps)
  • Use iota for sequential enum-like constants; use explicit values when order matters for serialisation
  • Be alert to shadowing β€” read your code for inner := that might shadow outer variables

🚫 Common Mistakes

go
package main

// ❌ MISTAKE 1: Declared variable never used
func unused() {
    x := 42  // compile error: x declared and not used
}

// ❌ MISTAKE 2: Assuming := updates an outer variable
func shadowErr() error {
    err := doSomething()
    if true {
        result, err := doMore()  // BUG: shadows outer err
        _ = result; _ = err
    }
    return err  // this is the original err, not the one from doMore()
}

// ❌ MISTAKE 3: Thinking short int literals are int64
func intSizeAssumption() {
    x := 42        // x is int (platform-dependent: 32 or 64 bits)
    var y int64 = x // compile error: cannot use x (int) as int64
    _ = y
}

// βœ… FIX for mistake 3:
func intSizeFixed() {
    x := int64(42)   // explicit conversion
    var y int64 = 42 // or explicit type declaration
    _, _ = x, y
}

// ❌ MISTAKE 4: Modifying a nil map
func nilMap() {
    var m map[string]int  // m is nil
    m["key"] = 1          // PANIC: assignment to entry in nil map
}

// βœ… FIX:
func nilMapFixed() {
    m := make(map[string]int)  // initialise with make
    m["key"] = 1               // now safe
}

func doSomething() error  { return nil }
func doMore() (int, error) { return 0, nil }

func main() {}

🎀 Interview Questions

1
What is the difference between var x int and x := 0?
β–Ά
Detailed Answer

Both declare an integer variable x with value 0. The differences are:

Syntax: var x int uses the long form β€” explicit type, initialised to zero value. x := 0 uses the short declaration β€” type inferred, can only appear inside functions.

Location: var x int can appear at package level; := cannot.

Idiom: var x int is preferred when the zero value is your intentional starting value and you want that intent to be visible in the code. x := 0 is most common inside functions when type inference makes the code cleaner.

Common interview trap: "Are they the same?" β€” Almost, but var x int is 100% valid at package scope; x := 0 is a compile error at package scope.

2
What is escape analysis? When does a variable escape to the heap?
β–Ά
Detailed Answer

Escape analysis is a compile-time analysis that determines where each variable should be allocated β€” stack or heap. It's performed by the Go compiler.

A variable "escapes to the heap" when it must outlive the function that creates it. Common reasons: (1) a pointer to the variable is returned; (2) a pointer is stored in an interface; (3) a pointer is stored in a data structure that itself escapes; (4) the variable is captured by a closure; (5) the variable's size is not known at compile time.

Stack allocation is faster (no GC involvement), heap allocation enables longer lifetimes but requires GC. You can inspect escape analysis decisions with: go build -gcflags="-m"

Production relevance: In hot code paths, excessive heap allocations cause GC pressure, increasing latency. Profiling with pprof and the allocs profile reveals this.

3
What is variable shadowing in Go? Provide an example of where it causes a bug.
β–Ά
Detailed Answer

Shadowing occurs when a variable declared in an inner scope has the same name as one in an outer scope, effectively hiding the outer variable within the inner scope.

Classic bug: using := in an if block to handle an error when you meant to update the outer error variable. The outer error remains unchanged, the inner one is discarded. The code compiles perfectly β€” the bug only manifests at runtime.

Detection: Use go vet with the shadow analyser, or configure golangci-lint to report shadowing.

Why interviewers ask: Shadowing is responsible for real production bugs. Knowing about it demonstrates you write defensive code.

4
What is iota? How do you create bit flags with it?
β–Ά
Detailed Answer

iota is a compile-time incrementing counter available inside const blocks. It starts at 0 and increments by 1 for each constant in the block.

Bit flags: use 1 << iota to generate powers of two, which can be combined with bitwise OR and tested with bitwise AND. This is the standard Go pattern for permission flags, feature flags, and option bitmasks.

Example: Read = 1 << iota gives 1; Write gives 2; Execute gives 4. These can be combined: Read | Write = 3. Tested: (perms & Write) != 0.

πŸ§ͺ Quiz

🧩
Variables & Constants
5 questions
1. Where can the short variable declaration := be used?
2. What is the zero value of a string in Go?
3. What type does Go infer for x := 42?
4. What value does iota have at the third constant in a const block?
5. A variable "escapes to the heap" when:
β€”

✏️ Exercises

Exercise 1
All Declaration Forms
⭐ Beginner

Write a program that declares at least one variable using each of the four forms (var type, var inference, :=, blank _), and prints the type of each using %T.

go
package main
import "fmt"
func main() {
    var a int = 10           // explicit type
    var b = "hello"          // type inference with var
    c := 3.14                // short declaration
    d, _ := 42, "discard"   // _ discards second value
    fmt.Printf("a: %T = %v\n", a, a)
    fmt.Printf("b: %T = %v\n", b, b)
    fmt.Printf("c: %T = %v\n", c, c)
    fmt.Printf("d: %T = %v\n", d, d)
}
Exercise 2
HTTP Status Code Enum
⭐⭐ Intermediate

Create a custom type StatusCode and use iota to define constants for: StatusOK=200, StatusCreated=201, StatusBadRequest=400, StatusUnauthorized=401, StatusNotFound=404, StatusInternalServerError=500. Implement a String() method.

go
package main
import "fmt"

type StatusCode int

const (
    StatusOK                  StatusCode = 200
    StatusCreated             StatusCode = 201
    StatusBadRequest          StatusCode = 400
    StatusUnauthorized        StatusCode = 401
    StatusNotFound            StatusCode = 404
    StatusInternalServerError StatusCode = 500
)

func (s StatusCode) String() string {
    switch s {
    case StatusOK:                  return "200 OK"
    case StatusCreated:             return "201 Created"
    case StatusBadRequest:          return "400 Bad Request"
    case StatusUnauthorized:        return "401 Unauthorized"
    case StatusNotFound:            return "404 Not Found"
    case StatusInternalServerError: return "500 Internal Server Error"
    default:                        return fmt.Sprintf("Unknown(%d)", int(s))
    }
}

func main() {
    codes := []StatusCode{StatusOK, StatusNotFound, StatusInternalServerError}
    for _, c := range codes {
        fmt.Println(c)
    }
}
// 200 OK
// 404 Not Found
// 500 Internal Server Error
Exercise 3
Escape Analysis Investigation
⭐⭐⭐ Advanced

Write three functions: one that returns a value (no escape), one that returns a pointer (escape), and one that stores a value in a slice (check if it escapes). Use go build -gcflags="-m" to observe the compiler's escape analysis output.

go
package main

// noEscape: x stays on stack
func noEscape() int {
    x := 42
    return x  // value copy, not pointer
}

// escapes: x goes to heap
func escapes() *int {
    x := 42
    return &x  // pointer returned β€” x escapes
}

// mayEscape: depends on what happens to the slice
func sliceEscape() []int {
    s := make([]int, 3)  // slice backing array may escape
    s[0] = 1
    return s  // returning slice β€” backing array escapes
}

func main() {
    a := noEscape()
    b := escapes()
    c := sliceEscape()
    _, _, _ = a, b, c
}

// Build with: go build -gcflags="-m" main.go
// Output:
// ./main.go:5:2:  x does not escape
// ./main.go:11:2: x escapes to heap
// ./main.go:17:15: make([]int, 3) escapes to heap

πŸ“‹ Summary

  • Four declaration forms: var T, var inferred, :=, blank _
  • Every Go variable gets its zero value β€” no uninitialised variables
  • Constants are compile-time values; iota creates sequential enums and bit flags
  • Scope is lexical: inner blocks see outer names; outer blocks cannot see inner names
  • Shadowing: inner := can silently hide outer variable β€” watch for err shadowing
  • Stack: fast, goroutine-local, auto-freed on function return
  • Heap: GC-managed, used when variable must outlive its function
  • Escape analysis: use go build -gcflags="-m" to see compiler decisions

πŸ“Œ Cheat Sheet

πŸ“Œ Variables & Constants
Explicit type
var x int = 42
Type inference
var x = 42
Short decl (func only)
x := 42
Discard value
_, err := f()
Constant
const Max = 100
iota enum
A = iota // 0, B // 1...
Bit flag
R = 1 << iota // 1,2,4...
Zero value string
""
Zero value int
0
Zero value bool
false
Zero value pointer
nil
Escape analysis
go build -gcflags="-m"