Go slices interview questions, with answers
Slices are the most used data structure in Go and the one interviewers probe hardest, because their behaviour follows from a detail that is easy to skip: a slice is a small header that points into an array, not the array itself. Whether two slices see each other's writes, whether append changes the original, and why a function can modify a slice's elements but not its length all follow from that one fact.
The questions below build it up in order: arrays, the slice header, length and capacity, how append grows a slice, and the copying, limiting and removing operations that avoid the traps. Every output was produced by running the code on Go 1.27. Then take the free Go diagnostic — ten questions across every Go topic in the bank — to see which of these you can explain but not yet predict.
The questions, with answers
1.What is the difference between an array and a slice in Go?
In short: An array has a fixed length that is part of its type and is a value; a slice is a view of an array with a length that can change.
An array's length is part of its type, so [4]int and [5]int are different types, and an array is a value: assigning it or passing it to a function copies every element. That makes arrays rare in everyday code, useful mainly for fixed-size data such as a hash digest. A slice, written []int, has no length in its type. It is a small descriptor pointing into an underlying array, and copying a slice copies only that descriptor, so both copies see the same elements. Slices are what functions take and return, what append grows and what range usually iterates over; slicing an array, as nums[1:3] below, is one way to create one.
nums := [4]int{10, 20, 30, 40} part := nums[1:3] fmt.Println(len(nums), part) // 4 [20 30]
2.What are a slice's length and capacity?
In short: The length is how many elements the slice holds; the capacity is how many elements its backing array has from the slice's start to the array's end.
A slice header has three fields: a pointer to the first element it can see, a length and a capacity. len(s) is the number of elements you can index, from s[0] to s[len(s)-1]. cap(s) counts from the same first element to the end of the backing array, so it says how far the slice can grow without new memory. make([]int, 2, 6) creates a six-element array and a slice of the first two. Re-slicing moves the window: s[1:] drops one element from the front, reducing both length and capacity, while s[:4] extends the length into spare capacity, exposing elements that were already in the array.
s := make([]int, 2, 6) fmt.Println(len(s), cap(s)) // 2 6 t := s[1:] fmt.Println(len(t), cap(t)) // 1 5 u := s[:4] fmt.Println(len(u), u) // 4 [0 0 0 0]
3.How does append grow a slice in Go?
In short: If there is spare capacity, append writes into it; otherwise it allocates a larger array, copies the elements and returns a slice of the new array.
append returns a slice, which is why it is always written s = append(s, x). While the backing array has room, append stores the new elements in it and returns a longer slice over the same array. When the capacity is used up, it allocates a bigger array, copies the existing elements across and returns a slice pointing at the new one; the old array is left behind. The growth is roughly doubling for small slices and around 1.25 times for large ones, so appending n elements costs amortised constant time per element. The cap values below come from Go 1.27's growth rule; when the final size is known, make([]T, 0, n) avoids the reallocations entirely.
s := []int{} for i := range 5 { s = append(s, i) fmt.Print(cap(s), " ") } fmt.Println() // 4 4 4 4 8
4.Why can changing one Go slice change another?
In short: Slices made from the same array share its elements, so a write through one is visible through every slice whose window covers that element.
Slicing never copies elements: row := grid[2:5] creates a new header pointing into grid's array. Writing row[0] therefore changes grid[2], because they are the same memory. The same sharing happens when a slice is passed to a function or assigned to another variable. It is what makes slicing cheap, and also what makes it dangerous: appending to a sub-slice that still has capacity overwrites the elements that follow it in the original. When a slice must be independent, copy it with slices.Clone or copy, or cap it with a full slice expression, so that its first append is forced to allocate.
grid := []int{1, 2, 3, 4, 5, 6} row := grid[2:5] row[0] = 30 fmt.Println(grid) // [1 2 30 4 5 6]
5.How do you copy a slice in Go?
In short: Use copy(dst, src), which copies min(len(dst), len(src)) elements and returns that count, or slices.Clone, which returns an independent copy.
Assigning a slice copies only its header, so both variables still share one array. copy(dst, src) copies elements between two slices and returns how many it copied, the smaller of the two lengths, so the destination must already have the length you want, typically from make([]T, len(src)). It copies correctly even when the two slices overlap. slices.Clone(s), since Go 1.21, allocates and copies in one call and is the clearest way to get an independent slice. After copying, writes to one slice no longer affect the other, as the code shows with dst and src.
src := []int{7, 8, 9} dst := make([]int, 2) n := copy(dst, src) dst[0] = 0 fmt.Println(n, dst, src) // 2 [0 8] [7 8 9]
6.What is a full slice expression, s[low:high:max]?
In short: It is a slice expression with a third index that sets the new slice's capacity to max minus low, so later appends cannot overwrite the original array.
A normal slice expression s[low:high] gives the new slice all of the remaining capacity, up to the end of the backing array, so appending to it writes into elements that the original may still be using. The three-index form s[low:high:max] limits the capacity to max - low. With no spare capacity, the first append to the new slice has to allocate a fresh array, leaving the original untouched, as base shows below. Libraries use it when they hand out a slice of an internal buffer and must not let the caller's appends overwrite the rest of it.
base := []int{1, 2, 3, 4} head := base[0:2:2] head = append(head, 99) fmt.Println(base, head) // [1 2 3 4] [1 2 99]
7.How do you remove an element from a slice in Go?
In short: Shift the later elements left and shorten the slice, with append(s[:i], s[i+1:]...) or slices.Delete, which also clears the freed slot.
Go has no built-in remove, because removal from the middle means moving every later element. The classic idiom is s = append(s[:i], s[i+1:]...), which copies the tail one place left within the same array and returns a slice one element shorter. Since Go 1.21, slices.Delete(s, i, j) removes the elements from i up to j in the same way and zeroes the slots left at the end, so a slice of pointers does not keep deleted objects alive. Both modify the original array, so any other slice sharing it sees the shifted elements. When order does not matter, moving the last element into the gap is faster.
xs := []string{"a", "b", "c"} xs = slices.Delete(xs, 1, 2) fmt.Println(xs, len(xs)) // [a c] 2
How the diagnostic asks it
One question from the Go bank, exactly as a sitting would show it. The bank has 5 on slices & arrays and 30 across Go.
What does this Go code print?
s := []int{1, 2, 3, 4} t := s[1:3] t[0] = 20 fmt.Println(s)
- 1[1 20 3 4]correct
- 2[1 2 3 4]
- 3[20 2 3 4]
- 4[1 20 3]
A slice expression does not copy anything: t is a new slice header pointing into the same backing array as s, starting at s's index 1, with length 2. So t[0] is the same element as s[1], and writing 20 there changes s, which prints [1 20 3 4]. [1 2 3 4] assumes slicing copies, which only copy() or append onto a new slice does. [20 2 3 4] maps t[0] to s[0], forgetting the offset. [1 20 3] confuses s with t's range, but s's length is unchanged at 4. Use copy, or slices.Clone, when the new slice must be independent.
Measure it
Reading answers tells you what’s true. A diagnostic tells you what you get wrong.
10 Go questions across its topics, easy to hard, about fifteen minutes. You get a readiness figure with the arithmetic shown, the topics you missed named, and a practice set sized for today. Free: 1 diagnostic a month and 15 problems a day. No card.