Growing Slices
Slices grow with the built-in append — and one idiom you should burn in
immediately:
package main
import "fmt"
func main() {
var s []int
s = append(s, 1)
s = append(s, 2, 3)
fmt.Println(s) // [1 2 3]
}
Always write s = append(s, ...) — reassign the result. append may
return a different slice than the one you passed in, so calling it without
reassigning silently throws the addition away.
Why append can return a new slice
Remember: a slice is a view over a backing array, and cap(s) says how much
room that array has. When you append:
- Room left (
len < cap): the element is written into the existing backing array and the returned slice is one longer. - Full (
len == cap): Go allocates a bigger array (roughly doubling the capacity), copies everything over, and returns a slice pointing at the new array.
s := make([]int, 0, 2) // len 0, cap 2
s = append(s, 1, 2) // fits — same array
s = append(s, 3) // full! new array, cap grows
fmt.Println(len(s), cap(s)) // 3 4
That make([]T, len, cap) form is how you pre-size a slice: if you know
you'll append a thousand elements, make([]int, 0, 1000) avoids every
intermediate reallocation. make([]int, 5) instead gives you five ready
zero-valued elements.
The shared-backing gotcha
The "sometimes same array, sometimes new" behavior has a famous consequence. If a slice still has spare capacity, appending to a sub-slice of it writes into memory the original can see:
a := []int{1, 2, 3, 4}
b := a[:2] // len 2, cap 4 — same backing array
b = append(b, 99) // fits in spare capacity...
fmt.Println(a) // [1 2 99 4] — a changed!
Nothing crashed and no warning fired: append found room at index 2 and used
it, clobbering a[2]. The fix, when you need independence, is to copy.
copy: explicit, honest duplication
The built-in copy(dst, src) copies elements between slices, returning how
many it copied (the smaller of the two lengths). The destination must already
have length:
src := []int{1, 2, 3}
dst := make([]int, len(src))
copy(dst, src)
dst[0] = 99
fmt.Println(src, dst) // [1 2 3] [99 2 3]
This is Go's design showing again: sharing is the cheap default, copying is a
visible, deliberate act. When a function promises to return a new slice
without touching its input, the implementation is exactly this shape — make
a result of the right length, fill it, return it.
The exercise asks for precisely that contract: reverse must return a new
reversed slice and leave the input byte-for-byte intact. The hidden test
checks both.
Practice
1. Reverse, Don't Mutate
BeginnerImplement
reverseso it returns a new slice with the elements ofnumsin reverse order:func reverse(nums []int) []intFor example,
reverse([]int{1, 2, 3})returns[3 2 1].The contract has two parts, and the hidden tests check both:
- The returned slice is reversed.
numsitself is unchanged afterwards — no in-place swapping.
Allocate the result with
make([]int, len(nums))and fill it: input elementilands at output indexlen(nums)-1-i. (Appending elements while walking the input backwards works too.) An empty ornilinput should simply produce a length-zero result.package main import "fmt" // reverse returns a new slice with the elements of nums in reverse order. // It does not modify nums. func reverse(nums []int) []int { // TODO: implement return nil } func main() { fmt.Println(reverse([]int{1, 2, 3})) }Sign in to write and run your solution in the browser.
Sign in for hints and the solution.