> series: learn Go from 0 to 100 | article 2 of 19
In the previous article we installed Go and compiled our first binary. Today we cover the raw material everything else is made of: Go data types, variables and constants. And at the end, iota, that word that shows up in every Go codebase and nobody explains right the first time.
Static typing, minimal ceremony
Go is statically typed: every variable gets a type nailed down at compile time. What it doesn’t have is the verbosity you associate with that sentence. There are three ways to declare:
// Full form: var, name, type, value.
var age int = 30
// The compiler infers the type. Still an int.
var age = 30
// Short form: declare and initialize in one shot.
// Only works inside functions.
age := 30
The := operator is the one you’ll use 90% of the time. At package level (outside functions) you need var. Source: A Tour of Go, short declarations.
Coming from Python or JS, age := 30 will look like your everyday assignment. The difference: the type is locked forever. A later age = “thirty” is not a rebind, it’s a compile error. Coming from Java, this is your Java 10 var, except Go shipped it in 2009 (JEP 286 landed in 2018, no hard feelings).
One important detail: a declared and unused variable is a compile error, not a warning to ignore. It’s a design decision to keep codebases free of clutter (official FAQ).
The basic types: the full catalog of Go data types
The full catalog of Go data types, per the spec:
- bool
- string (immutable, UTF-8 inside, we’ll tear it apart in article 8)
- Signed integers: int, int8, int16, int32, int64
- Unsigned integers: uint, uint8, uint16, uint32, uint64, uintptr
- byte (alias for uint8) and rune (alias for int32, a Unicode code point)
- Floats: float32, float64
- Complex: complex64, complex128
Plain int is platform dependent: 64 bits on 64-bit systems, 32 on 32-bit ones (A Tour of Go, basic types). Unless you have a concrete reason, use int for integers and float64 for decimals.
Zero values: nothing goes uninitialized
Go has no garbage values and no undefined. A variable declared without a value gets its zero value: 0 for numbers, false for bool, empty string for strings (A Tour of Go, zero values).
var counter int // 0
var active bool // false
var name string // ""
// %v prints the value, %q the quoted string.
fmt.Printf("%v %v %q\n", counter, active, name)
Quick comparison: in JS an unassigned variable is undefined and in Java an uninitialized field can surprise you with null. In Go the behavior is defined in the spec, period.
Conversions: always explicit
Go never converts types for you. Not even between int and int64:
i := 42
f := float64(i) // explicit conversion
u := uint(f)
// This does NOT compile: mismatched types
// sum := i + f
The syntax is Type(value), no exceptions (A Tour of Go, conversions).
Debugging trick: fmt.Printf(“%T”, x) tells you the type of anything.
Constants
Declared with const, and := is not allowed:
const Pi = 3.14159
const Greeting = "hello"
const Max int = 100
Here’s a little-known superpower: untyped constants live with arbitrary precision until they’re used. You can write const Huge = 1 << 100 and it compiles fine, as long as it fits the destination type when used (spec, constants).
iota: the enum generator that isn’t an enum
Go has no enum keyword. It has something simpler and stranger: iota, a counter worth 0 on the first line of a const block, adding 1 per following line (spec, iota).
type Level int
const (
Debug Level = iota // 0
Info // 1 (the expression repeats itself)
Warn // 2
Error // 3
)
Notice the trick: the following lines write nothing and inherit the first line’s expression, with iota already incremented. Compared to the ceremony of a Java enum or a Python Enum class, this is almost ascii art.
And the canonical example from Effective Go, byte sizes with bit shifting:
type ByteSize float64
const (
_ = iota // discard the 0
KB ByteSize = 1 << (10 * iota) // 1 << 10 = 1024
MB // 1 << 20
GB // 1 << 30
TB // 1 << 40
)
The underscore _ is the blank identifier: assign anything to it and it throws it away, no complaints.
Technical honesty: this is not a real enum. Nothing stops someone from writing Level(42) and compiling. Go picks simplicity and leaves validation in your hands. We’ll get better tools when we reach interfaces (article 6).

Weekly challenge: Go data types
- Declare one variable of each basic type without initializing and print value and type with fmt.Printf(“%v %T\n”, …). Confirm the zero values.
- Reproduce the ByteSize block and compute how many MB fit in 3 GB using only the constants.
- Bonus: declare a variable, don’t use it, and enjoy the compiler message. Then try assigning a string to an int. Knowing the errors is also learning the language.
Next article: if, for and switch. Spoiler: the whole language has exactly one loop.
> exit