Basic Data Types in Go
An overview of Go's built-in data types — booleans, numeric types, strings, and architecture-dependent integers — with practical examples, memory fundamentals, and common pitfalls every beginner should know.
Go programs are built from data. Every variable, every field, every expression result carries a value, and that value belongs to a specific type. The type determines what you can do with the data, how much memory it uses, and what happens when you combine it with other values.
The basic data types covered here are the foundation for everything else: slices, maps, structs, and interfaces are built on top of them. Understanding these types — how they behave, how they interact, and where beginners get tripped up — makes the rest of the language easier to learn.
What a Data Type Tells the Compiler
A data type is a classification that answers three questions:
- What kind of value is stored — a truth value, a whole number, a decimal number, a sequence of characters.
- How much memory the value occupies — an 8-bit integer uses one byte, a 64-bit floating-point number uses eight bytes.
- What operations are valid — you can add two integers, but you cannot add an integer and a boolean.
Go is statically typed, meaning every variable has a fixed type at compile time. The compiler refuses to build a program where you try to store a string in an int variable, or add a bool to a float64. This strictness catches entire categories of bugs before the program ever runs.
A Beginner Mental Model:
Think of a type as a shape of box. An int box only accepts whole numbers; a string box only accepts text. You cannot pour a string into an int-shaped box without first converting it into a number — a deliberate, explicit step. Go forces you to be honest about what shape your data has.
The Four Categories of Basic Types
Go's basic data types fall into four groups, each described in more detail in its own dedicated section:
- Boolean — the
booltype, representingtrueorfalse - Numeric — signed and unsigned integers of various sizes, floating-point numbers, and complex numbers
- String — the
stringtype, a sequence of bytes that holds text - Architecture-Dependent —
int,uint, anduintptr, whose size changes with the platform
The following overview introduces each group with enough depth to start using them confidently. The separate pages under this section explore edge cases, internal representation, and advanced operations.
Boolean Type
A bool holds one of two values: true or false. It is the simplest data type and the backbone of all conditional logic and comparisons.
var isReady bool = true
completed := false
fmt.Println(isReady) // true
Boolean values result from comparison operators (==, !=, \u003c, \u003e, \u003c=, \u003e=) and work with logical operators (&&, ||, !). The expression 5 > 3 evaluates to the boolean true, and you can combine such results: (age > 18) && (country == "IN").
A common pitfall in languages that treat true as 1 and false as 0 does not exist in Go. A bool is not a number, and there is no implicit conversion between the two. This prevents a whole class of subtle bugs where a truth value accidentally participates in arithmetic.
Booleans Are Not Integers:
Code like var flag bool = 1 or var n int = true will not compile. Go demands an explicit bool literal (true or false) for boolean variables and a conversion function (which does not exist in the standard library) if you ever need to bridge the gap — a deliberate design choice.
Numeric Types
Go provides a rich set of numeric types that cover whole numbers, fractional numbers, and even complex numbers. The variety gives you precise control over memory and value range, but it also means you must choose the right type for the job.
Integers
Integers come in two families: signed (can represent negative values) and unsigned (only zero and positive). Each family offers four sizes: 8, 16, 32, and 64 bits, plus the architecture-dependent int and uint.
| Signed | Unsigned | Size (bits) | Range (signed) | Range (unsigned) |
|---|---|---|---|---|
| int8 | uint8 | 8 | -128 to 127 | 0 to 255 |
| int16 | uint16 | 16 | -32,768 to 32,767 | 0 to 65,535 |
| int32 | uint32 | 32 | -2,147,483,648 to 2,147,483,647 | 0 to 4,294,967,295 |
| int64 | uint64 | 64 | ~ -9.2e18 to 9.2e18 | 0 to ~1.8e19 |
Two integer aliases appear frequently:
byteisuint8— used for raw byte dataruneisint32— used for Unicode code points
Most of the time, you should reach for plain int. It adapts to the word size of the machine (32 or 64 bits) and works for the overwhelming majority of everyday values like counters, indices, and small arithmetic. Choose a specific size only when dealing with binary protocols, file formats, or tight memory constraints.
var count int = 142
var small int8 = 100
fmt.Printf("count type: %T, small type: %T\n", count, small)
// Output: count type: int, small type: int8
The Printf format verb %T prints the type — a handy tool when you are unsure what the compiler inferred.
Signed Overflow Is Silent:
Go does not panic on integer overflow. If you assign 300 to an int8 variable, the compiler stops you. But if you increment a value past its range at runtime, the bits wrap around without warning. Be mindful of ranges when using small integer types inside loops or counters.
Floating-Point Numbers
float32 and float64 follow the IEEE 754 standard. float64 is the default type for floating-point literals and the safe choice unless you have a specific reason to save memory.
pi := 3.14159
var radius float32 = 2.5
fmt.Printf("pi type: %T, radius type: %T\n", pi, radius)
// Output: pi type: float64, radius type: float32
Floating-point arithmetic is an approximation. Values like 0.1 cannot be represented exactly in binary, so 0.1 + 0.2 may not equal exactly 0.3. Use floating-point for measurements, graphics, and scientific computation — not for money. For exact decimal representation, turn to integer pennies or the math/big package.
Avoid Float for Currency:
Represent monetary amounts as an integer number of cents (or the smallest unit). Using float64 for money leads to rounding errors that accumulate in ledgers and financial calculations.
Complex Numbers
Go has built-in support for complex64 (float32 real and imaginary parts) and complex128 (float64 parts). They are useful for signal processing, physics simulations, and certain mathematical domains.
c1 := complex(2, 3) // 2 + 3i, type complex128
c2 := 5 + 1i
sum := c1 + c2
fmt.Println("Sum:", sum) // (7+4i)
The built-in functions real() and imag() extract the components. For most beginner programs, you will not need complex numbers, but their presence in the language means you can write numeric code across all standard numeric types without third-party libraries.
String Type
A string is a read-only sequence of bytes. It can contain any byte value — UTF-8 encoded text, binary data, or even null bytes. Go source code treats string literals as UTF-8, so you can write and print emoji, accented characters, and symbols directly.
greeting := "Hello, 世界"
raw := `A raw string
spanning multiple lines.
No escape sequences needed.`
fmt.Println(greeting)
fmt.Println(len(greeting)) // 13 bytes (not 9 characters)
Strings are immutable: once created, you cannot change individual bytes. Operations like greeting[0] = 'h' will not compile. Instead, you build new strings using concatenation (+), fmt.Sprintf, or the strings package.
The len function returns the number of bytes, not the number of characters. A single Unicode character (rune) can occupy multiple bytes in UTF-8, so iterating over a string byte-by-byte gives a different picture than iterating rune-by-rune using for range.
Strings Are Not Slices:
Although strings feel similar to byte slices ([]byte), they are a distinct type with no mutable state. To modify a string, convert to a []byte or []rune, make changes, and convert back. This copy-then-modify pattern protects against unintended data sharing.
Architecture-Dependent Types
int, uint, and uintptr are sized to the native word size of the CPU the code is compiled for. On a 64-bit machine, they are 64 bits (8 bytes); on a 32-bit machine, they are 32 bits (4 bytes).
This design ties Go's most common integer type to the hardware, which generally gives the best performance for arithmetic and loop counters. But it also means the same code can behave differently on different machines if you make assumptions about the range of int.
var x int = 1\u003c\u003c62 // Safe on 64-bit; overflows on 32-bit
fmt.Println(x)
Do Not Hardcode int Size:
If your logic depends on an exact 64-bit width, use int64. Writing var n int = 1\u003c\u003c50 will compile on a 64-bit system but panic (or silently overflow) when compiled for a 32-bit target. Explicitly sized types eliminate this risk.
The general advice from the Go team: use int unless you have a documented reason for a sized type (binary protocol, cross-platform file format, tight memory control). This keeps your code idiomatic and portable across the architectures Go supports.
uintptr in Practice
uintptr is an unsigned integer large enough to hold a memory address. You will rarely use it directly unless interacting with the unsafe package or low-level system calls. Most pointer manipulation stays safely within Go's type-safe pointer rules.
Common Mistakes That Compile But Misbehave
- Mixing
intanduintwithout conversion — Go requires explicit conversion. The compiler stops you outright, which is a safety feature. Still, programmers new to the language often writevar u uint = -1and wonder why it fails: unsigned types cannot represent negative values. - Assuming
len("hello")returns character count — it returns bytes. For the number of runes, useutf8.RuneCountInString. - Comparing floating-point values with
==— due to approximation,a == bmay fail for values that are mathematically equal. Compare within a small tolerance instead. - Relying on
intbeing 64 bits — code that shifts or stores values beyond 2^31-1 must useint64if 32-bit compatibility matters.
You Are on the Right Track If...:
Your code compiles cleanly, uses int for everyday integers and float64 for decimals, treats strings as immutable byte sequences, and converts types explicitly when moving between numeric categories. These habits make Go programs predictable and easy to debug.