The Array–Slice Difference
Fast recall
Quick revision
Core answer
[3]int.Interview-ready guide
Interview answer
The difference begins in the type system. An array type includes its
length — [3]int — so the value is a fixed block and the compiler knows
its shape everywhere. A slice type is []int and carries no length at
all; at runtime it is a small three-field header pointing into a backing
array, with its own length and capacity. Everything practical follows from
this split.
Copying is the clearest consequence. Assigning an array copies every
element — y := x produces two independent values. Assigning a slice
copies only the header — t := s leaves two headers pointing at the same
elements, so writes through either are visible in both:
x := [3]int{10, 20, 30}
y := x
y[0] = 7
fmt.Println(x, y) // [10 20 30] [7 20 30] — independent
s := []int{10, 20, 30}
t := s
t[0] = 7
fmt.Println(s, t) // [7 20 30] [7 20 30] — shared storageComparison rules differ for the same reason. Two arrays of one type
compare element-wise ([3]int{1,2,3} == [3]int{1,2,3} is true). A slice
cannot be the operand of == (except s == nil) because a meaningful
equality would have to define what it means for two windows over shared,
growable storage — so Go declines. Zero values match the pictures too: an
uninitialized array is a full block of zeroed elements, an uninitialized
slice is nil.
In practice: use the array when the bound is meaning (a [32]byte
digest) or isolation is wanted; use the slice — the default — whenever
length varies or the data must travel between functions cheaply, since
passing a slice copies only the header.
The Array–Slice Difference
Direct Answer
An array is a fixed-length value whose length lives in its type: [3]int.
Deep Dive & Architecture
Hold one picture and this whole topic stops being trivia: an array is the storage; a slice is a window onto storage.
The picture
array x [3]int slice s []int
┌────┬────┬────┐ ┌──────────────┐
│ 10 │ 20 │ 30 │ │ ptr ────────────▶ ┌────┬────┬────┐
└────┴────┴────┘ │ len: 3 │ │ 10 │ 20 │ 30 │
(the value │ cap: 3 │ └────┴────┴────┘
itself) └──────────────┘ (backing array)
y := x copies the row → two rows. t := s copies the window record →
two windows, one row.
Why == is legal for one and illegal for the other
Comparing two [3]int values is a closed question — the compiler emits
exactly three element comparisons. Comparing two slices is not closed:
their lengths can differ, their storage can overlap, and after any
append the answer could change. Instead of picking a semantics, Go
makes slice equality a compile error except for s == nil and sends you
to reflect.DeepEqual or slices.Equal when you genuinely need it — with
the awareness that those answer "same elements now," a different question
from "same window."
The growth asymmetry
An array can never grow — its length is fixed in its type, which is
precisely why [32]byte is a safe digest type. A slice grows because
append may allocate new storage and rewrite the header's pointer and
capacity. That mechanism — when it copies versus writes in place — is the
subject of its own question in this sequence, and it is the direct result
of the window/storage split drawn here.
Code Example
x := [3]int{10, 20, 30}
y := x
y[0] = 7 // x unchanged
s := []int{10, 20, 30}
t := s
t[0] = 7 // s changed too — shared storage
Common Mistakes & Pitfalls
- 'Slices are just dynamic arrays' — the phrase that makes aliasing inexplicable.
- Believing slices support == comparison.
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.