Go Fundamentals - Variables, Types & Basic Syntax

The Bug That Taught Me Go's Type System

Three days into rewriting our API in Go, I hit a bug that made me question everything.

func calculateDiscount(price float64, discount int) float64 {
    return price - (price * discount / 100)
}

result := calculateDiscount(100.50, 15)
fmt.Printf("Final price: $%.2f\n", result)
// Expected: Final price: $85.43
// Got:      Final price: $100.50

The discount wasn't applying. I stared at this for 20 minutes before it hit me: integer division.

In Python, 15 / 100 gives 0.15. In Go, 15 / 100 gives 0 (integer division). The fix was simple once I understood Go's type system:

return price - (price * float64(discount) / 100)
// Now: Final price: $85.43

This moment taught me that Go's explicit type system isn't a burden - it's a feature. The compiler caught what would have been a subtle production bug. This article covers everything I learned about Go's type system the hard way.


Variable Declarations: Three Ways

Go has three ways to declare variables. Coming from Python's single name = value syntax, this confused me initially. But each serves a purpose.

1. The var Declaration

Key insight: Go initializes all variables to their zero value. No undefined, no null, no surprise NullPointerException.

Zero values by type:

  • Numeric types: 0

  • Boolean: false

  • String: "" (empty string)

  • Pointers, slices, maps, channels, functions, interfaces: nil

2. Declaration with Initialization

But Go infers types, so this is idiomatic:

3. Short Declaration := (Most Common)

This is the idiomatic Go way for local variables. Short, clean, type-inferred.

When to use which?

Multiple Declarations


Basic Types

Numeric Types

Go has 11 numeric types. This seemed excessive coming from Python's simple int and float.

Real-world lesson: I started using int32 everywhere because our database IDs were 32-bit integers. Then I hit this:

Solution: Use int by default. Convert only when interfacing with external systems:

String Type

UTF-8 gotcha I learned the hard way:

For Unicode-aware operations, use the unicode/utf8 package:

Boolean Type


Type Conversions (No Implicit Conversions!)

This is where Go differs dramatically from Python or JavaScript:

Real example from my code:

Common Conversions


Constants

Constants are declared with const and must be compile-time values:

Untyped Constants (Powerful Feature)

This is useful in APIs:

The iota Enumerator

iota creates incrementing constants - perfect for enumerations:

Real-world usage from my API:


Type Inference and Type Aliases

Type Inference

Type Aliases

Why use type aliases? They add semantic meaning:


Zero Values: A Safety Feature

Every variable has a zero value. This prevents undefined behavior:

Real bug I caught with zero values:

The bug was subtle: the code ran but had wrong logic. Zero values are safe but require thinking about default states.


Practical Example: Building a Config Parser

Here's real code from my first Go project - parsing environment config:

Usage:


Common Mistakes I Made

1. Unused Variables

Solution: Go forces you to use or remove variables. Use _ to explicitly ignore:

2. Short Declaration Scope

Variables declared in if are scoped to that block.

3. Shadowing Variables

This caused a bug in my code. The fix: don't redeclare, just assign:

4. Integer Division Surprise


Your Challenge

Build a simple calculator that handles different numeric types:

Try handling type conversions, return types, and error cases (like division by zero).


Key Takeaways

  1. Three declaration styles: var, var =, and := - use := for local variables

  2. Zero values: Every variable is initialized - no undefined behavior

  3. No implicit conversions: Explicit is better than implicit

  4. Type aliases: Add semantic meaning to primitive types

  5. Constants and iota: Powerful enumeration support

  6. Go is strict: Unused variables, type mismatches are compile errors - catches bugs early


What I Learned

Coming from Python, Go's type system felt restrictive. But after shipping production code:

  • Explicit conversions prevented the discount calculation bug

  • Zero values eliminated null/undefined checks

  • Type safety caught integration bugs at compile time

  • Unused variable errors kept code clean

The type system isn't fighting you - it's catching bugs before they reach production.


Next: Control Structures

In the next article, we'll explore Go's control flow: if/else, switch, loops, and the powerful defer statement. You'll learn how Go's simpler approach to control structures actually makes code more readable.

Last updated