Skip to content

Variables, Constants & Data Types Beginner

Go is statically typed with type inference -- you get the safety of explicit types with the brevity of dynamic languages. Understanding Go's type system, declaration styles, and zero values is foundational to everything else.


Variable Declaration

Go offers two declaration styles. Knowing when to use each is an interview staple.

var keyword (explicit declaration)

var name string              // zero value: ""
var age int = 30             // explicit type + value
var ratio float64 = 0.85     // explicit type + value
var active bool              // zero value: false

// Block declaration
var (
    host    string = "localhost"
    port    int    = 8080
    verbose bool
)

Short declaration := (inside functions only)

func main() {
    name := "Go"           // type inferred as string
    count := 42            // type inferred as int
    rate := 3.14           // type inferred as float64
    done := false          // type inferred as bool

    // Multiple assignment
    x, y, z := 1, 2.5, "three"
}

var vs := -- When to Use Which

Feature var :=
Package level Yes No
Inside functions Yes Yes
Type inference Optional Always
Zero value init Yes (var x int) No (must assign)
Redeclaration in multi-assign No Yes (at least one new)
// := allows redeclaration when at least one variable is new
val, err := strconv.Atoi("42")
val2, err := strconv.Atoi("99") // err is redeclared, val2 is new -- OK

:= is NOT available at package level

package main

name := "Go" // COMPILE ERROR: non-declaration statement outside function body

Zero Values

Every type in Go has a zero value -- the default when no value is assigned. There is no undefined or null surprise.

Type Zero Value Example
bool false var b boolfalse
int, int8...int64 0 var n int0
uint, uint8...uint64 0 var u uint0
float32, float64 0.0 var f float640.0
complex64, complex128 (0+0i) var c complex128(0+0i)
string "" (empty) var s string""
byte (alias uint8) 0 var b byte0
rune (alias int32) 0 var r rune0
Pointer nil var p *intnil
Slice nil var s []intnil
Map nil var m map[string]intnil
Channel nil var ch chan intnil
Interface nil var i interface{}nil
Function nil var f func()nil
Struct All fields zeroed var t time.Time0001-01-01 00:00:00

Interview Tip

"In Go, every variable is usable at its zero value. A sync.Mutex works without initialization, bytes.Buffer is ready to write to, and slices can be appended to even when nil. This is a deliberate design choice that eliminates an entire class of constructor-related bugs."


Basic Data Types

Numeric Types

Type Size Range
int8 1 byte -128 to 127
int16 2 bytes -32,768 to 32,767
int32 4 bytes -2.1B to 2.1B
int64 8 bytes ±9.2 × 10¹⁸
int Platform-dependent 32 or 64 bit
uint8 / byte 1 byte 0 to 255
uint16 2 bytes 0 to 65,535
uint32 4 bytes 0 to 4.2B
uint64 8 bytes 0 to 18.4 × 10¹⁸
uint Platform-dependent 32 or 64 bit
uintptr Platform-dependent Holds a pointer value
float32 4 bytes ~7 decimal digits precision
float64 8 bytes ~15 decimal digits precision
complex64 8 bytes float32 real + imag
complex128 16 bytes float64 real + imag
var (
    counter   int     = 1_000_000   // underscores for readability (Go 1.13+)
    price     float64 = 19.99
    initial   byte    = 'A'         // byte is uint8
    codepoint rune    = '世'         // rune is int32
    mask      uint8   = 0xFF
    bigNum    int64   = 1 << 40
)

String and Boolean

var (
    greeting string = "Hello, Go"
    raw      string = `Line 1\nStill line 1` // raw string literal, no escape processing
    flag     bool   = true
)

// Strings are immutable byte slices
s := "hello"
// s[0] = 'H'  // COMPILE ERROR: cannot assign to s[0]

Constants

Constants are compile-time values. They cannot be declared with :=.

const pi = 3.14159
const maxRetries int = 3

const (
    appName    = "myservice"
    appVersion = "1.2.0"
)

Untyped Constants

Go constants can be untyped, giving them higher precision and flexibility.

const x = 1_000_000_000_000_000 // untyped -- works with any numeric type
var a int32 = x                  // OK if x fits in int32 range -- this won't, compile error
var b int64 = x                  // OK
var c float64 = x                // OK

iota -- Enumeration Generator

iota starts at 0 and increments by 1 for each constant in a const block.

type Weekday int

const (
    Sunday    Weekday = iota // 0
    Monday                   // 1
    Tuesday                  // 2
    Wednesday                // 3
    Thursday                 // 4
    Friday                   // 5
    Saturday                 // 6
)

Common iota Patterns

// Skip zero value (useful to detect unset)
type Color int

const (
    _    Color = iota // skip 0
    Red               // 1
    Green             // 2
    Blue              // 3
)

// Bit flags
type Permission uint8

const (
    Read    Permission = 1 << iota // 1
    Write                          // 2
    Execute                        // 4
)

func hasPermission(p, flag Permission) bool {
    return p&flag != 0
}

// Size units
const (
    _  = iota
    KB = 1 << (10 * iota) // 1024
    MB                     // 1,048,576
    GB                     // 1,073,741,824
    TB                     // 1,099,511,627,776
)

Interview Tip

"Skip iota value 0 when the zero value would be ambiguous. For example, if you have a Status enum, an uninitialized variable would silently become the first status. Using _ = iota for 0 forces explicit assignment."


Type Conversions

Go has no implicit type conversions. Every conversion must be explicit.

var i int = 42
var f float64 = float64(i)     // int → float64
var u uint = uint(f)           // float64 → uint

// String conversions
s := string(65)                // "A" (Unicode codepoint)
n, err := strconv.Atoi("42")  // string → int (with error check)
s2 := strconv.Itoa(42)        // int → string
s3 := fmt.Sprintf("%d", 42)   // int → string via formatting

// Between integer types -- be careful of overflow
var big int64 = 1<<40
var small int32 = int32(big)   // SILENT OVERFLOW -- no runtime error

No Implicit Conversions

var x int32 = 10
var y int64 = 20
// sum := x + y    // COMPILE ERROR: mismatched types
sum := int64(x) + y // explicit conversion required

Type Aliases vs Custom Types

// Type alias -- same type, new name (Go 1.9+)
type NodeID = int64   // NodeID IS int64, fully interchangeable

// Custom type -- new distinct type
type UserID int64     // UserID is NOT int64, needs conversion

var n NodeID = 42
var i int64 = n       // OK -- alias is the same type

var u UserID = 42
// var j int64 = u    // COMPILE ERROR -- different types
var j int64 = int64(u) // explicit conversion required

Custom types can have methods attached; aliases cannot.

type Celsius float64

func (c Celsius) ToFahrenheit() float64 {
    return float64(c)*9/5 + 32
}

temp := Celsius(100)
fmt.Println(temp.ToFahrenheit()) // 212

Quick Reference

Operation Syntax Notes
Declare with zero value var x int Package or function level
Declare with value var x int = 5 Explicit type
Short declare x := 5 Function level only, type inferred
Multiple declare var a, b int Same type
Multiple short a, b := 1, "hi" Different types OK
Constant const x = 5 Compile-time, untyped by default
Typed constant const x int = 5 Fixed type
Enum with iota const ( A = iota; B; C ) 0, 1, 2
Type conversion float64(x) Always explicit
Custom type type Age int Distinct type, can have methods
Type alias type ID = int64 Same type, new name

Best Practices

  1. Prefer := inside functions -- it's idiomatic and concise.
  2. Use var for zero-value initialization -- var buf bytes.Buffer is clearer than buf := bytes.Buffer{}.
  3. Use var at package level -- := isn't allowed there anyway.
  4. Group related var and const declarations in blocks for readability.
  5. Prefer int unless you need a specific size -- it matches the platform word size and avoids unnecessary conversions.
  6. Use custom types for domain concepts -- type UserID int64 prevents accidentally mixing user IDs with order IDs.
  7. Use _ to skip iota value 0 when the zero value would be ambiguous.

Common Pitfalls

Silent integer overflow

Go does not panic on integer overflow. Values wrap around silently.

var x uint8 = 255
x++
fmt.Println(x) // 0, not 256

Short declaration shadowing

:= inside an if or for block creates a new variable that shadows the outer one.

x := 10
if true {
    x := 20  // new variable, shadows outer x
    fmt.Println(x) // 20
}
fmt.Println(x) // still 10

Untyped constant overflow

Untyped constants have arbitrary precision, but overflow is checked at assignment.

const huge = 1 << 100        // OK as untyped constant
// var x int64 = huge         // COMPILE ERROR: overflows int64
var y float64 = huge          // OK: float64 can represent it (with precision loss)

nil map write panic

A zero-value map is nil. Reading returns the zero value, but writing panics.

var m map[string]int
_ = m["key"]      // OK, returns 0
m["key"] = 1      // PANIC: assignment to entry in nil map


Performance Considerations

  • int vs sized integers: int matches the CPU word size, which is fastest for arithmetic. Use int32/int64 only when interacting with external formats or saving memory in large slices.
  • float64 vs float32: Prefer float64 -- it's the default for numeric literals and most math functions. float32 only saves memory in large datasets.
  • String concatenation: Strings are immutable. Repeated += creates O(n²) allocations. Use strings.Builder for building strings in loops.
  • Small structs vs pointers: For structs ≤ 3-4 fields of basic types, pass by value to avoid heap allocation. The copy is cheaper than the GC overhead.

Key Takeaways

  1. Go has two declaration styles: var (anywhere) and := (functions only).
  2. Every type has a usable zero value -- no uninitialized variable bugs.
  3. There are no implicit type conversions -- all conversions are explicit.
  4. iota generates sequential constants; skip 0 when the zero value is ambiguous.
  5. Custom types (type X int) create distinct types with method capability; aliases (type X = int) are interchangeable with the original.
  6. Integer overflow is silent -- no runtime panic or error.