Basic Types
Every value in Go has a type. The basic types (integers, floats, booleans, strings, and complex numbers) are the atoms you'll combine into everything else. This chapter is a reference tour: ranges, sizes, overflow, and Go's strict conversion rules.
Learning objectives
- List Go's integer types (signed and unsigned) and their ranges.
- Know when to prefer
int64vsint. - Explain what happens on integer overflow at run time.
- Pick the right floating-point type and understand its precision limits.
- Convert between numeric types explicitly and safely.
Integer types
Go's integer family has ten members. Five signed (hold positive and negative values), five unsigned (positive only):
| Signed | Range | Unsigned | Range |
|---|---|---|---|
int8 | -128 to 127 | uint8 / byte | 0 to 255 |
int16 | -32,768 to 32,767 | uint16 | 0 to 65,535 |
int32 / rune | ~±2.1 billion | uint32 | ~4.3 billion |
int64 | ~±9.2 × 1018 | uint64 | ~1.8 × 1019 |
int | 32- or 64-bit (platform) | uint | 32- or 64-bit (platform) |
Two aliases worth noticing:
byteis an alias foruint8: you'll see it constantly when dealing with bytes of data.runeis an alias forint32: it represents a Unicode code point. Chapter 7 is entirely about runes and strings.
int until you have a reason not to
int is the idiomatic default for counters, indexes, and
general numeric work. Reach for a sized type like int64
when you need a specific range, performance characteristic, or
binary/protocol compatibility. Unsigned types are less common than
you'd think, int is fine for "counts of things".
Overflow and wrap-around
At compile time, Go rejects literal values that don't fit:
var b int8 = 300 // ❌ constant 300 overflows int8
At run time, integer arithmetic silently wraps around:
package main
import "fmt"
func main() {
var x uint8 = 255
x = x + 1 // wraps to 0
fmt.Println(x) // prints 0
var y int8 = 127
y = y + 1 // wraps to -128
fmt.Println(y) // prints -128
}
Unlike some languages, Go does not panic on overflow. If you
need overflow detection, you have to check explicitly. The
math package defines constants like math.MaxInt32
and math.MinInt64 to help.
Floating-point types
Go has two floating-point types, both IEEE 754:
float32: single precision, ~7 decimal digits.float64: double precision, ~15 digits. The default choice.
var pi float64 = 3.14159265358979
var temp float32 = 98.6
Floating-point arithmetic isn't exact. The classic trap:
package main
import "fmt"
func main() {
a := 0.1 + 0.2
fmt.Println(a) // 0.30000000000000004
fmt.Println(a == 0.3) // false
}
github.com/shopspring/decimal. This is a lesson
every programmer learns once the hard way.
Complex numbers
Go has built-in complex numbers, for when you're doing signal processing, scientific computing, or you just want to feel fancy:
package main
import "fmt"
func main() {
var z complex128 = 2 + 3i
fmt.Println(z) // (2+3i)
fmt.Println(real(z)) // 2
fmt.Println(imag(z)) // 3
}
complex64 stores two float32s;
complex128 stores two float64s. The built-in
functions complex(real, imag), real(), and
imag() construct and decompose them.
You probably won't reach for these in typical application code. They're included here for completeness.
Booleans
bool is either true or false. No
truthy/falsy values, 0 is not false, "" is
not false. Only a bool works in a condition:
package main
import "fmt"
func main() {
isActive := true
isExpired := false
// Only bool-valued expressions are allowed in if/for conditions.
if isActive && !isExpired {
fmt.Println("account is active")
}
}
This strictness is deliberate, it prevents whole categories of bugs
(e.g. accidentally treating 0 as "no value"). Chapter 8
covers the &&, ||, and
! operators in detail.
Strings (quick intro)
A string in Go is an immutable sequence
of bytes, typically UTF-8 encoded text. You can't modify individual
bytes; to "change" a string you build a new one.
package main
import "fmt"
func main() {
first := "John"
last := "Doe"
full := first + " " + last // concatenation with +
fmt.Println(full) // John Doe
fmt.Println(len(full)) // 8 (bytes, not characters)
}
There's a LOT more to strings, indexing returns a byte, ranging gives you runes, and UTF-8 encoding has subtleties that trip up nearly everyone. All of that is the entire next chapter.
Type conversions
Go does no implicit numeric conversions. This is unusual if you're coming from C or JavaScript, even "compatible" numeric types need an explicit cast:
package main
import "fmt"
func main() {
var a int = 42
var b float64 = float64(a) // int → float64
var c int32 = int32(a) // int → int32 (may truncate)
var d int = int(3.9) // float → int (truncates toward zero: 3)
fmt.Println(a, b, c, d)
}
The syntax is T(x): the destination type, then the value
in parentheses. Like calling a constructor.
int32(1_000_000_000_000) silently discards the high
bits. If you're going from a wider type to a narrower one, check
bounds yourself.
Converting to/from strings is a whole different kind of operation.
You'll use the strconv package:
package main
import (
"fmt"
"strconv"
)
func main() {
s := strconv.Itoa(42) // int → string: "42"
fmt.Println(s)
n, err := strconv.Atoi("42") // string → int
if err != nil {
fmt.Println("parse failed:", err)
return
}
fmt.Println(n + 1) // 43
}
Don't confuse string(65) with strconv.Itoa(65)!
The first converts a code point to a rune-string (it produces
"A"), which is almost never what you want. Use
strconv for number ↔ string conversions.
Summary table
| Category | Types | Default | Notes |
|---|---|---|---|
| Signed int | int8, int16, int32, int64, int | int | Silent wrap-around on overflow |
| Unsigned int | uint8…uint64, uint, uintptr | uint | byte = uint8 |
| Float | float32, float64 | float64 | IEEE 754; don't use for money |
| Complex | complex64, complex128 | complex128 | Rarely used |
| Bool | bool | bool | No truthy/falsy |
| String | string | string | Immutable, typically UTF-8 |
| Alias | rune = int32 | n/a | A Unicode code point |
Check your understanding
Practice exercises
Overflow playground
Write a program that demonstrates overflow for int8,
uint8, and int32. For each type, start
at its maximum value and add 1. Print the result.
Bonus: try starting at the minimum value and subtracting 1.
Show a working solution
package main
import (
"fmt"
"math"
)
func main() {
var a int8 = math.MaxInt8
var b uint8 = math.MaxUint8
var c int32 = math.MaxInt32
fmt.Printf("int8 %d + 1 = %d\n", a, a+1)
fmt.Printf("uint8 %d + 1 = %d\n", b, b+1)
fmt.Printf("int32 %d + 1 = %d\n", c, c+1)
var d int8 = math.MinInt8
fmt.Printf("int8 %d - 1 = %d\n", d, d-1) // wraps to 127
}
Safe string → int
Write a program that reads the program's first command-line
argument, converts it to an int, doubles it, and
prints the result. If the argument is missing or not a number,
print a friendly error message and exit with code 1.
Show one possible solution
package main
import (
"fmt"
"os"
"strconv"
)
func main() {
if len(os.Args) < 2 {
fmt.Fprintln(os.Stderr, "usage: double N")
os.Exit(1)
}
n, err := strconv.Atoi(os.Args[1])
if err != nil {
fmt.Fprintf(os.Stderr, "not a valid integer: %q\n", os.Args[1])
os.Exit(1)
}
fmt.Println(n * 2)
}
Further reading
- Go spec, Types
- Go spec, Numeric types
- strconv package
- 0.30000000000000004.com: why floating-point math is imprecise.
Types squared away.