> ## Documentation Index
> Fetch the complete documentation index at: https://resources.devweekends.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Variables and Types

> Understanding Go's type system, variables, constants, and zero values

# Variables and Types

Go is a statically typed language, meaning variable types are known at compile time. However, its syntax is designed to be concise, often inferring types for you.

Think of Go's type system like a strict but helpful librarian: it will not let you put a novel on the science shelf (type safety), but it also will not make you fill out a form every time you check out a book (type inference). You get safety without ceremony.

## Variable Declaration

There are three main ways to declare variables in Go.

### 1. `var` keyword (Explicit Type)

Use this when you want to declare a variable without initializing it immediately, or when you want to be explicit about the type.

```go theme={null}
var name string = "Gopher"
var age int = 10
```

### 2. `var` keyword (Type Inferred)

If you provide an initial value, you can omit the type.

```go theme={null}
var name = "Gopher" // inferred as string
var age = 10        // inferred as int
```

### 3. Short Declaration `:=`

Inside functions, you can use the `:=` operator. This is the most common and idiomatic way to declare and initialize variables in Go.

```go theme={null}
func main() {
    name := "Gopher"  // Type inferred as string
    age := 10         // Type inferred as int
    isCool := true    // Type inferred as bool
}
```

**Why `:=` is Preferred**: It's concise and lets the compiler infer types, making code cleaner while maintaining type safety.

<Warning>
  **Restriction**: The `:=` syntax can **only** be used inside functions. At the package level (outside functions), you must use `var`.

  **Common Gotcha**: `:=` declares a new variable. If you want to reassign an existing variable, use `=` instead:

  ```go theme={null}
  name := "Alice"  // Declares new variable
  name = "Bob"     // Reassigns existing variable
  name := "Charlie" // ERROR: no new variable on left side of :=
  ```
</Warning>

***

## Basic Types

Go has a rich set of built-in types.

### Integers

* `int`, `int8`, `int16`, `int32`, `int64`
* `uint`, `uint8`, `uint16`, `uint32`, `uint64`, `uintptr`

**Note**: `int` and `uint` are platform-dependent (32-bit on 32-bit systems, 64-bit on 64-bit systems). In 99% of cases, just use `int`. Only reach for specific-width types like `int32` or `int64` when you are dealing with serialization formats (protobuf, binary protocols), interacting with C code via cgo, or need to guarantee the bit width across platforms.

### Floats

* `float32`, `float64`

**Note**: `float64` is the default for floating-point numbers.

### Booleans

* `bool` (true or false)

### Strings

* `string` (immutable sequence of bytes, UTF-8 encoded)

### Complex Numbers

* `complex64`, `complex128`

***

## Zero Values

Variables declared without an initial value are given their **zero value**. This is a key safety feature: **Go does not have uninitialized variables**. Every variable always has a well-defined value.

| Type     | Zero Value          |
| -------- | ------------------- |
| `int`    | `0`                 |
| `float`  | `0.0`               |
| `bool`   | `false`             |
| `string` | `""` (empty string) |
| pointers | `nil`               |
| slices   | `nil`               |
| maps     | `nil`               |
| channels | `nil`               |

```go theme={null}
var i int
var s string
var p *int
fmt.Printf("%d %q %v\n", i, s, p) // Output: 0 "" <nil>
```

**Why This Matters**: Zero values make Go code more predictable and safer. You can rely on variables having sensible defaults:

```go theme={null}
var counter int  // Automatically 0, ready to use
counter++        // Now 1

var buffer bytes.Buffer  // Ready to use immediately, no initialization needed
buffer.WriteString("Hello")
```

***

## Constants

Constants are declared using `const`. They can be character, string, boolean, or numeric values.

```go theme={null}
const Pi = 3.14
const World = "世界"
```

Constants cannot be declared using the `:=` syntax.

### Iota

`iota` is a special identifier used to create enumerated constants. It resets to 0 whenever `const` appears and increments by 1 for each line.

```go theme={null}
const (
    Red = iota   // 0
    Green        // 1
    Blue         // 2
)
```

**Practical Use Cases**:

```go theme={null}
// File permissions (powers of 2 for bitwise operations)
const (
    Read = 1 << iota  // 1 << 0 = 1
    Write             // 1 << 1 = 2
    Execute           // 1 << 2 = 4
)

// Skip values
const (
    _ = iota  // Skip 0
    KB = 1 << (10 * iota)  // 1024
    MB                      // 1048576
    GB                      // 1073741824
)
```

***

## Type Conversion

Go requires explicit type conversion. There is no implicit casting (e.g., you cannot assign an `int` to a `float64` variable without casting). This is a deliberate design decision -- implicit conversions are a notorious source of subtle bugs in C and JavaScript.

```go theme={null}
var i int = 42
var f float64 = float64(i)  // Explicit: you see the conversion happening
var u uint = uint(f)         // Explicit: potential data loss is visible
```

If you try `f = i`, the compiler will throw an error.

<Warning>
  **Pitfall -- Numeric Overflow**: Go does not panic on integer overflow. Values silently wrap around. This can cause hard-to-find bugs:

  ```go theme={null}
  var x uint8 = 255
  x++                 // x is now 0, not 256 -- no error, no warning
  ```

  If overflow matters for your use case, check bounds manually or use the `math` package constants (`math.MaxInt64`, `math.MaxUint32`, etc.) for guard checks.
</Warning>

<Tip>
  **Idiomatic Go**: Prefer `strconv` over `fmt.Sprintf` for type conversions to/from strings. `strconv.Itoa(42)` is faster and more intention-revealing than `fmt.Sprintf("%d", 42)`, and `strconv.Atoi("42")` returns a proper error for invalid input.
</Tip>

***

## Interview Deep-Dive

<AccordionGroup>
  <Accordion title="Go has zero values for all types. How does this design choice affect the way you write code compared to languages with null or undefined, and where can it bite you?">
    **Strong Answer:**

    * Zero values mean every variable in Go is always initialized to a well-defined, usable value. An `int` is `0`, a `string` is `""`, a `bool` is `false`, and a `struct` has all fields set to their respective zero values. This eliminates entire classes of "uninitialized variable" bugs that plague C and C++, and avoids the "billion dollar mistake" of null references in languages like Java.
    * Practically, this enables patterns like declaring a `bytes.Buffer` or a `sync.Mutex` and using them immediately without any constructor call. The zero value IS the valid initial state. This is a deliberate design principle: "make the zero value useful."
    * Where it bites you: a nil map reads fine (returns zero values) but panics on write. A nil slice is safe to append to but a nil pointer to a struct will panic when you access a field. And the most subtle gotcha: a nil pointer inside an interface is NOT a nil interface. If you return a `*MyError(nil)` as an `error` interface, the caller's `err != nil` check returns true because the interface has a type even though its underlying value is nil.
    * In production, the zero-value principle means you need to think carefully about whether "zero" is a valid state for your domain. For example, a `UserID` of `0` might look valid to Go but represent "no user" in your business logic. Some teams use pointer fields or custom types to distinguish "not set" from "set to zero."

    **Follow-up: Why does Go require explicit type conversions between numeric types, and what real bug does this prevent?**

    Go requires explicit conversions (like `float64(myInt)`) because implicit numeric conversions are a notorious source of silent bugs. In C, assigning a `float` to an `int` silently truncates. In JavaScript, adding a number to a string silently concatenates. Go's explicit conversions force you to acknowledge potential data loss at the call site. For example, converting an `int64` to `int32` can overflow silently, but at least the conversion is visible in the code and can be caught during review. The trade-off is verbosity: Go code has more explicit casts than Python or JavaScript. But in production systems handling financial data or sensor readings, silent numeric truncation can cause real damage -- an explicit `int32(bigValue)` makes the risk visible.
  </Accordion>

  <Accordion title="Explain how iota works in Go. Show me a non-trivial use case beyond simple sequential constants.">
    **Strong Answer:**

    * `iota` is a compile-time constant generator that resets to 0 at each `const` block and increments by 1 for each constant specification (each line in the block). It is Go's replacement for C-style enums.
    * The simple case is sequential enums: `Red = iota` gives 0, 1, 2. But iota's real power is that it can be used in expressions that are repeated implicitly. For example, you can define byte-size constants using `1 << (10 * iota)`: skipping the zero value with `_`, KB becomes 1024, MB becomes 1048576, GB becomes 1073741824.
    * A practical production example is bitwise permission flags: `Read = 1 << iota` gives you 1, 2, 4 which you can combine with OR operations: `perms := Read | Write`. This is exactly how Unix file permissions work, and it is how Go's own `os.FileMode` constants are defined internally.
    * A more advanced pattern is using iota with a custom type and a `String()` method to create self-documenting enums that print as names instead of numbers. You define a `type Status int`, assign values with iota, then implement `func (s Status) String() string` using a slice or map lookup. This makes logging and debugging much clearer.

    **Follow-up: Go does not have a built-in enum type. If a function accepts a `Status` parameter typed as `int`, what prevents callers from passing an invalid value like 999?**

    Nothing at the type level -- this is a real limitation. Since `Status` is just an alias for `int`, any `int` value can be assigned to it. The Go compiler will not reject `Status(999)`. The defense is runtime validation: either check the value at API boundaries with a `func (s Status) IsValid() bool` method, or use the exhaustive switch pattern where your `switch` on the status has no `default` case and the `exhaustive` linter flags any missing cases. Some teams also define a `_maxStatus` sentinel as the last iota value and validate that `s >= 0 && s < _maxStatus`. This is a deliberate trade-off in Go's design -- simplicity over exhaustive compile-time checking. Languages with real sum types (Rust, Haskell) solve this at the type level, but Go chose not to add that complexity.
  </Accordion>

  <Accordion title="A colleague writes `var x uint8 = 255; x++` and expects 256. What happens, why, and how would you defend against this in production code?">
    **Strong Answer:**

    * Go does not panic on integer overflow. The value wraps around silently according to modular arithmetic. So `uint8(255) + 1` becomes `0`, not 256. For signed types, `int8(127) + 1` becomes `-128`. There is no runtime error, no warning, nothing.
    * This is inherited from C's behavior and exists because overflow checking on every arithmetic operation would add significant runtime overhead. Go chose performance over safety here, unlike Rust which panics on overflow in debug mode and wraps in release mode.
    * To defend against this in production: First, use `int` (which is 64-bit on modern systems) unless you have a specific reason to use a smaller type. The range of `int64` is large enough that overflow is practically impossible for most business logic. Second, when working with user input or wire protocols that use fixed-width integers, add explicit bounds checks before arithmetic: `if x > math.MaxUint8 - increment { return ErrOverflow }`. Third, for critical financial calculations, consider using `math/big` for arbitrary-precision arithmetic. Fourth, the `math` package provides constants like `math.MaxInt64`, `math.MaxUint32`, etc., for guard checks.
    * The real-world scenario where this burns people: processing counters or metrics that monotonically increase. If you use a `uint32` counter for HTTP requests and your service handles 100K requests per second, you overflow in about 12 hours.

    **Follow-up: Strings in Go are immutable sequences of bytes, not characters. What problems does this create when processing Unicode text?**

    A Go `string` is a read-only slice of bytes, typically UTF-8 encoded but not guaranteed to be. `len("hello")` returns the byte count, not the character count. For ASCII this is the same, but for multi-byte UTF-8 characters like emoji or CJK text, `len("...")` gives a larger number than expected. Iterating with a byte index (`s[i]`) gives individual bytes, which can split a multi-byte character. The correct way to iterate over characters (runes) is `for i, r := range s`, which decodes UTF-8 on the fly. For character counting, use `utf8.RuneCountInString(s)`. For string manipulation at the rune level, convert to `[]rune` first, but be aware that this allocates a new slice. In production APIs that handle international text, always be explicit about whether your "length" means bytes or runes, and always validate that incoming strings are valid UTF-8 using `utf8.Valid()`.
  </Accordion>
</AccordionGroup>
