Variables and Basic Types in Go
A practical tour of Go's variable declarations and basic types — var vs :=, int and float64, strings, bool, byte and rune, zero values, type conversion, and constants.
What you'll learn
- ✓The two ways to declare a variable in Go and when to use each
- ✓The basic numeric, string, and boolean types
- ✓How byte and rune relate to strings
- ✓What zero values are and why Go has them
- ✓How explicit type conversion works (and why Go has no implicit conversion)
- ✓How constants differ from variables
Prerequisites
- •A working Go install — see Install Go and Write Your First Program
Variables are how a program remembers things. Go’s approach is deliberately strict: every variable has a type that is known at compile time, and the compiler will not silently convert between types for you. The strictness pays for itself the first time it catches a bug that would have slipped through in Python or JavaScript. This post covers the syntax, the basic types, and the conventions you’ll see in every Go codebase.
Declaring a variable with var
The long form uses the var keyword:
var age int = 25
var name string = "Alice"
var price float64 = 19.99
var active bool = true
Each line declares a variable, gives it a type, and assigns a value. You can omit the type if you assign a value — Go infers it:
var age = 25 // int
var name = "Alice" // string
var price = 19.99 // float64
var active = true // bool
You can also omit the value if you specify the type. Go assigns a zero value, which we’ll cover below:
var count int // 0
var label string // ""
var ready bool // false
For multiple declarations at once:
var (
host = "localhost"
port = 8080
timeout = 30
)
This grouped form is conventional for package-level variables and configuration.
Short variable declaration with :=
Inside functions, Go has a shorter form. The := operator declares a new variable and assigns to it in one step, with the type inferred from the right-hand side:
age := 25
name := "Alice"
price := 19.99
active := true
This is the form you’ll use most often. The rules are:
:=works only inside functions — not at package level.- The left-hand side must introduce at least one new variable.
- Once declared, you use plain
=to assign new values.
score := 10 // declare and initialise
score = 20 // reassign
// score := 30 // error: no new variables on left side
You can declare multiple variables on one line:
x, y := 1, 2
name, age := "Alice", 30
This is especially common with functions that return multiple values — see Functions in Go.
The basic numeric types
Go has separate types for integers and floating-point numbers, and several sizes of each.
Integers
| Type | Size | Range |
|---|---|---|
int | 32 or 64 bits | platform-dependent |
int8 | 8 bits | -128 to 127 |
int16 | 16 bits | ~ ±32k |
int32 | 32 bits | ~ ±2 billion |
int64 | 64 bits | ~ ±9 quintillion |
uint | 32 or 64 bits | unsigned, platform-dependent |
uint8 | 8 bits | 0 to 255 (also called byte) |
uint32 | 32 bits | 0 to ~4 billion |
uint64 | 64 bits | 0 to ~18 quintillion |
In practice you should reach for int by default. Use the sized variants only when you have a specific reason — interop with a binary protocol, memory pressure on a large array, or working with a hardware register.
Floating point
float32 and float64. Use float64 by default — it’s the default type for a literal like 3.14, and the standard library favours it.
pi := 3.14159 // float64
ratio := float32(0.5) // explicit float32
Other numeric types
complex64andcomplex128exist for complex numbers. You will likely never use them.uintptrexists for low-level pointer arithmetic. You will likely never use it.
Strings
A string is an immutable sequence of bytes. By convention strings hold UTF-8 text, but Go does not enforce this.
greeting := "Hello, world!"
emoji := "Hi 👋"
multi := `a raw string
spanning lines`
Double-quoted strings interpret escape sequences (\n, \t, \"). Backtick-quoted raw strings don’t — what you see is what you get, including newlines. Raw strings are perfect for regex patterns and short multi-line literals.
You can concatenate with +:
first := "Hello"
second := "world"
full := first + ", " + second + "!"
fmt.Println(full)
// output: Hello, world!
The length of a string in bytes is len(s):
fmt.Println(len("hello")) // 5
fmt.Println(len("héllo")) // 6 — é is two bytes in UTF-8
That distinction between bytes and characters matters in any language that supports Unicode. Go handles it through byte and rune.
byte and rune
These are aliases for two integer types, used to make intent clear:
byteis an alias foruint8. Used when you’re working with raw bytes — files, network buffers, encodings.runeis an alias forint32. Used when you’re working with Unicode code points — a single character of text.
var b byte = 'A' // 65
var r rune = '世' // 19990
fmt.Println(b, r)
// output: 65 19990
Iterating a string with range yields runes, not bytes — which is what you want for text processing:
for i, r := range "héllo" {
fmt.Printf("%d: %c\n", i, r)
}
// output:
// 0: h
// 1: é
// 3: l
// 4: l
// 5: o
Notice i skips from 1 to 3 — é occupies two bytes, but range advances by code point. We cover range in detail in Control Flow in Go.
bool
Booleans are true or false. No truthy strings, no zero-is-false. Conditions must be of type bool:
ready := true
fmt.Println(!ready) // false
fmt.Println(ready && false) // false
fmt.Println(ready || false) // true
Trying to use a non-bool in a condition is a compile error:
n := 5
// if n { ... } // error: non-bool n (type int) used as if condition
This is one of those small strictnesses that catches real bugs.
Zero values
If you declare a variable without assigning to it, Go gives it the zero value for its type:
var (
n int
f float64
s string
b bool
)
fmt.Println(n, f, s, b)
// output: 0 0 false
The zero values are:
0for all numeric types""for stringsfalsefor booleansnilfor pointers, slices, maps, channels, functions, and interfaces
This matters in Go more than in most languages because Go has no concept of undefined or a “null pointer exception” at the variable level. A freshly declared variable is always usable. You will rely on this constantly — for example, accumulating into a var total int without remembering to set it to zero first.
Type conversion (no implicit casts)
Go does not convert between numeric types implicitly. This is illegal:
var i int = 42
var f float64 = i // error: cannot use i (type int) as type float64
You must convert explicitly:
var i int = 42
var f float64 = float64(i)
var u uint = uint(f)
The syntax is T(x) — the destination type wrapped around the value. The same applies to strings and bytes:
n := 65
s := string(n) // "A" — converts rune to its UTF-8 string
b := []byte("hello") // [104 101 108 108 111]
back := string(b) // "hello"
This explicitness is one of Go’s hallmark choices. It means you never lose precision by accident, and a numeric type appearing somewhere implies intent.
Constants
A const is a value fixed at compile time. The syntax mirrors var:
const Pi = 3.14159
const MaxRetries = 5
const Greeting = "Hello"
Constants in Go have a clever twist: an untyped constant takes on the type required by its context. So this works:
const Limit = 100
var i int = Limit
var f float64 = Limit
// both compile, even though Limit "is" neither int nor float64
You can group constants like variables:
const (
StatusOK = 200
StatusNotFound = 404
StatusError = 500
)
For sequences of related constants, Go has iota — an auto-incrementing counter that resets at the start of each const block:
const (
Sunday = iota // 0
Monday // 1
Tuesday // 2
Wednesday // 3
Thursday // 4
Friday // 5
Saturday // 6
)
iota is the standard way to define enum-like sets in Go. We’ll see it again later in the series.
Try this. Create a file units.go with package main. Declare constants MetresPerKm = 1000 and SecondsPerHour = 3600. Write a main function that converts 90.0 (km/h) into metres per second and prints it with two decimal places using fmt.Printf("%.2f\n", value). You’ll need explicit conversion between float64 and the integer constants — though the constants being untyped should make it smoother than you expect.
Naming conventions
Go’s naming rules are unusually load-bearing because of how visibility works.
- camelCase for local variables and unexported names:
userName,httpClient,lastIndex. - PascalCase for exported names — anything starting with a capital letter is visible from outside its package:
ServeHTTP,MaxRetries,Println. - Short names in short scopes are idiomatic:
ifor a loop index,rfor aReader,sfor astring. Go style explicitly prefersioverindexinside a loop. - Acronyms stay uppercase:
URL,HTTP,ID. So a setter isSetURL, notSetUrl.
These aren’t suggestions — the visibility rule is part of the language. Renaming total to Total makes it part of your package’s public API.
A worked example
A small program tying the basic types together:
package main
import "fmt"
const PricePerUnit = 4.99
func main() {
var name string = "Widget"
quantity := 12
inStock := true
total := float64(quantity) * PricePerUnit
fmt.Printf("%-10s | qty: %3d | in stock: %t | total: $%.2f\n",
name, quantity, inStock, total)
}
// output: Widget | qty: 12 | in stock: true | total: $59.88
Every type is doing something visible — the explicit float64(quantity) turns the integer into a float so the multiplication is well-defined, and the Printf verbs show off %-10s (left-aligned string), %3d (right-aligned integer), %t (bool), and %.2f (two-decimal float).
Recap
You now know:
varis the long form;:=is the short form, available only inside functions.- Default to
int,float64,string, andboolunless you have a specific reason for something else. - Strings are immutable byte sequences; iterate with
rangeto get runes. - Every type has a zero value, and a freshly declared variable is always usable.
- Go has no implicit type conversion — use
T(x)to convert explicitly. constdeclares compile-time values;iotamakes enum-like sequences.
Next steps
The next post covers Go’s functions — declaration syntax, multiple return values, named returns, the error convention, variadic parameters, and functions as values.
Questions or feedback? Email codeloomdevv@gmail.com.
Related articles
- Go Control Flow in Go: if, for, switch
Go's control flow in full — if with an init statement, the only loop (for) in all four forms, switch without break, type switches, and how defer fits in.
- Go Functions in Go: Declarations, Multiple Returns, and Errors
A complete guide to Go functions — declarations, parameters, multiple and named returns, the error idiom that defines Go, variadic parameters, and functions as values.
- Go Install Go and Write Your First Program
A complete setup guide for Go on macOS, Linux, and Windows — install the toolchain, verify with go version, create a module, and run a real program with go run and go build.
- Go Slices and Maps in Go
A practical guide to Go's two everyday collections — slices and maps. Covers arrays vs slices, make and append, len and cap, the shared-backing-array gotcha, and the comma-ok idiom.