ProgrammingMiddle Go developer

How are slices and arrays structured in Go? Why is it important to distinguish their semantics when passing to functions and working with memory?

Pass interviews with Hintsage AI assistant

Answer.

Slices and arrays are among the most commonly used data structures in Go. Despite similar syntax, the difference in their structure and behavior can lead to performance, memory, and semantic errors.

Background:

Go has chosen an explicit memory management model from the very beginning, in which arrays are a sequence of elements of fixed size, while slices are a dynamic view on the array. This separation allows controlling the cost of operations and code behavior.

Problem:

The main challenge is the confusion between array copying (value semantics) and the "referential nature" of slices. Errors often occur when passing these types to functions and modifying values, leading to unexpected side effects.

Solution:

Arrays are always copied when passed by value: the function receives a copy of the entire content. A slice, however, is a small structure (header) that contains a pointer to the array, length, and capacity. Changes within the slice are visible outside if the content of the array is modified (but not if the slice itself is redirected to a new array within the function).

Code example:

func updateArray(arr [3]int) { arr[0] = 10 } func updateSlice(slc []int) { slc[0] = 10 } func main() { a := [3]int{1,2,3} b := []int{1,2,3} updateArray(a) updateSlice(b) fmt.Println(a) // [1 2 3] fmt.Println(b) // [10 2 3] }

Key features:

  • Array is a value type, fully copied when passed (size is compiled into the type).
  • Slice is a wrapper structure: pointer to the array, length, and capacity.
  • Efficiency of passing slices: operation involves copying only the header, not the entire content (but changes inside are visible from all "views").

Trick questions.

What happens if you change the length of the slice inside a function? Will it affect the original slice?

No, changing the length of the slice (for example, using slc = slc[:2]) inside the function will only affect the local copy of the header. The original slice will remain unchanged.

Does the append operator return the modified slice in the same memory area?

Not necessarily. If there isn't enough capacity, a new array is created and a pointer to the new array is returned. The old array will remain unaltered.

Code example:

s := []int{1,2,3} s2 := append(s, 4, 5, 6) // s2 may be in a new memory area

Can you assign an array to a slice or vice versa?

No. []int and [5]int are different types. To pass an array as a slice, you need to use the conversion arr[:]. The reverse is not possible.

Common mistakes and anti-patterns

  • Copying an array and expecting changes to be visible outside the function.
  • Changing the length of a slice inside a function and expecting this to reflect outside the function.
  • Memory leaks through "long" backing arrays of slices stored for small views.
  • Errors when using append in a loop — new arrays may be created, while old slices remain "dangling".

Real-life example

Negative case

A junior developer implemented a table update function by passing an array to the function with the expectation that the changes would be applied to the original array. The modifications were not "saved".

Pros:

  • Code was easy to read and tested in small examples.

Cons:

  • Bugs on real data, diagnostic difficulties — change was hidden.

Positive case

The function accepted a slice and explicitly returned a modified copy, enhancing the predictability of the effect. All changes were conscious, data did not "leak" and was not modified implicitly.

Pros:

  • Simplicity and predictability of behavior.
  • No "magic" with copying or modification.

Cons:

  • Need to remember where and when pointers and slices are passed to avoid retaining unnecessary memory (backing array).