Skip to content

02 · Variables, Types & Operators

🎥 Video walkthrough

Declaring variables

Go is statically typed but infers types for you most of the time. Two ways to declare a variable:

package main

import "fmt"

func main() {
    // var form: explicit type, works anywhere (including package scope)
    var age int = 30
    var name string = "Ada"

    // type inference: var without a type
    var city = "Boston"

    // short declaration :=  -- only inside function bodies
    country := "USA"

    fmt.Println(age, name, city, country)
}

:= is the idiomatic choice inside functions. var is required at package level, when you want a zero value with no initializer, or when the inferred type would be wrong (e.g. you want float64 but wrote a whole number).

Zero values

Unlike many languages, Go never leaves a declared variable "undefined" — it gets a zero value automatically:

var count int      // 0
var price float64   // 0.0
var label string     // "" (empty string)
var ready bool       // false
var data []int       // nil (nil slice, safe to read, len is 0)

Basic types

Category Types
Integers int, int8/16/32/64, uint, uint8 (byte), uint16/32/64
Floating point float32, float64
Text string, rune (an alias for int32, holds a Unicode code point)
Boolean bool
Complex (rare) complex64, complex128

int is the general-purpose choice — its size (32 or 64 bit) matches the platform's native word size. Use a specific width (int64, uint8) only when the size matters, e.g. binary protocols or memory-tight structures.

package main

import "fmt"

func main() {
    var temperature float64 = 98.6
    var initial byte = 'A'          // byte is an alias for uint8
    var letter rune = '日'           // rune holds one Unicode code point

    fmt.Println(temperature, initial, string(letter))
    fmt.Printf("%T %T %T\n", temperature, initial, letter)
    // float64 uint8 int32
}

Constants and iota

package main

import "fmt"

const Pi = 3.14159 // untyped constant, adapts to context

const (
    StatusPending = iota // 0
    StatusActive         // 1
    StatusDone            // 2
)

func main() {
    fmt.Println(Pi, StatusPending, StatusActive, StatusDone)
}

iota resets to 0 at each const (...) block and increments by one per line — the standard idiom for defining a small enum-like set of related constants.

Type conversion

Go never converts types implicitly. Mixing an int and a float64 in an expression is a compile error — you must convert explicitly:

package main

import "fmt"

func main() {
    var i int = 10
    var f float64 = 3.0

    // result := i + f          // compile error: mismatched types
    result := float64(i) + f
    fmt.Println(result) // 13

    back := int(result) // truncates, does not round: 13
    fmt.Println(back)
}

Operators

package main

import "fmt"

func main() {
    a, b := 17, 5

    fmt.Println(a + b)  // 22
    fmt.Println(a - b)  // 12
    fmt.Println(a * b)  // 85
    fmt.Println(a / b)  // 3   (integer division truncates)
    fmt.Println(a % b)  // 2   (remainder)

    // Comparison
    fmt.Println(a > b, a == b, a != b) // true false true

    // Logical
    fmt.Println(a > 10 && b < 10) // true
    fmt.Println(a < 10 || b < 10) // true

    // Increment/decrement are statements, not expressions
    a++
    b--
    fmt.Println(a, b) // 18 4
}

How It Actually Works

Every Go value carries a static type known at compile time — the compiler uses it to pick the machine instructions for +, decide how many bytes to allocate, and reject mismatched operations before the binary is even built. This is different from Python or JS, where a value carries its type at runtime and the interpreter checks it on every operation. var x int allocates 8 bytes (on a 64-bit target) on the stack if escape analysis proves x never outlives the function, or on the heap otherwise — the compiler decides this, not you (see level-1/07 for more). Numeric conversions like float64(i) are never implicit in Go specifically because implicit widening conversions are a classic source of silent precision-loss bugs in C; the language designers traded convenience for a compiler error you see immediately. Untyped constants (const x = 5) are a special case: they live in the compiler's constant table with arbitrary precision until they're assigned to a typed variable, which is why const x = 1 << 100 compiles fine but var x int64 = 1 << 100 doesn't.

Cheat sheet

Concept Syntax
Declare with inference x := 5
Declare with explicit type var x int = 5
Zero value var x int0
Constant const Pi = 3.14
Enum-like constants const ( A = iota; B; C )
Convert types float64(x), int(y)
Multiple assignment a, b := 1, 2

🔀 See this in another language

Exercise

Write a program that declares a rectangle's width and height as float64 using :=, computes its area and perimeter, and prints both with fmt.Printf using the %.2f verb for two decimal places. Then add an int constant block using iota for three shape types (Rectangle, Circle, Triangle) and print the constant matching your rectangle.