Array versus Slice
Fast recall
Quick revision
Core answer
[n]T with the length in the type; slice is []T,Interview-ready guide
Interview answer
An array and a slice differ at the type level, and every practical
difference follows from that. An array type includes its length — [3]int
— so the length is fixed at compile time and the value is the storage
itself. A slice type is just []int: at runtime a slice is a small
structure with three fields — a pointer into a backing array, a length,
and a capacity. The elements live in the backing array; the slice is a
window onto them.
Assignment is where the difference becomes visible:
x := [3]int{10, 20, 30}
y := x // copies the three elements
y[0] = 7 // x is unchanged
s := []int{1, 2, 3}
t := s // copies the header — same backing storage
t[0] = 9 // s[0] is now 9 tooThe array pair prints [10 20 30] and [7 20 30]. The slice pair both see
the write, because t and s are two windows over one block.
Comparability follows the same logic. Two arrays of the same type compare
element by element — [3]int{1,2,3} == [3]int{1,2,3} is true — because
the compiler knows exactly how many elements to check. Slices cannot be
compared with == (except s == nil) because lengths and backing storage
change at runtime; equality would be ambiguous, so the language forbids
it. Their zero values differ the same way: an uninitialized array is a
filled block ([0 0 0]), an uninitialized slice is nil.
Practically: arrays are chosen deliberately when the bound is meaning or copy protection is wanted; slices are the default for anything that grows or travels between functions — passing a slice copies only the three-word header, not the elements.
Array versus Slice
Direct Answer
Type shape: array is [n]T with the length in the type; slice is []T,
Deep Dive & Architecture
The cleanest way to hold the comparison is to see the slice header as a real data structure, because then "why do these behave differently" stops being a memorized table and becomes mechanics.
What each one actually is
Draw an array as the elements in a row, inline in the variable. Draw a
slice as a small three-field record on the left — ptr, len 2, cap 3 —
with an arrow to a row of elements elsewhere. Both pictures are literally
true at the machine level, and they predict every behavior:
y := xfor arrays copies the row → independent values.t := sfor slices copies the record → two records, one row.==for arrays compares two known-length rows → well defined.==for slices would compare two records pointing to possibly-shared rows → ambiguous, banned.
The experiment that fixes it in memory
The single most valuable snippet in this family, executed:
s := []int{1, 2, 3}
t := s[:2] // len 2, cap 1 remaining — same backing array
t = append(t, 42)
fmt.Println(s, t) // [1 2 42] [1 2 42]
append had capacity available (cap 3, len 2), so it wrote 42 straight
into s's third slot. The source slice changed without anyone touching
it. This is not a bug in Go — it is what header-sharing means, and it is
why the follow-up questions about aliasing, capacity growth, and safe
copying exist. The array version of this snippet cannot happen: copies
have no shared row to write through.
Choosing between them, in one pass
If the length is a fact about the data (digest sizes, dimensions, fixed protocol fields) or you want assignment to isolate — array. If length is a variable of the problem, data must grow, or functions should share a view — slice. In practice the second case dominates, which is why Go code shows slices everywhere and arrays in the specific niches where their guarantees pay.
Code Example
x := [3]int{10, 20, 30}
y := x
y[0] = 7 // x unchanged
s := []int{1, 2, 3}
t := s[:2]
t = append(t, 42) // s is now [1 2 42]
Common Mistakes & Pitfalls
- Describing slices as 'dynamic arrays' without the header model — which makes append aliasing inexplicable.
- Claiming slices can be compared with == (they cannot, except nil).
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.