Skip to content

Go Arrays and the Blank Identifier

Overview

Master Go's array fundamentals and the powerful blank identifier. This comprehensive guide covers fixed-size arrays, initialization patterns, and the versatile underscore (_) for ignoring values, building upon our understanding of data types and control structures.

Key Points

  • Arrays are fixed-size, value types in Go
  • Array length is part of the type definition
  • Multiple initialization and declaration patterns
  • The blank identifier (_) for discarding unwanted values
  • Arrays vs slices: when to use each

Understanding Arrays in Go 📦

Arrays in Go are fundamental data structures that store a fixed number of elements of the same type. Unlike slices, arrays have a compile-time fixed size that becomes part of their type.

Array Characteristics

graph TD
    A[Go Arrays] --> B[Fixed Size]
    A --> C[Value Type]
    A --> D[Same Element Type]
    A --> E[Zero-Indexed]
    B --> B1[Size in Type Definition]
    C --> C1[Copied on Assignment]
    D --> D1[Type Safety]
    E --> E1[Access by Index]
    style A fill:#999,stroke:#333,stroke-width:2px,color:#000

Array Declaration and Initialization ⚙

Go provides multiple ways to declare and initialize arrays, each suited for different scenarios.

Basic Declaration Patterns

Array Declaration Methods

zero_values.go
package main

import "fmt"

func main() {
    // Declare array with zero values
    var numbers [5]int
    var names [3]string

    fmt.Printf("Numbers: %v\n", numbers)  // [0 0 0 0 0]
    fmt.Printf("Names: %v\n", names)     // ["" "" ""]

    // Zero values by type:
    // int: 0, string: "", bool: false, float: 0.0
}

Zero Values

Arrays are automatically initialized with zero values for their element type.

literal_init.go
package main

import "fmt"

func main() {
    // Initialize with literal values
    scores := [5]int{95, 87, 92, 78, 88}
    cities := [3]string{"New York", "London", "Tokyo"}

    fmt.Printf("Scores: %v\n", scores)
    fmt.Printf("Cities: %v\n", cities)

    // Output:
    // Scores: [95 87 92 78 88]
    // Cities: [New York London Tokyo]
}
inferred_length.go
package main

import "fmt"

func main() {
    // Let compiler determine length
    primes := [...]int{2, 3, 5, 7, 11, 13}
    vowels := [...]string{"a", "e", "i", "o", "u"}

    fmt.Printf("Primes: %v (length: %d)\n", primes, len(primes))
    fmt.Printf("Vowels: %v (length: %d)\n", vowels, len(vowels))

    // Output:
    // Primes: [2 3 5 7 11 13] (length: 6)
    // Vowels: [a e i o u] (length: 5)
}

Advanced Initialization

Advanced Array Patterns

partial_init.go
package main

import "fmt"

func main() {
    // Partial initialization (rest are zero values)
    numbers := [10]int{1, 2, 3}  // [1 2 3 0 0 0 0 0 0 0]

    // Specific index initialization
    sparse := [5]int{1: 10, 3: 30}  // [0 10 0 30 0]

    fmt.Printf("Numbers: %v\n", numbers)
    fmt.Printf("Sparse: %v\n", sparse)
}
multidimensional.go
package main

import "fmt"

func main() {
    // 2D array (matrix)
    matrix := [3][3]int{
        {1, 2, 3},
        {4, 5, 6},
        {7, 8, 9},
    }

    // 3D array
    cube := [2][2][2]int{
        {{1, 2}, {3, 4}},
        {{5, 6}, {7, 8}},
    }

    fmt.Printf("Matrix:\n")
    for i := 0; i < 3; i++ {
        fmt.Printf("%v\n", matrix[i])
    }

    fmt.Printf("Cube: %v\n", cube)
}

Array Operations and Access 🔄

Accessing and Modifying Elements

Array Element Operations

array_access.go
package main

import "fmt"

func main() {
    fruits := [4]string{"apple", "banana", "orange", "grape"}

    // Access elements by index
    fmt.Printf("First fruit: %s\n", fruits[0])
    fmt.Printf("Last fruit: %s\n", fruits[len(fruits)-1])

    // Modify elements
    fruits[1] = "mango"
    fmt.Printf("Modified array: %v\n", fruits)

    // Array length
    fmt.Printf("Array length: %d\n", len(fruits))

    // Output:
    // First fruit: apple
    // Last fruit: grape
    // Modified array: [apple mango orange grape]
    // Array length: 4
}
bounds_checking.go
package main

import "fmt"

func main() {
    numbers := [3]int{10, 20, 30}

    // Safe access
    for i := 0; i < len(numbers); i++ {
        fmt.Printf("numbers[%d] = %d\n", i, numbers[i])
    }

    // This would cause a runtime panic:
    // fmt.Println(numbers[5])  // index out of range

    fmt.Println("Array access completed safely")
}

Iterating Over Arrays

Array Iteration Patterns

traditional_loop.go
package main

import "fmt"

func main() {
    temperatures := [7]float64{22.5, 25.0, 23.8, 26.2, 24.1, 21.9, 23.3}

    fmt.Println("Daily temperatures:")
    for i := 0; i < len(temperatures); i++ {
        fmt.Printf("Day %d: %.1f°C\n", i+1, temperatures[i])
    }
}
range_loop.go
package main

import "fmt"

func main() {
    colors := [5]string{"red", "green", "blue", "yellow", "purple"}

    fmt.Println("Colors with indices:")
    for index, color := range colors {
        fmt.Printf("Index %d: %s\n", index, color)
    }
}
value_only.go
package main

import "fmt"

func main() {
    scores := [4]int{95, 87, 92, 78}
    total := 0

    for _, score := range scores {
        total += score
    }

    average := float64(total) / float64(len(scores))
    fmt.Printf("Average score: %.2f\n", average)

    // Output: Average score: 88.00
}

The Blank Identifier: Ignoring Values ➖

The blank identifier (_) is a powerful Go feature that allows you to discard values you don't need.

Understanding the Blank Identifier

Blank Identifier Usage

ignore_indices.go
package main

import "fmt"

func main() {
    products := [4]string{"laptop", "mouse", "keyboard", "monitor"}

    fmt.Println("Product list:")
    for _, product := range products {
        fmt.Printf("- %s\n", product)
    }

    // Output:
    // Product list:
    // - laptop
    // - mouse
    // - keyboard
    // - monitor
}
ignore_values.go
package main

import "fmt"

func main() {
    data := [6]int{10, 20, 30, 40, 50, 60}

    fmt.Println("Array indices:")
    for index, _ := range data {
        fmt.Printf("Index: %d\n", index)
    }

    // More commonly written as:
    fmt.Println("\nArray indices (alternative):")
    for index := range data {
        fmt.Printf("Index: %d\n", index)
    }
}
function_returns.go
package main

import "fmt"

func getArrayStats(arr [5]int) (int, float64, int) {
    sum := 0
    for _, v := range arr {
        sum += v
    }
    return sum, float64(sum) / float64(len(arr)), len(arr)
}

func main() {
    numbers := [5]int{10, 20, 30, 40, 50}

    // Only interested in the sum, ignore average and length
    sum, _, _ := getArrayStats(numbers)
    fmt.Printf("Sum: %d\n", sum)

    // Only interested in average
    _, average, _ := getArrayStats(numbers)
    fmt.Printf("Average: %.2f\n", average)
}

Array Characteristics and Behavior 🔍

Arrays as Value Types

Value Type Behavior

array_copying.go
package main

import "fmt"

func main() {
    original := [3]int{1, 2, 3}

    // Arrays are copied by value
    copy := original
    copy[0] = 100

    fmt.Printf("Original: %v\n", original)  // [1 2 3]
    fmt.Printf("Copy: %v\n", copy)         // [100 2 3]

    // Changes to copy don't affect original
}
function_params.go
package main

import "fmt"

func modifyArray(arr [3]int) {
    arr[0] = 999
    fmt.Printf("Inside function: %v\n", arr)
}

func modifyArrayPointer(arr *[3]int) {
    arr[0] = 999
    fmt.Printf("Inside function (pointer): %v\n", *arr)
}

func main() {
    numbers := [3]int{1, 2, 3}

    fmt.Printf("Before function call: %v\n", numbers)
    modifyArray(numbers)
    fmt.Printf("After function call: %v\n", numbers)

    fmt.Println("\nUsing pointer:")
    modifyArrayPointer(&numbers)
    fmt.Printf("After pointer function call: %v\n", numbers)
}

Array Comparison

Comparing Arrays

array_equality.go
package main

import "fmt"

func main() {
    arr1 := [3]int{1, 2, 3}
    arr2 := [3]int{1, 2, 3}
    arr3 := [3]int{1, 2, 4}

    // Arrays can be compared with == and !=
    fmt.Printf("arr1 == arr2: %t\n", arr1 == arr2)  // true
    fmt.Printf("arr1 == arr3: %t\n", arr1 == arr3)  // false
    fmt.Printf("arr1 != arr3: %t\n", arr1 != arr3)  // true

    // Arrays of different sizes cannot be compared
    // arr4 := [4]int{1, 2, 3, 4}
    // fmt.Println(arr1 == arr4)  // Compile error
}

Best Practices and Common Patterns 📓

Array Best Practices

  1. Choose Arrays When Size is Fixed

    1
    2
    3
    4
    5
    6
    // Good: Fixed configuration values
    const maxRetries = 3
    var retryDelays [maxRetries]time.Duration
    
    // Consider slices for dynamic data
    var dynamicData []int
    

  2. Use Range for Iteration

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // Preferred: Safe and idiomatic
    for i, value := range array {
        process(i, value)
    }
    
    // Avoid: Error-prone
    for i := 0; i < len(array); i++ {
        process(i, array[i])
    }
    

  3. Leverage the Blank Identifier

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // When you only need values
    for _, value := range array {
        process(value)
    }
    
    // When you only need indices
    for index := range array {
        processIndex(index)
    }
    

  4. Consider Pointers for Large Arrays

    1
    2
    3
    4
    5
    6
    7
    func processLargeArray(arr *[1000]int) {
        // Avoids copying 1000 integers
        for i, v := range arr {
            // Process array elements
            _ = i + v
        }
    }
    

Common Pitfalls

  • Array Size in Type: [3]int and [4]int are different types
  • Value Semantics: Arrays are copied, not referenced
  • Bounds Checking: Runtime panics for out-of-bounds access
  • Function Parameters: Large arrays are expensive to copy

Quick Reference 📑

Key Takeaways

  1. Fixed Size: Arrays have compile-time determined, fixed sizes
  2. Value Types: Arrays are copied when assigned or passed to functions
  3. Type Safety: Array length is part of the type definition
  4. Zero Values: Arrays are initialized with zero values for their element type
  5. Blank Identifier: Use _ to ignore unwanted values in range loops
  6. Comparison: Arrays of the same type and size can be compared with == and !=

Remember

"Use arrays when you need fixed-size collections with value semantics. For dynamic collections, prefer slices. The blank identifier is your friend for ignoring unwanted values."