Go > Collections > Arrays and Slices > Slice internals and sharing

Understanding Slice Sharing in Go

This example demonstrates how slices in Go share the underlying array. Modifying a slice might affect other slices if they share the same array. It's crucial to understand this behavior to avoid unexpected side effects.

The Basic Concept: Slice Sharing

Slices in Go are not independent data structures. They are essentially views into an underlying array. Multiple slices can point to the same array. Therefore, changes made through one slice might be visible through another if they share the same underlying array. This sharing is a key aspect of slice efficiency, but it can also lead to subtle bugs if not understood properly.

Code Example: Demonstrating Shared Underlying Array

This code creates an array arr and then creates two slices, slice1 and slice2, that overlap. When we modify slice1[0], the change is also reflected in the original array arr and in slice2 because they all point to the same underlying data.

package main

import "fmt"

func main() {
    arr := [5]int{1, 2, 3, 4, 5}
    slice1 := arr[0:3]
    slice2 := arr[1:4]

    fmt.Println("Original array:", arr)
    fmt.Println("Slice 1:", slice1)
    fmt.Println("Slice 2:", slice2)

    slice1[0] = 100

    fmt.Println("\nAfter modifying slice1:")
    fmt.Println("Original array:", arr)
    fmt.Println("Slice 1:", slice1)
    fmt.Println("Slice 2:", slice2)
}

Explanation of the Output

The output of the program demonstrates that modifying slice1 alters the underlying array and subsequently affects slice2. This is because slice1 and slice2 are backed by the same array. The key takeaway is that slice operations can have unexpected side effects if you are not aware of the sharing mechanism. Careful consideration should be given to whether copying the slice data is necessary to avoid unintended modifications.

Avoiding Unintended Modifications: Copying Slices

To avoid modifying the original array or other slices that share the same underlying array, you can create a new slice and copy the data. The copy function creates a new slice with the same length and capacity as the source slice and copies the elements. Now, modifying slice1 will not affect slice2 because they have different underlying arrays.

package main

import "fmt"

func main() {
    arr := [5]int{1, 2, 3, 4, 5}
    slice1 := arr[0:3]
    slice2 := make([]int, len(slice1))
    copy(slice2, slice1)

    fmt.Println("Original array:", arr)
    fmt.Println("Slice 1:", slice1)
    fmt.Println("Slice 2 (copied):", slice2)

    slice1[0] = 100

    fmt.Println("\nAfter modifying slice1:")
    fmt.Println("Original array:", arr)
    fmt.Println("Slice 1:", slice1)
    fmt.Println("Slice 2 (copied):", slice2)
}

When to use them

Use slice sharing when you want to efficiently pass around portions of a large array without incurring the cost of copying the data. However, be extremely cautious when modifying shared slices, as changes can affect other parts of your code. Copy slices when you need to ensure that modifications to one slice do not affect others.

Memory footprint

Sharing slices reduces memory footprint because you're not duplicating the underlying data. Copying slices increases memory footprint because you're creating a new copy of the data.

Interview Tip

A common interview question is about slice sharing and how to avoid unintended side effects. Be prepared to explain the concept of underlying arrays, slice headers (pointer, length, capacity), and the use of the copy function.

Best Practices

  • Be aware of slice sharing, especially when passing slices to functions or methods.
  • Use the copy function when you need to modify a slice without affecting the original data.
  • Consider the trade-offs between memory usage and potential side effects when choosing between sharing and copying slices.

Real-Life Use Case Section

Image processing is a good example. Imagine processing a large image. You might use slices to represent different regions of the image. By passing slice to different image processing operations, without copy operation, you can increase performance.

FAQ

  • What is the difference between length and capacity of a slice?

    The length of a slice is the number of elements currently present in the slice. The capacity is the maximum number of elements the slice can hold before it needs to be reallocated. The capacity is determined by the size of the underlying array from the slice's starting index to the end of array. When you append to a slice and its length reaches its capacity, a new, larger array is allocated, and the slice's underlying array is updated to point to the new array. This involves copying the existing elements to the new location.
  • How can I create a slice that doesn't share its underlying array with another slice?

    Use the copy function to create a new slice with its own underlying array. Alternatively, you can use the make function to create a new slice with a specified length and capacity, which will allocate a new underlying array.