Go Variables, Types & Constants: Complete Guide

Go Variables, Types & Constants: Complete Guide
In our previous modules, we established the Go environment and wrote a basic "Hello World" application. Now, we dive into the core building blocks of any programming language: how to store and manage data.
Go Variables and Types at a Glance
In Go, you declare variables with the var keyword or the shorthand := operator. The compiler infers the type from the assigned value, but the type is fixed at declaration — you cannot assign a string to an integer variable. Every declared variable is automatically initialized to its zero value, eliminating undefined behaviour and null-pointer bugs common in other languages.
While Go can often infer what type a variable should be, the compiler is incredibly strict once a type is set. You cannot change a variable's type after declaration, and you cannot perform operations between different types (like adding an integer to a float) without explicit conversion.
1. The Memory Mirror: Machine Word Alignment
When you declare a variable in Go, you aren't just giving a name to a value—you are claiming a specific amount of Silicon Real Estate.
The Word Mirror
- Fixed Bit-Depth: Types like
int32anduint64are "Hardware-Mirrors." Anint32always occupies exactly 4 bytes (32 bits), mapping directly to a single CPU register instruction. - Alignment Physics: Go's compiler automatically aligns variables to "Word Boundaries" (e.g., 8 bytes on a 64-bit CPU). This prevents "Torn Reads" where the CPU would have to perform two memory fetches to see one piece of data.
- The Result: By choosing specific integer sizes, you can architect data structures that fit perfectly into the L1 Cache, drastically reducing latency in high-throughput systems.
2. Declaring Variables
There are two primary ways to declare variables in Go. Both are used extensively, but they serve different purposes depending on the context.
1. The var Keyword
The var keyword is the formal way to declare a variable. It is most commonly used for package-level variables or when you want to declare a variable without immediately assigning it a value.
package main
import "fmt"
// Package-level declaration
var globalVar string = "I am visible everywhere in this package"
func main() {
// Function-level declaration
var score int = 100
fmt.Println(score)
}2. The Short Declaration Operator (:=)
For declaring and initializing variables inside functions, Go provides the short declaration operator. This is the most common way to create variables in Go.
func main() {
// The compiler infers that 'name' is a string and 'age' is an int
name := "TopicTrick"
age := 5
fmt.Printf("User: %s (Age: %d)\n", name, age)
}| Task / Feature | var Keyword | Short Declaration (:=) |
|---|---|---|
| No comparison data available | ||
Fundamental Data Types
Go provides a robust set of built-in types. Because Go is designed for systems programming, it gives you granular control over memory by offering different sizes for integers and floats.
Go Basic Types
In many languages, uninitialized variables contain "garbage" data or are null. In Go, every variable is initialized to a predictable Zero Value if no initial value is provided.
The Zero-Value Mirror
- Neutralization: By clearing memory to zero upon allocation, Go eliminates "Memory Entropy"—the random state left behind by previous programs.
- No Nulls for Basics: Unlike Java where an
Integercan be null, a Gointis always 0. This eliminates the #1 cause of crashes in large-scale software. - Consistency:
int: 0float: 0.0bool: falsestring: "" (empty string)pointers/interfaces: nil
4. Working with Constants
Constants are values that are known at compile time and cannot be changed during the execution of the program. They are declared using the const keyword.
const Pi = 3.14159
const (
StatusActive = 1
StatusInactive = 0
)Constants in Go are "untyped" until they are used in a context that requires a specific type, allowing for high flexibility without sacrificing safety.
Type Conversion
Go never automatically converts between types — you must be explicit. This prevents subtle bugs caused by implicit widening or narrowing in other languages.
func main() {
var x int = 42
var y float64 = float64(x) // Explicit conversion
var z int = int(y * 1.5) // Back to int — fractional part is truncated
fmt.Println(x, y, z) // 42 63.0 63
// String-to-byte-slice conversion
s := "Hello"
b := []byte(s) // Safe copy: modifying b does not affect s
b[0] = 'J'
fmt.Println(s, string(b)) // Hello Jello
}Enumerated Constants with iota
The iota identifier generates a sequence of incrementing integer constants within a const block — it is Go's idiomatic way to define enumerations.
type Direction int
const (
North Direction = iota // 0
East // 1
South // 2
West // 3
)
type ByteSize float64
const (
_ = iota // Skip zero
KB ByteSize = 1 << (10 * iota) // 1024
MB // 1048576
GB // 1073741824
)iota resets to zero at the start of each new const block, making it composable across multiple constant declarations.
Pointer Types
Go includes pointers, but unlike C, you cannot perform pointer arithmetic. Pointers are used to avoid copying large values and to allow functions to modify their arguments.
func increment(n *int) {
*n++ // Dereference to modify the original value
}
func main() {
count := 0
increment(&count) // Pass the address of count
fmt.Println(count) // 1
}Pointers become central when you define methods on structs, which is explored in the Go functions, pointers, and structs guide.
Type Aliases and Defined Types
Go distinguishes between type aliases (type Celsius = float64) and defined types (type Celsius float64). A defined type creates a new, distinct type that does not implicitly convert with its underlying type — enforcing semantic clarity.
type Celsius float64
type Fahrenheit float64
func toCelsius(f Fahrenheit) Celsius {
return Celsius((f - 32) * 5 / 9)
}
func main() {
var f Fahrenheit = 212
fmt.Println(toCelsius(f)) // 100
// This would NOT compile — type mismatch:
// var c Celsius = f
}This strict type separation prevents you from accidentally mixing units, currencies, or any semantically different values that share the same underlying representation.
Blank Identifier
The blank identifier _ discards values you are required to receive but do not need — common with multi-return functions.
result, _ := strconv.Atoi("42") // Discard the error (only acceptable if you are certain it won't fail)Use the blank identifier sparingly and always handle errors in production code. See the Effective Go section on the blank identifier for the full usage guidelines.
Phase 3: The Data-Structure Mastery Checklist
- Verify Byte Alignment: Use
unsafe.Alignofto see how the compiler is aligning your variables to the CPU word size. - Audit Zero-Value Dependency: Ensure that your data structures are designed to be "Useful" immediately upon declaration without a manual constructor.
- Implement Explicit Conversions: Identify every point in your code where an
intis converted to afloat64and evaluate the potential for precision loss. - Test Pointer Safety: Confirm that you are using pointers correctly to avoid unnecessary "Heap Escapes" during large struct transfers.
- Use Typed Constants: Replace "Magic Numbers" in your code with typed constants and
iotaenumerations for binary safety.
Read next: Go Slices & Maps: The Collection Mirror →
Next Steps
Understanding how to store data is the first major hurdle. However, programs aren't just static data; they need to make decisions and repeat tasks. In our next tutorial, we will explore Control Flow, including If/Else logic, Switch statements, and why Go only has one type of loop: the for loop.
Common Mistakes with Go Variables and Types
1. Shadowing variables with := inside blocks
Declaring x := 10 inside an if block creates a new variable that shadows the outer x. The outer variable is unchanged after the block exits. Use = (assignment) when you intend to modify an existing variable, and := only when declaring a new one.
2. Integer overflow without warning
Go does not panic on integer overflow — it wraps silently. If you are working with values that could exceed int32 or int64 bounds (e.g. financial calculations), use explicit bounds checks or the math/big package.
3. Comparing floating-point values with ==
0.1 + 0.2 == 0.3 evaluates to false in Go (and most languages) due to IEEE 754 floating-point representation. Use an epsilon comparison: math.Abs(a-b) < 1e-9. See the Go spec on numeric types for the full type hierarchy.
4. Using var when := is cleaner (and vice versa)
var is preferred at package level and when you need the zero value explicitly. := is idiomatic inside functions. Mixing them randomly reduces readability — pick the form that communicates intent.
5. Untyped constants surprising you at assignment
An untyped constant like const x = 1 takes on the type of whatever context it is used in. var y int8 = x works fine, but var y int8 = 200 overflows. Untyped constants have arbitrary precision until assigned.
Frequently Asked Questions
What is the zero value in Go?
Every variable in Go is automatically initialised to its zero value if not explicitly set: 0 for numeric types, false for booleans, "" for strings, and nil for pointers, slices, maps, channels, and interfaces. This eliminates a whole class of uninitialised-variable bugs. The Go spec on zero values documents this formally.
When should I use int vs int64?
Use int for general-purpose integer work — its size matches the platform (32-bit on 32-bit systems, 64-bit on 64-bit). Use int64 when you need a guaranteed 64-bit integer regardless of platform, such as when storing timestamps, file sizes, or working with external APIs that specify 64-bit integers.
Can I declare multiple variables on one line?
Yes. var a, b, c int = 1, 2, 3 and x, y := 10, 20 are both valid. The short form is idiomatic for return value unpacking: val, err := someFunc().
Continue Learning
Once you are comfortable with Go variables and types, the next step is Go Conditional Logic & Loops — covering if/else, switch, and the for loop (Go's only loop construct).
