On this page
Variables and Types
Go's Type System
Go is a strongly statically typed language. This means two things: first, every variable has a fixed type that the compiler knows at compile time; second, conversions between types never happen implicitly — you must always be explicit. This combination eliminates an entire category of bugs common in JavaScript or Python.
Understanding Go's type system is fundamental because everything else in the language is built on top of it.
Declaring Variables
Go offers several ways to declare variables, each with its ideal context:
1. Explicit declaration with `var`
var name string = "Go"
var age int = 13 // Go turned 13 in 2022
var pi float64 = 3.14159
var active bool = true2. Declaration with type inference
If you provide an initial value, Go can infer the type:
var name = "Go" // string inferred
var pi = 3.14159 // float64 inferred
var active = true // bool inferred3. Short declaration with `:=`
The most common form inside functions. Only works inside functions (not at package level):
func main() {
name := "Gopher" // string
version := 1.26 // float64
active := true // bool
b := byte('A') // byte (uint8)
}4. `var` block for multiple variables
When declaring several related variables, a var block improves readability:
var (
host string = "localhost"
port int = 8080
debug bool = false
timeout float64 = 30.0
)Numeric Types
Go has a specific set of numeric types. The right choice matters for performance and semantics:
Signed integers
| Type | Size | Range |
|---|---|---|
int8 |
8 bits | -128 to 127 |
int16 |
16 bits | -32,768 to 32,767 |
int32 |
32 bits | -2,147,483,648 to 2,147,483,647 |
int64 |
64 bits | ±9.2 × 10¹⁸ |
int |
32 or 64 bits* | platform-dependent |
*int is 64 bits on 64-bit systems (virtually all modern systems). It is the default integer type.
Unsigned integers
var b uint8 = 255 // byte — alias for uint8
var s uint16 = 65535
var u uint32 = 4294967295
var g uint64 = 18446744073709551615
var n uint = 42 // unsigned, platform sizeFloating point
var f32 float32 = 3.14 // single precision (~7 decimal digits)
var f64 float64 = 3.14159265358979 // double precision (~15 decimal digits)
// float64 is the default type for floating-point literals
pi := 3.14159 // float64The `string` Type
In Go, a string is an immutable sequence of bytes encoded in UTF-8. You cannot modify a character in a string directly — you must create a new one:
greeting := "Hello, Go!"
// Length in bytes (not in Unicode characters)
fmt.Println(len(greeting))
// Concatenation
full := greeting + " How are you?"
// Multi-line string with backticks (raw string literal)
json := `{
"name": "Go",
"version": 1.26
}`
// Interpolation (requires fmt.Sprintf)
message := fmt.Sprintf("Version: %.2f", 1.26)`byte` and `rune`
Here is a crucial distinction in Go:
byte(alias foruint8): represents a single byte. Useful for working with binary data.rune(alias forint32): represents a Unicode code point. A single character such asñ,é, or中may occupy multiple bytes in UTF-8, but is always a single rune.
// Iterate over bytes
s := "hello"
for i := 0; i < len(s); i++ {
fmt.Printf("byte[%d] = %d\n", i, s[i])
}
// Iterate over runes (Unicode characters correctly)
for i, r := range "Hello!" {
fmt.Printf("rune[%d] = %c (%d)\n", i, r, r)
}
// Convert string to rune slice for index access
runes := []rune("Hello!")
fmt.Println(len(runes)) // 6 (characters), not bytesThe `bool` Type
active := true
inactive := false
// Logical operators
result := active && !inactive // true
either := active || inactive // trueZero Values
This is one of Go's most elegant features: every variable declared without an initial value automatically receives its zero value. There are no uninitialized variables, no undefined behavior:
var i int // 0
var f float64 // 0.0
var b bool // false
var s string // "" (empty string)
var p *int // nil
var slice []int // nil
var m map[string]int // nilZero values make code predictable. In other languages, reading an uninitialized variable can give random results or throw exceptions. In Go, you always get the well-defined zero value for the type.
Constants and `const`
Constants in Go are evaluated at compile time and do not occupy memory at runtime:
const Pi = 3.14159265358979323846
const Company = "Bemorex"
const MaxConnections = 100
// Typed constant
const Timeout time.Duration = 30 * time.Second
// Constant block
const (
KB = 1024
MB = 1024 * KB
GB = 1024 * MB
)`iota`: Idiomatic Enumerations
iota is a special identifier used within const blocks. It starts at 0 and increments by 1 for each constant in the block:
type Level int
const (
Beginner Level = iota // 0
Intermediate // 1
Advanced // 2
Expert // 3
)
// With expressions
type ByteSize float64
const (
_ = iota // Ignore the first value with _
KB ByteSize = 1 << (10 * iota) // 1024
MB // 1048576
GB // 1073741824
TB // 1099511627776
)Explicit Type Conversion
In Go, there is never implicit conversion between numeric types. You must be explicit:
var i int = 42
var f float64 = float64(i) // int → float64
var u uint = uint(f) // float64 → uint
// String to number
import "strconv"
n, err := strconv.Atoi("42") // string → int
if err != nil {
fmt.Println("Conversion error")
}
f, err := strconv.ParseFloat("3.14", 64) // string → float64
// Number to string
s := strconv.Itoa(42) // int → string (more efficient than fmt.Sprintf)
s2 := fmt.Sprintf("%.2f", 3.14) // float64 → formatted stringMultiple Assignment
Go allows assigning multiple variables in a single line:
x, y := 10, 20
a, b := "hello", true
// Swap values without a temporary variable
x, y = y, x
fmt.Println(x, y) // 20 10The Blank Identifier `_`
The underscore _ discards values. It is Go's "black hole":
// Ignore the index in a range
for _, value := range []int{1, 2, 3} {
fmt.Println(value)
}
// Ignore a return value
result, _ := strconv.Atoi("42") // ignore the error (not recommended in production)With this solid understanding of the type system, in the next lesson we will explore how to control execution flow with if, switch, and Go's only loop: for.
Sign in to track your progress