In Go, what is the difference between an array `[5]int` and a slice `[]int`?
answer
- think about what assignment actually copies
- one of the two has a fixed size
- [3]int and [4]int are different types
- the other is a view with a capacity
basics
~20 sAn array's length is part of its type, and assigning or passing one copies every element. A slice is a resizable view onto a backing array, carrying a length and a capacity; copying a slice shares that array.
solid answer
~50 sIn Go, `[5]int` is an array: the length 5 belongs to the type, so `[5]int` and `[6]int` are different types, `len` on it is a compile-time constant, and assigning it or passing it to a function copies all five elements. `[]int` is a slice: a value that refers to a backing array and carries a length and a capacity, so its length is a run-time property and can differ from one value to the next. Copying a slice value copies that reference, which means two slices can see each other's writes to the shared elements. Slicing an addressable array with `a[:]` produces a slice over the array's own storage, which is the usual way to hand an array to code that expects a slice. Almost all Go APIs take slices; arrays show up where a size is fixed and known, such as a `[32]byte` digest.
code
go · 9 linesa := [3]int{1, 2, 3}
b := a // copies all three elements
b[0] = 99
// a[0] is still 1
s := []int{1, 2, 3}
t := s // copies the slice value, not the elements
t[0] = 99
// s[0] is now 99go deeper
Be ready to state the two headline facts without hesitating: the length is part of an array's type, and assigning or passing an array copies every element while assigning a slice shares the elements. Know that a[:] turns an array into a slice.
Expect to explain the mechanics: a slice value carries a length and a capacity alongside its reference to a backing array, an array's len is a compile-time constant, and slicing an array requires the array to be addressable.
Show the production instinct: spot a large array passed by value in a hot path, and know when a fixed size in the type is a real guarantee rather than an inconvenience. Be able to explain aliasing consequences to a reviewer in one sentence.
Frame it as an API question. Fixed-size arrays put a length guarantee in the type at the cost of copying and conversion friction at every boundary; slices are the lingua franca of Go APIs. Decide which your package exposes before other teams depend on it.
## Two different types, not two spellings of one Go's `[5]int` and `[]int` look similar and behave almost nothing alike. Understanding the difference is the first real hurdle for anyone arriving from a language where "array" means one thing. ### An array is a value with its length in its type An array type is written `[N]T`, where `N` is a constant expression. That `N` is part of the type's identity: `[3]int` and `[4]int` are two distinct types, and no assignment or argument passing converts between them. Because the length is fixed in the type, `len(a)` for an array `a` is a constant the compiler folds away, and there is no way for an array's length to change at run time. An array is also an ordinary value, in exactly the way an `int` or a struct is. Assignment copies it: ```go var a [3]int b := a b[0] = 99 // a[0] is still 0 ``` The same is true at a call boundary: `func f(x [3]int)` receives a copy of all three elements, and whatever `f` writes into `x` is invisible to the caller. If you want the callee to write through to the caller's array, you pass `*[3]int` or, far more commonly, a slice. The storage for an array lives wherever the array itself lives. A local array sits in the function's frame unless the compiler decides it must escape; an array field inside a struct sits inline in that struct, with no separate allocation and no pointer to follow. ### A slice is a view onto a backing array A slice type is written `[]T`, with no length. A slice value refers to a contiguous run of elements in some backing array and carries two numbers with it: a **length** (`len(s)`, how many elements you may index) and a **capacity** (`cap(s)`, how many elements are available in the backing array from the slice's start). Different slice values of the same type routinely have different lengths, so the length is data, not type. Copying a slice value copies the reference along with the two numbers; it does not copy elements: ```go s := []int{1, 2, 3} t := s t[0] = 99 // s[0] is now 99 ``` That is why passing a slice to a function is cheap regardless of how many elements it holds, and why a function that writes `s[0] = x` is visible to its caller. Growing a slice is a separate matter with its own rules; what matters here is that an array can never grow, while a slice's length is a run-time value. ### Moving between them Slicing an array produces a slice over that array's storage: given `var arr [4]byte`, the expression `arr[:]` is a `[]byte` with length 4 and capacity 4, sharing `arr`'s bytes. A later write through the slice changes `arr`. The array being sliced must be addressable, which is why you can slice a variable but not the array returned directly by a function call. In the other direction, a slice can be converted to an array (`[4]byte(b)`, which copies) or to an array pointer (`(*[4]byte)(b)`, which aliases), and both panic if the slice is too short. ### Why the standard library almost always uses slices A function that takes `[]byte` accepts input of any length, accepts a sub-range of a larger buffer without copying, and can be handed the storage of an array with `[:]`. A function that takes `[32]byte` accepts exactly 32 bytes and copies them on every call. So slices are the general-purpose sequence type, and arrays appear where the size is genuinely fixed by a specification: a 32-byte digest, a 16-byte network address, a fixed-size frame read from a device. ### The mistakes to avoid - Treating an array parameter as a reference, as it would be in C or Java, and being surprised that the caller sees nothing. - Treating a slice as a container that owns its elements, and being surprised that two slice variables affect each other. - Assuming `[3]int` will convert to `[4]int` or to `[]int` implicitly; neither happens, and the slice case needs an explicit `a[:]`. - Passing a large array such as `[4096]byte` by value in a hot path and paying a 4 KB copy per call without noticing.
- If passing an array copies it, how do you let a function modify the caller's array?Two ways. Pass a pointer, `func f(a *[3]int)`, and index through it as `a[0]` — Go dereferences the array pointer for you. Or, far more idiomatically, pass a slice over the array's storage: `f(arr[:])` with `func f(s []int)`. The slice refers to the same elements, so the callee's writes land in the caller's array.
- What does `len` report for an array versus a slice, and when is it known?For an array the value comes from the type, so `len(a)` on a `[5]int` is the constant 5 — usable anywhere a constant is, including an array length itself. For a slice it is run-time data stored with the slice value, and it can differ between two values of the same slice type or change as the program reslices.
- Why do standard-library functions take `[]byte` rather than a fixed-size byte array?A `[]byte` parameter accepts any length, accepts a sub-range of a bigger buffer with no copy, and lets the callee write through to the caller's storage — all of which reading and writing APIs need. A `[N]byte` parameter would fix the length in the signature and copy N bytes on every call.
saying these in an interview costs you the question
- Says a Go array is just a slice with a fixed size
- Thinks passing an array to a function passes a reference
- Believes [3]int and [4]int are the same type
- Cannot say what a slice's capacity means
- Claims arr[:] copies the array's elements