Close Menu
    DevStackTipsDevStackTips
    • Home
    • News & Updates
      1. Tech & Work
      2. View All

      This week in AI updates: Mistral’s new Le Chat features, ChatGPT updates, and more (September 5, 2025)

      September 6, 2025

      Designing For TV: Principles, Patterns And Practical Guidance (Part 2)

      September 5, 2025

      Neo4j introduces new graph architecture that allows operational and analytics workloads to be run together

      September 5, 2025

      Beyond the benchmarks: Understanding the coding personalities of different LLMs

      September 5, 2025

      Hitachi Energy Pledges $1B to Strengthen US Grid, Build Largest Transformer Plant in Virginia

      September 5, 2025

      How to debug a web app with Playwright MCP and GitHub Copilot

      September 5, 2025

      Between Strategy and Story: Thierry Chopain’s Creative Path

      September 5, 2025

      What You Need to Know About CSS Color Interpolation

      September 5, 2025
    • Development
      1. Algorithms & Data Structures
      2. Artificial Intelligence
      3. Back-End Development
      4. Databases
      5. Front-End Development
      6. Libraries & Frameworks
      7. Machine Learning
      8. Security
      9. Software Engineering
      10. Tools & IDEs
      11. Web Design
      12. Web Development
      13. Web Security
      14. Programming Languages
        • PHP
        • JavaScript
      Featured

      Why browsers throttle JavaScript timers (and what to do about it)

      September 6, 2025
      Recent

      Why browsers throttle JavaScript timers (and what to do about it)

      September 6, 2025

      How to create Google Gemini AI component in Total.js Flow

      September 6, 2025

      Drupal 11’s AI Features: What They Actually Mean for Your Team

      September 5, 2025
    • Operating Systems
      1. Windows
      2. Linux
      3. macOS
      Featured

      Harnessing GitOps on Linux for Seamless, Git-First Infrastructure Management

      September 6, 2025
      Recent

      Harnessing GitOps on Linux for Seamless, Git-First Infrastructure Management

      September 6, 2025

      How DevOps Teams Are Redefining Reliability with NixOS and OSTree-Powered Linux

      September 5, 2025

      Distribution Release: Linux Mint 22.2

      September 4, 2025
    • Learning Resources
      • Books
      • Cheatsheets
      • Tutorials & Guides
    Home»Development»Arrays, Slices, and Maps in Go: a Quick Guide to Collection Types

    Arrays, Slices, and Maps in Go: a Quick Guide to Collection Types

    September 6, 2025

    Golang has a reputation for simplicity, and one reason is its small set of core data structures. Unlike some languages that offer lists, vectors, dictionaries, hashmaps, tuples, sets, and more, Go keeps things minimal.

    The three fundamental building blocks you’ll use every day are:

    • Arrays: fixed-size sequences of elements.

    • Slices: flexible, dynamic views of arrays.

    • Maps: key–value stores implemented as hash tables.

    With these three, you can represent almost any collection of data you need.

    In this tutorial, you’ll learn how to use arrays, slices, and maps effectively. You’ll also peek under the hood to see how Go represents them in memory. This will help you understand their performance characteristics and avoid common pitfalls.

    By the end, you’ll be able to:

    • Choose the right data type for your problem.

    • Write idiomatic Go code for collections.

    • Understand how these types behave internally.

    • Build a small project combining arrays, slices, and maps.

    Let’s dive in!

    What We’ll Cover:

    1. Prerequisites

    2. Arrays in Go

      • Initializing with Values

      • Array Length

      • Inner Representation of Arrays

      • Multi-dimensional Arrays

      • Limitations

      • When Arrays Are Useful

    3. Slices in Go

      • When to use slices?

      • How to Declare a Slice

      • Allocate (with make)

      • Append Elements

      • How to Slice Slices

      • Inner Representation of Slices

      • How to Copy Slices

      • Multi-dimensional Slices

      • Slices vs Arrays

    4. Maps in Go

      • How to Declare a Map

      • How to Access Values

      • How to Iterate Over a Map

      • Inner Representation of Maps

      • Arrays vs. Slices vs. Maps

    5. Mini Project: Shopping Cart Totals

    6. Practice Challenge

    7. Conclusion

      • Practice Challenge Solution

    Prerequisites

    This tutorial is designed for readers who already have some basic experience with Go. You don’t need to be an expert, but you should be comfortable with:

    • Writing and running simple Go programs (go run, go build).

    • Declaring and using variables, functions, and basic types (for example, int, string, bool).

    • Understanding control structures like if, for, and range.

    • Using the Go toolchain and go mod init to set up a project.

    If you’ve completed the Tour of Go or written a few small Go programs, you’ll be ready to follow along – we’ll cover the internals at a beginner-friendly level.

    Arrays in Go

    An array is a numbered sequence of elements of the same type with a fixed length. Here’s an example in Go:

    <span class="hljs-keyword">package</span> main
    
    <span class="hljs-keyword">import</span> <span class="hljs-string">"fmt"</span>
    
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
        <span class="hljs-keyword">var</span> nums [<span class="hljs-number">3</span>]<span class="hljs-keyword">int</span> <span class="hljs-comment">// array of 3 integers</span>
        fmt.Println(nums) <span class="hljs-comment">// [0 0 0]</span>
    }
    

    This code declares an array with space for exactly three int values. Go arrays are zero-indexed, meaning the first element is at index 0. The elements, like every Go variable, are initialized to the zero value of their type (0 for integers, ““ for strings, and so on).

    Once the array is created, you can access its elements using their index like this:

    <span class="hljs-keyword">package</span> main
    
    <span class="hljs-keyword">import</span> <span class="hljs-string">"fmt"</span>
    
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
        <span class="hljs-keyword">var</span> nums [<span class="hljs-number">3</span>]<span class="hljs-keyword">int</span> <span class="hljs-comment">// array of 3 integers</span>
        nums[<span class="hljs-number">1</span>] = <span class="hljs-number">2</span>
        fmt.Println(nums[<span class="hljs-number">1</span>]) <span class="hljs-comment">// 2</span>
        fmt.Println(nums) <span class="hljs-comment">// [0 2 0]</span>
    }
    

    Initializing with Values

    So far, we’ve seen that arrays are created with their elements set to the zero value of the element type. But often, you’ll want to give an array specific starting values right when you declare it. This process is called initialization: you provide the values in a list, and Go fills the array in order.

    <span class="hljs-keyword">package</span> main
    
    <span class="hljs-keyword">import</span> <span class="hljs-string">"fmt"</span>
    
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
        nums := [<span class="hljs-number">3</span>]<span class="hljs-keyword">int</span>{<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>} <span class="hljs-comment">// array of 3 integers</span>
        fmt.Println(nums) <span class="hljs-comment">// [1 2 3]</span>
    }
    

    If you omit the size when initializing an array, Go will infer it from the number of elements you provide:

    <span class="hljs-keyword">package</span> main
    
    <span class="hljs-keyword">import</span> <span class="hljs-string">"fmt"</span>
    
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
        nums := [...]<span class="hljs-keyword">int</span>{<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>} <span class="hljs-comment">// array of 3 integers</span>
        fmt.Println(nums) <span class="hljs-comment">// [1 2 3]</span>
    }
    

    If you specify the size explicitly, the compiler will enforce that size:

    <span class="hljs-keyword">package</span> main
    
    <span class="hljs-keyword">import</span> <span class="hljs-string">"fmt"</span>
    
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
        nums := [<span class="hljs-number">3</span>]<span class="hljs-keyword">int</span>{<span class="hljs-number">1</span>, <span class="hljs-number">2</span>} <span class="hljs-comment">// array of 3 integers</span>
        fmt.Println(nums) <span class="hljs-comment">// [1 2 0]</span>
    }
    

    Array Length

    In Go, the length of an array is part of its type. [3]int and [4]int are considered completely different types, even though they both hold integers (you cannot assign a [4]int to a [3]int, or even compare them directly, because their lengths don’t match):

    <span class="hljs-keyword">package</span> main
    
    <span class="hljs-keyword">import</span> <span class="hljs-string">"fmt"</span>
    
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
        <span class="hljs-keyword">var</span> a [<span class="hljs-number">3</span>]<span class="hljs-keyword">int</span>
        <span class="hljs-keyword">var</span> b [<span class="hljs-number">4</span>]<span class="hljs-keyword">int</span>
        fmt.Println(a == b) <span class="hljs-comment">// compilation error</span>
    }
    

    When you use [...] in an array literal, Go counts how many elements you’ve provided and that will be the length. The length of an array is fixed and cannot be changed afterwards.

    You can retrieve the length of an array using the built-in len function:

    <span class="hljs-keyword">package</span> main
    
    <span class="hljs-keyword">import</span> <span class="hljs-string">"fmt"</span>
    
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
        nums := [<span class="hljs-number">3</span>]<span class="hljs-keyword">int</span>{<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>}
        fmt.Println(<span class="hljs-built_in">len</span>(nums)) <span class="hljs-comment">// 3</span>
    }
    

    Inner Representation of Arrays

    In Go, arrays are represented as contiguous blocks of memory. This means that the elements of an array are stored one after the other in memory, making it easy to calculate the address of any element based on its index.

    For example, consider the following array:

    <span class="hljs-keyword">package</span> main
    
    <span class="hljs-keyword">import</span> <span class="hljs-string">"fmt"</span>
    
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
        nums := [<span class="hljs-number">3</span>]<span class="hljs-keyword">int32</span>{<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>} <span class="hljs-comment">// array of 3 32-bit integers</span>
        fmt.Println(&nums[<span class="hljs-number">0</span>]) <span class="hljs-comment">// address of the first element</span>
        fmt.Println(&nums[<span class="hljs-number">1</span>]) <span class="hljs-comment">// address of the second element</span>
        fmt.Println(&nums[<span class="hljs-number">2</span>]) <span class="hljs-comment">// address of the third element</span>
    }
    

    It will give you something like this:

    0xc00000a0f0
    0xc00000a0f4
    0xc00000a0f8
    

    32 bits are 4 bytes, so the addresses of the elements differ by 4 bytes as well.

    In the example above, we used &nums[0] to get the address of the first element. You might wonder what happens if you take the address of the array itself, using &nums:

    fmt.Println(&nums)
    

    At first glance, you might expect this to give you the same result as &nums[0], like in C where arrays often “decay” into pointers. But Go is different:

    • &nums is a pointer to the entire array (type *[3]int32).

    • &nums[0] is a pointer to the first element (type *int32).

    When you print &nums, the fmt package recognizes it as a pointer to an array and shows the array’s contents (&[1 2 3]) rather than a raw memory address.

    In Go, arrays and pointers to arrays are distinct types, and &nums is of type *[3]int32, not *int32. When you print &nums, fmt recognizes it as a pointer to an array and displays the array’s contents, not the address. If you want the address of the first element, you use &nums[0], which is of type *int32.

    If you try to access an out-of-bounds index, your program will panic at runtime with an error:

    <span class="hljs-keyword">package</span> main
    
    <span class="hljs-keyword">import</span> <span class="hljs-string">"fmt"</span>
    
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
        nums := [<span class="hljs-number">3</span>]<span class="hljs-keyword">int32</span>{<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>}
            i := <span class="hljs-number">4</span>
        fmt.Println(&nums[i])
    }
    
    panic: runtime error: index out of range [4] with length 3
    
    goroutine 1 [running]:
    main.main()
            C:/projects/Articles/Go Context/main.go:8 +0x3d
    exit status 2
    

    This behavior is called bounds checking: before Go reads or writes an array element, it ensures the index is within the valid range (0 up to len(array)-1). If it’s not, the program immediately panics instead of letting you access memory that doesn’t belong to the array.

    Bounds checking is important because it:

    • Prevents memory corruption: in languages like C, out-of-bounds access can overwrite unrelated memory and cause hard-to-find bugs or security issues.

    • Makes programs safer by default: Go will stop execution right away rather than let invalid memory access continue silently.

    • Helps debugging: the panic message clearly shows the invalid index and the array’s length, so you can quickly track down the bug.

    • It trades a small runtime cost for much greater safety and reliability.

    Like every other data structure in Go, arrays are passed by value, meaning that when you pass an array to a function, a copy is made. This can lead to performance issues, so for large arrays, it’s often better to pass a pointer to the array instead.

    Multi-dimensional Arrays

    Multi-dimensional arrays let you model data that naturally fits into rows and columns (or higher dimensions). Some common uses include:

    • Matrices and grids

    • Images and pixel data in 2D or 3D

    • Static lookup tables

    Go supports multi-dimensional arrays, which are essentially arrays of arrays. Here’s an example:

    <span class="hljs-keyword">package</span> main
    
    <span class="hljs-keyword">import</span> <span class="hljs-string">"fmt"</span>
    
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
        <span class="hljs-keyword">var</span> matrix [<span class="hljs-number">2</span>][<span class="hljs-number">3</span>]<span class="hljs-keyword">int</span> <span class="hljs-comment">// 2x3 matrix</span>
        matrix[<span class="hljs-number">0</span>][<span class="hljs-number">0</span>] = <span class="hljs-number">1</span>
        matrix[<span class="hljs-number">0</span>][<span class="hljs-number">1</span>] = <span class="hljs-number">2</span>
        matrix[<span class="hljs-number">0</span>][<span class="hljs-number">2</span>] = <span class="hljs-number">3</span>
        matrix[<span class="hljs-number">1</span>][<span class="hljs-number">0</span>] = <span class="hljs-number">4</span>
        matrix[<span class="hljs-number">1</span>][<span class="hljs-number">1</span>] = <span class="hljs-number">5</span>
        matrix[<span class="hljs-number">1</span>][<span class="hljs-number">2</span>] = <span class="hljs-number">6</span>
        fmt.Println(matrix)
    }
    

    In this example, we create a 2×3 matrix (2 rows and 3 columns) and initialize its elements. You can access elements using two indices: the first for the row and the second for the column. This can be extended to more dimensions too, but the size of each dimension must be known at compile time.

    Limitations

    The greatest limitation of arrays in Golang is that their size must be known at compile time. Once it’s declared, the size can’t be changed. Because of this rigidity, arrays are rarely used directly.

    When Arrays Are Useful

    Despite their rigidity, arrays have a few niche but important use cases in Go:

    • Fixed-size data like IP addresses

    • Low-level data structures

    • Interop with C or system calls

    Slices in Go

    Because arrays are fixed-size, Go introduced slices: flexible, dynamic sequences built on top of arrays. Think of slices as views into arrays. A slice keeps three things:

    1. Pointer: A reference to the underlying array.

    2. Length: The number of elements in the slice.

    3. Capacity: The maximum number of elements the slice can hold (which is always greater than or equal to the length).

    Unlike arrays, a slice’s length and capacity can change dynamically as you add or remove elements.

    When to Use Slices

    In practice, slices are the default way to work with collections in Go. You’ll use them when:

    • You don’t know the size of the collection in advance.

    • You need to grow or shrink the collection over time.

    • You want to pass around subsections of an array without copying data.

    • You want idiomatic Go code (most Go APIs accept and return slices, not arrays).

    Arrays are mainly useful when you need a fixed size known at compile time (like a 16-byte UUID). For almost everything else, slices are the go-to choice.

    How to Declare a Slice

    <span class="hljs-keyword">var</span> s []<span class="hljs-keyword">int</span>           <span class="hljs-comment">// slice of integers</span>
    fmt.Println(s)        <span class="hljs-comment">// []</span>
    fmt.Println(<span class="hljs-built_in">len</span>(s))   <span class="hljs-comment">// length: 0</span>
    fmt.Println(<span class="hljs-built_in">cap</span>(s))   <span class="hljs-comment">// capacity: 0</span>
    

    With var s []int you are declaring a slice. That means you’ve introduced a variable s of type “slice of int” ([]int), but you haven’t yet given it any backing array. At this point, s is nil – it doesn’t point to any actual storage. That’s why its length and capacity are both zero, until you allocate or append to it.

    Note that you can also declare a slice using var s[]int{} which initializes the slice with zero elements, but you can’t create an empty array using this syntax: var s[...]int{}. The latter is invalid in Go: you can’t use [...] with var and an empty initialiser!

    Allocate (with make)

    s := <span class="hljs-built_in">make</span>([]<span class="hljs-keyword">int</span>, <span class="hljs-number">3</span>) <span class="hljs-comment">// length 3, capacity 3</span>
    fmt.Println(s)      <span class="hljs-comment">// [0 0 0]</span>
    

    Here, Go creates an underlying array of size 3 and makes s point to it. Now s has length 3 and capacity 3.

    You can also specify a larger capacity:

    s := <span class="hljs-built_in">make</span>([]<span class="hljs-keyword">int</span>, <span class="hljs-number">3</span>, <span class="hljs-number">5</span>) <span class="hljs-comment">// length 3, capacity 5</span>
    fmt.Println(s)         <span class="hljs-comment">// [0 0 0]</span>
    fmt.Println(<span class="hljs-built_in">len</span>(s))    <span class="hljs-comment">// length: 3</span>
    fmt.Println(<span class="hljs-built_in">cap</span>(s))    <span class="hljs-comment">// capacity: 5</span>
    

    The built-in make function is Go’s way of allocating and initializing certain composite types: slices, maps, and channels. Unlike new, which gives you a pointer to a zeroed value, make sets up the internal data structures those types need to work.

    For slices, make does three things under the hood:

    1. Allocates an array of the given size (either the length you specify, or the capacity if you provide both).

    2. Creates a slice header (pointer, length, capacity) that points to that array.

    3. Returns the slice header, ready to use.

    Append Elements

    One of the main reasons slices are so useful compared to arrays is that they can grow dynamically. In practice, you’ll often start with a slice of a certain length and then need to add more elements later. Again, this is something arrays don’t allow.

    Go provides the built-in append function for this. append takes an existing slice and one or more new elements, and returns a new slice with those elements added:

    s := <span class="hljs-built_in">make</span>([]<span class="hljs-keyword">int</span>, <span class="hljs-number">3</span>, <span class="hljs-number">5</span>)  <span class="hljs-comment">// create [0 0 0]</span>
    s = <span class="hljs-built_in">append</span>(s, <span class="hljs-number">1</span>)
    s = <span class="hljs-built_in">append</span>(s, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>)
    fmt.Println(s)          <span class="hljs-comment">// [0 0 0 1 2 3]</span>
    fmt.Println(<span class="hljs-built_in">len</span>(s))     <span class="hljs-comment">// length: 6</span>
    fmt.Println(<span class="hljs-built_in">cap</span>(s))     <span class="hljs-comment">// capacity: 10 - may be different, depending on the Go version and implementation, but generally it will double when exceeded</span>
    

    If there’s enough capacity, append just writes into the existing array. If not, Go automatically allocates a new larger array, copies the old elements over, and adds the new value. That’s why a slice can grow even though arrays themselves are fixed-size. On one hand, this provides flexibility, but it can also lead to performance overhead due to the need for memory allocation and copying.

    To mitigate this, it’s a good practice to preallocate slices with an appropriate capacity when you know the size in advance.

    How to Slice Slices

    In Golang, you can create a new slice by slicing an existing one. You can do this using the [:] operator. The syntax is slice[low:high], where low is the starting index (inclusive) and high is the ending index (exclusive). If low is omitted, it defaults to 0. If high is omitted, it defaults to the length of the slice:

    s := []<span class="hljs-keyword">int</span>{<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>, <span class="hljs-number">5</span>}
    s1 := s[<span class="hljs-number">1</span>:<span class="hljs-number">4</span>] <span class="hljs-comment">// [2 3 4]</span>
    s2 := s[:<span class="hljs-number">3</span>]  <span class="hljs-comment">// [1 2 3]</span>
    s3 := s[<span class="hljs-number">2</span>:]  <span class="hljs-comment">// [3 4 5]</span>
    fmt.Println(s1, s2, s3)
    

    If two slices share the same underlying array, changes to the elements of one slice will be reflected in the other. This is because both slices point to the same data in memory. For example:

    s := []<span class="hljs-keyword">int</span>{<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>, <span class="hljs-number">5</span>}
    s1 := s[<span class="hljs-number">1</span>:<span class="hljs-number">4</span>] <span class="hljs-comment">// [2 3 4]</span>
    s2 := s[<span class="hljs-number">2</span>:]  <span class="hljs-comment">// [3 4 5]</span>
    s1[<span class="hljs-number">0</span>] = <span class="hljs-number">10</span>
    fmt.Println(s)  <span class="hljs-comment">// [1 10 3 4 5]</span>
    fmt.Println(s2)  <span class="hljs-comment">// [10 3 4 5]</span>
    

    Inner Representation of Slices

    Internally, a slice is represented by a struct that contains a pointer to the underlying array, the length of the slice, and its capacity:

    <span class="hljs-keyword">type</span> slice <span class="hljs-keyword">struct</span> {
        ptr *ElementType  <span class="hljs-comment">// pointer to underlying array</span>
        <span class="hljs-built_in">len</span> <span class="hljs-keyword">int</span>
        <span class="hljs-built_in">cap</span> <span class="hljs-keyword">int</span>
    }
    

    This allows slices to be lightweight and efficient, as they don’t require copying the entire array when being passed around, just the pointer to the array (and length and capacity). This is often a source of confusion: passing a slice to a function feels like passing by reference, as the values are not copied – but the slice struct itself is still passed by value:

    <span class="hljs-keyword">package</span> main
    
    <span class="hljs-keyword">import</span> <span class="hljs-string">"fmt"</span>
    
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">modify</span><span class="hljs-params">(s1 [3]<span class="hljs-keyword">int</span>, s2 []<span class="hljs-keyword">int</span>)</span></span> {
        s1[<span class="hljs-number">0</span>] = <span class="hljs-number">99</span>
        s2[<span class="hljs-number">0</span>] = <span class="hljs-number">99</span>
    }
    
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
        nums_array := [...]<span class="hljs-keyword">int</span>{<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>} <span class="hljs-comment">// array of 3 integers</span>
        nums_slice := []<span class="hljs-keyword">int</span>{<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>}    <span class="hljs-comment">// slice of 3 integers</span>
        modify(nums_array, nums_slice)
        fmt.Println(nums_array)         <span class="hljs-comment">// Output: [1 2 3] - only modified the copy</span>
        fmt.Println(nums_slice)         <span class="hljs-comment">// Output: [99 2 3] - modified the value in the original slice</span>
    }
    

    How to Copy Slices

    Copying a slice creates a new slice with the same elements. You can do this using the built-in copy function:

    s1 := []<span class="hljs-keyword">int</span>{<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>}
    s2 := <span class="hljs-built_in">make</span>([]<span class="hljs-keyword">int</span>, <span class="hljs-built_in">len</span>(s1))
    <span class="hljs-built_in">copy</span>(s2, s1)      <span class="hljs-comment">// copies elements from s1 to s2</span>
    fmt.Println(s2)   <span class="hljs-comment">// [1 2 3]</span>
    

    Common pitfalls when copying slices:

    • Capacity: When copying a slice, the capacity of the destination slice is not automatically adjusted. If the destination slice has a smaller capacity than the source slice, it will only copy up to the capacity of the destination slice.

    • Nil Slices: If the source slice is nil, the copy function will not panic, but the destination slice will remain unchanged.

    • Overlapping Slices: If the source and destination slices overlap, the behavior is undefined. To avoid this, make sure to copy to a separate slice.

    Multi-dimensional Slices

    Just like multi-dimensional arrays, you can create multi-dimensional slices, which are essentially slices of slices:

    matrix := [][]<span class="hljs-keyword">int</span>{
        {<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>},
        {<span class="hljs-number">4</span>, <span class="hljs-number">5</span>, <span class="hljs-number">6</span>},
        {<span class="hljs-number">7</span>, <span class="hljs-number">8</span>, <span class="hljs-number">9</span>},
    }
    fmt.Println(matrix)
    

    Or:

    rows := <span class="hljs-number">3</span>
    cols := <span class="hljs-number">4</span>
    matrix := <span class="hljs-built_in">make</span>([][]<span class="hljs-keyword">int</span>, rows)
    <span class="hljs-keyword">for</span> i := <span class="hljs-keyword">range</span> matrix {
        matrix[i] = <span class="hljs-built_in">make</span>([]<span class="hljs-keyword">int</span>, cols)
    }
    

    Multi-dimensional slices are useful when you need flexible, dynamic grids of data. Common use cases include:

    • Representing game boards (for example, Tic-Tac-Toe, Minesweeper). This could be done with an array, too.

    • Mathematical matrices where the size isn’t fixed.

    • Jagged arrays, where each row can have a different length.

    Because slices can grow and shrink, they’re generally preferred over multi-dimensional arrays unless you need a fixed size known at compile time.

    Slices vs Arrays

    Let’s recap the key differences between slices and arrays in Go:

    1. Size: Arrays have a fixed size, while slices can grow and shrink dynamically.

    2. Memory: Arrays are value types and are copied when passed to functions, while slices are reference types and only the slice header is copied.

    3. Flexibility: Slices provide more flexibility and are generally preferred over arrays for most use cases.

    Maps in Go

    A map is Go’s built-in associative data type (hash table). It stores key-value pairs with fast average-time lookups.

    Unlike arrays and slices, which are indexed only by integers, maps let you use more meaningful keys such as names, IDs, or other comparable values. This makes them ideal when you need to look up, group, or count data quickly, for example, storing user ages by username, counting word frequencies in text, or mapping product IDs to their prices.

    How to Declare a Map

    m := <span class="hljs-built_in">make</span>(<span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]<span class="hljs-keyword">int</span>)  <span class="hljs-comment">// a map with string keys and int values</span>
    m[<span class="hljs-string">"alice"</span>] = <span class="hljs-number">23</span>
    m[<span class="hljs-string">"bob"</span>] = <span class="hljs-number">30</span>
    fmt.Println(m)             <span class="hljs-comment">// map[alice:23 bob:30]</span>
    

    Here, we create a map with string keys and int values. We can add key-value pairs to the map using the syntax m[key] = value. The make function is used to initialize the map. When we print the map, we see the key-value pairs in the output.

    Keys can be of any type that is comparable (for example, strings, integers, structs). But they can’t be slices, maps, or functions.

    A key in a map must be unique. If you assign a value to an existing key, it will overwrite the previous value.

    How to Access Values

    Once you have a map, you can retrieve a value using its key with the syntax map[key]:

    age := m[<span class="hljs-string">"alice"</span>]
    fmt.Println(age) <span class="hljs-comment">// 23</span>
    

    If the key doesn’t exist, you get the zero value:

    age := m[<span class="hljs-string">"charlie"</span>]
    fmt.Println(age) <span class="hljs-comment">// 0</span>
    

    Here’s what happens under the hood:

    1. Go computes the hash of the key ("alice") to find which bucket in the hash table to look in. A bucket is a small container within the hash table that holds one or more key-value pairs. When multiple keys hash to the same bucket, they are stored together inside it.

    2. It searches the bucket for the key.

    3. If the key exists, Go returns the associated value (23 in this case).

    4. If the key doesn’t exist, Go returns the zero value of the map’s value type (0 for int, "" for string, nil for a pointer or slice, and so on).

    To distinguish between a key that doesn’t exist and a key whose value happens to be the zero value of the map’s value type, Go provides a second return value when you access a map. Normally, m[key] just returns the value. But if you write:

    value, ok := m[key]
    
    • value is the map value for that key (or the zero value if the key is missing).

    • ok is a boolean that is true if the key exists in the map, and false if it does not.

    You need this because some types have a zero value that is valid in your application. For example, consider a map of usernames to ages:

    m := <span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]<span class="hljs-keyword">int</span>{
        <span class="hljs-string">"alice"</span>: <span class="hljs-number">23</span>,
        <span class="hljs-string">"bob"</span>:   <span class="hljs-number">0</span>,
    }
    

    If you try to access "bob" or "charlie" without the second return value:

    fmt.Println(m[<span class="hljs-string">"bob"</span>])     <span class="hljs-comment">// 0</span>
    fmt.Println(m[<span class="hljs-string">"charlie"</span>]) <span class="hljs-comment">// 0</span>
    

    Both print 0, so you can’t tell whether "charlie" is missing or "bob" actually has age 0. Using the second return value solves this:

    age, ok := m[<span class="hljs-string">"charlie"</span>]
    <span class="hljs-keyword">if</span> !ok {
        fmt.Println(<span class="hljs-string">"Key not found"</span>)
    }
    

    Here, ok is false for "charlie" but would be true for "bob". This is a common pattern in Go to safely handle map lookups.

    How to Iterate Over a Map

    Iterating over a map means going through all key-value pairs in the map, one at a time. You do this with a for loop and the range keyword:

    <span class="hljs-keyword">for</span> key, value := <span class="hljs-keyword">range</span> m {
        fmt.Printf(<span class="hljs-string">"%s: %dn"</span>, key, value)
    }
    

    What’s happening here:

    • range m produces each key in the map, one by one.

    • The loop assigns the current key to key and the corresponding value to value.

    • Inside the loop, you can use key and value to process, print, or modify data.

    Iterating over a map is useful whenever you need to:

    • Process all entries in the map (for example, compute a total, filter items, or apply a transformation).

    • Print or display data in key-value format (like logging user ages or product prices).

    • Perform aggregate operations, such as counting, summing, or finding the maximum/minimum value.

    Important note: Map iteration order in Go is randomized: each loop may produce keys in a different order. This prevents you from relying on insertion order. If you need a deterministic order, you can collect the keys into a slice, sort them, and iterate over the sorted keys.

    Inner Representation of Maps

    Go maps are implemented as hash tables with buckets:

    • Keys are hashed to decide which bucket they go into.

    • Each bucket holds multiple key-value pairs.

    • When a bucket gets too full, Go splits it into two (similar to dynamic resizing).

    • That’s why map operations are usually O(1), but not guaranteed constant time.

    Just keep in mind that maps are not safe for concurrent writes. If multiple goroutines write to a map at the same time, you’ll get a runtime panic. Use sync.Mutex or sync.RWMutex to protect map access in concurrent scenarios.

    If you’re interested in how different hash map implementations work under the hood, check out my article on hash maps.

    Arrays vs. Slices vs. Maps

    Here’s a quick comparison of the feature set of collection types in Go:

    FeatureArraysSlicesMaps
    SizeFixedDynamicDynamic
    TypeValue typeReference typeReference type
    Zero valueArray of zero valuesNil sliceNil map
    LengthKnown at compile timeKnown at runtimeN/A
    IndexingBy integerBy integerBy key
    Internal repContiguous memory blockHeader (ptr, len, cap) + arrayHash table with buckets
    Use casesLow-level, fixed-size dataMost lists, sequencesLookups, dictionaries

    Mini Project: Shopping Cart Totals

    Let’s combine slices and maps into a practical program: given a list of items and their prices, compute the total cost of all items.

    The list of items is represented as a slice of strings, and the prices are stored in a map. The key is the item name, and the value is the price:

    <span class="hljs-keyword">package</span> main
    
    <span class="hljs-keyword">import</span> <span class="hljs-string">"fmt"</span>
    
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
        items := []<span class="hljs-keyword">string</span>{<span class="hljs-string">"apple"</span>, <span class="hljs-string">"banana"</span>, <span class="hljs-string">"orange"</span>}
        prices := <span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]<span class="hljs-keyword">float64</span>{
            <span class="hljs-string">"apple"</span>:  <span class="hljs-number">0.99</span>,
            <span class="hljs-string">"banana"</span>: <span class="hljs-number">0.59</span>,
            <span class="hljs-string">"orange"</span>: <span class="hljs-number">0.79</span>,
        }
    
        <span class="hljs-keyword">var</span> total <span class="hljs-keyword">float64</span>
        <span class="hljs-keyword">for</span> _, item := <span class="hljs-keyword">range</span> items {
            total += prices[item]
        }
        fmt.Printf(<span class="hljs-string">"Total cost: $%.2fn"</span>, total)
    }
    
    Total cost: $2.37
    

    This short example shows the synergy between slices (to hold the item names) and maps (to look up prices).

    Practice Challenge

    Write a function that takes a slice of integers and returns a new slice with duplicates removed. (Solution below.)

    Conclusion

    Go keeps things simple: with arrays, slices, and maps, you can model almost all everyday data problems.

    • Arrays: fixed size, contiguous memory, rarely used directly.

    • Slices: flexible, built on top of arrays, your go-to for ordered collections.

    • Maps: hash tables for key–value lookups.

    You now have the tools to confidently handle collections in Go. The next step? Try writing a small project where you read data from a file, store it in slices, and process it into maps for quick lookups. That’s how Go developers handle real-world data.

    Practice Challenge Solution

    To remove duplicates from a slice, we can keep track of the values we’ve seen in a map and build a new slice containing only the first occurrence of each element:

    <span class="hljs-keyword">package</span> main
    
    <span class="hljs-keyword">import</span> <span class="hljs-string">"fmt"</span>
    
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">removeDuplicates</span><span class="hljs-params">(intSlice []<span class="hljs-keyword">int</span>)</span> []<span class="hljs-title">int</span></span> {
        seen := <span class="hljs-built_in">make</span>(<span class="hljs-keyword">map</span>[<span class="hljs-keyword">int</span>]<span class="hljs-keyword">bool</span>) <span class="hljs-comment">// to track seen integers</span>
        result := []<span class="hljs-keyword">int</span>{}
        <span class="hljs-keyword">for</span> _, v := <span class="hljs-keyword">range</span> intSlice {
            <span class="hljs-keyword">if</span> !seen[v] { <span class="hljs-comment">// if we haven't seen this integer yet, set it to seen and add it to the result</span>
                seen[v] = <span class="hljs-literal">true</span>
                result = <span class="hljs-built_in">append</span>(result, v)
            }
        }
        <span class="hljs-keyword">return</span> result
    }
    
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
        s := []<span class="hljs-keyword">int</span>{<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>, <span class="hljs-number">4</span>, <span class="hljs-number">5</span>}
        s = removeDuplicates(s)
        fmt.Println(s) <span class="hljs-comment">// [1 2 3 4 5]</span>
    }
    

    How it works:

    • seen keeps track of numbers that have already been added.

    • result collects unique numbers as we iterate.

    • For each element in the input slice, if it hasn’t been seen, we mark it and append it to result.

    • Finally, result contains only unique values.

    Source: freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More 

    Facebook Twitter Reddit Email Copy Link
    Previous ArticleScaling Up Reinforcement Learning for Traffic Smoothing: A 100-AV Highway Deployment
    Next Article How to Build an Upload Service in Flutter Web with Firebase

    Related Posts

    Development

    How to focus on building your skills when everything’s so distracting with Ania Kubów [Podcast #187]

    September 6, 2025
    Development

    Introducing freeCodeCamp Daily Python and JavaScript Challenges – Solve a New Programming Puzzle Every Day

    September 6, 2025
    Leave A Reply Cancel Reply

    For security, use of Google's reCAPTCHA service is required which is subject to the Google Privacy Policy and Terms of Use.

    Continue Reading

    People are using ChatGPT to write their text messages – here’s how you can tell

    News & Updates

    CVE-2025-47892 – Apache HTTP Server Cross-Site Request Forgery

    Common Vulnerabilities and Exposures (CVEs)

    CVE-2025-46804 – Screen Information Disclosure Vulnerability

    Common Vulnerabilities and Exposures (CVEs)

    CVE-2025-4575 – OpenSSL -addreject Option Truncation Vulnerability

    Common Vulnerabilities and Exposures (CVEs)

    Highlights

    CVE-2025-5616 – PHPGurukul Online Fire Reporting System SQL Injection Vulnerability

    June 4, 2025

    CVE ID : CVE-2025-5616

    Published : June 4, 2025, 11:15 p.m. | 22 minutes ago

    Description : A vulnerability was found in PHPGurukul Online Fire Reporting System 1.2. It has been rated as critical. Affected by this issue is some unknown functionality of the file /admin/profile.php. The manipulation of the argument mobilenumber leads to sql injection. The attack may be launched remotely. The exploit has been disclosed to the public and may be used. Other parameters might be affected as well.

    Severity: 6.3 | MEDIUM

    Visit the link for more details, such as CVSS details, affected products, timeline, and more…

    ABBYY’s new OCR API enables developers to more easily extract data from documents

    April 15, 2025

    Responsible AI in action: How Data Reply red teaming supports generative AI safety on AWS

    April 29, 2025

    CVE-2025-5622 – D-Link DIR-816 Wireless Stack-Based Buffer Overflow

    June 4, 2025
    © DevStackTips 2025. All rights reserved.
    • Contact
    • Privacy Policy

    Type above and press Enter to search. Press Esc to cancel.