Skip to content
Codeloom
Go

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.

·10 min read · By Codeloom
Beginner 10 min read

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

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

TypeSizeRange
int32 or 64 bitsplatform-dependent
int88 bits-128 to 127
int1616 bits~ ±32k
int3232 bits~ ±2 billion
int6464 bits~ ±9 quintillion
uint32 or 64 bitsunsigned, platform-dependent
uint88 bits0 to 255 (also called byte)
uint3232 bits0 to ~4 billion
uint6464 bits0 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

  • complex64 and complex128 exist for complex numbers. You will likely never use them.
  • uintptr exists 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:

  • byte is an alias for uint8. Used when you’re working with raw bytes — files, network buffers, encodings.
  • rune is an alias for int32. 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:

  • 0 for all numeric types
  • "" for strings
  • false for booleans
  • nil for 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: i for a loop index, r for a Reader, s for a string. Go style explicitly prefers i over index inside a loop.
  • Acronyms stay uppercase: URL, HTTP, ID. So a setter is SetURL, not SetUrl.

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:

  • var is the long form; := is the short form, available only inside functions.
  • Default to int, float64, string, and bool unless you have a specific reason for something else.
  • Strings are immutable byte sequences; iterate with range to 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.
  • const declares compile-time values; iota makes 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.

→ Next: Functions in Go

Questions or feedback? Email codeloomdevv@gmail.com.