Language Internals
Part 6 of 11 · Go Language ProficiencyPointers, Slices, Maps & Memory
Pointers, slice headers, maps, and escape analysis contrasted with TypeScript and Python.
- 1Gist
- 2Maps
- 3Q&A
- 4Sandbox
Voice readout needs Web Speech Synthesis in this browser.
Question ladder
L1
What does a pointer parameter change?
Answer
The callee writes the caller's variable. A value parameter gets a copy. Go has no pointer arithmetic.
L2
What is in a slice header?
Answer
A pointer to the backing array, a length, and a capacity.
L3
When does append overwrite another slice?
Answer
When the subslicing still has spare capacity, so the write lands in the shared array.
L4
How do you detach?
Answer
copy into a make of the right length, or append onto a nil slice. A full slice expression caps capacity so a later append reallocates.
L5
What does a missing map key return?
Answer
The zero value and ok false. Always use the comma-ok form when absence matters.
L6
nil slice or empty slice?
Answer
Both have length 0 and range cleanly. A nil slice equals nil. An empty slice does not. JSON encodes nil as null and empty as an array.
L7
When does a local escape?
Answer
When you return its address, or otherwise let it outlive the function. go build -gcflags=-m reports the decision.
Failure modes
Append into shared capacity
b is a[:1] and append writes a[1] because cap is still large.
Comma-ok skipped
A missing key looks like a stored zero.
Concurrent map write
Maps are not safe when one goroutine writes while others read or write (mutex or sync.Map).
Nil map write
Reading a nil map is fine. Writing panics. make the map first.
Misconceptions
A slice assignment copies the elements.
It copies the header. The array is shared until growth or an explicit copy.
Python slicing and Go slicing match.
CPython slicing builds a new list. Go slicing builds a new header.
A nil slice cannot be ranged.
Ranging a nil slice is fine. The surprise is JSON null versus an empty array.
new and make are the same.
new returns a pointer to a zero value. make initializes a slice, map, or channel.
Interviewer traps
Saying append always allocates.
It allocates when length would exceed capacity. Otherwise it writes the shared array.
Using a missing key as a presence test.
Show the comma-ok form. Zero is a legal stored value.
Claiming maps are concurrency-safe because they are references.
A reference type is still a data race if two goroutines write it.
Design scenario
Same prompt for every reader.
Requirements
Full slice expression or an explicit copy, comma-ok on the index map, no shared map writes.
Traffic / scale
Many handlers read one parsed buffer.
Latency
A surprise copy of a large array dominates before the JSON encode does.
Consistency
A handler append must not change another handler's view.
Availability
A nil map write panics the request.
Failure assumptions
- Slicing copies elements the way Python does.
- A missing key and a stored zero are the same bug.
Constraints
- Stay on slices and maps. Do not introduce a channel.
- Name the three fields of the header.
Prompt
Return a view of a record buffer to a handler without letting the handler append over the next record.
Headers versus copying slices
Prefer
Shared backing array
A slice assignment copies three words. The elements stay put until growth or copy.
- Capacity is the hidden field.
- Full slice expressions cap it.
- Maps need comma-ok.
Alternative
slice and subscript copy
TypeScript slice and CPython slicing usually allocate a new list. Push does not reach the original.
- Objects are still references.
- Primitives copy.
- Missing keys are undefined or None, not zero.
Overview
* and & are the sharing syntax. A slice is not the array. A map lookup without ok cannot tell a stored zero from a miss. Go copy (new backing array) and full-slice expressions (cap limit so append reallocates) are the tools that prevent accidental sharing.
Decisions
- 1
Slice header
- nextCapacity remains?
- ?
Capacity remains?
- yesAppend may clobber
- noNew backing array
- 3
Append may clobber
- nextcopy or full slice
- 4
New backing array
- 5
copy or full slice
Lesson map
Pointers, Slices, Maps & Memory
Pointers, slice headers, maps, and escape analysis contrasted with TypeScript and Python.
Architecture. Slice header Ready. Capacity remains? Ready. Append may clobber Ready. New backing array Ready. copy or full slice Ready
Select a node to see why it exists, or an edge to see the protocol, direction, effect, and consequence.
Mermaid export
flowchart TB Header["Slice header Ready"] Spare["Capacity remains? Ready"] Clobber["Append may clobber Ready"] Fresh["New backing array Ready"] Detach["copy or full slice Ready"] Header -->|continues| Spare Spare -->|yes| Clobber Spare -->|no| Fresh Clobber -->|continues| Detach
Press Run. Snippets must be self-contained — no network, files, or native modules.
That Python list is standing in for the shared array. A real Python slice a[:1] would have copied.
Rosetta — pointer versus reference
package ptr
func bump(p *int) { *p++ }
func Demo() {
x := 1
bump(&x)
}function bump(box: { n: number }) {
box.n++;
}
const box = { n: 1 };
bump(box);def bump(box: dict) -> None:
box["n"] += 1
box = {"n": 1}
bump(box)Go makes the pointer explicit. TypeScript and Python objects are references. Their primitives copy. Go does not offer pointer arithmetic.
Rosetta — append alias
package slicesdemo
import "fmt"
func Demo() {
a := make([]int, 0, 4)
a = append(a, 1, 2)
b := a[:1]
b = append(b, 9)
fmt.Println(a, b)
c := append([]int(nil), a...)
_ = c
}const a = [1, 2];
const b = a.slice(0, 1);
b.push(9);
console.log(a, b);a = [1, 2]
b = a[:1]
b.append(9)
print(a, b)b = append(b, 9) can overwrite a[1] because both headers still fit in the capacity of 4. TypeScript slice and Python slicing allocate. Detach with copy or append([]int(nil), a...).
Rosetta — maps
package mapsdemo
func Demo() {
m := make(map[string]int)
m["a"] = 1
v, ok := m["b"]
_ = v
_ = ok
delete(m, "a")
}const m = new Map<string, number>();
m.set("a", 1);
const v = m.get("b");
m.delete("a");
console.log(v);m: dict[str, int] = {"a": 1}
v = m.get("b")
m.pop("a", None)
print(v)v, ok := m["b"] gives 0, false. Map.get gives undefined. dict.get gives None. Maps are not safe when one goroutine writes while others read or write (mutex or sync.Map).
copy and the full slice
package mem
func Demo() []int {
src := []int{1, 2, 3, 4}
dst := make([]int, len(src))
copy(dst, src)
view := src[1:3:3]
return append(view, 99)
}const src = [1, 2, 3, 4];
const dst = [...src];
const view = src.slice(1, 3);
view.push(99);
console.log(dst, view);src = [1, 2, 3, 4]
dst = src.copy()
view = src[1:3]
view.append(99)
print(dst, view)src[1:3:3] sets the capacity to the high index, so append cannot write src[3]. Returning &x from a function makes x escape to the heap. go build -gcflags=-m prints that choice. Go 1.27 also specializes allocations smaller than 80 bytes. The aliasing rules do not change.
A nil slice and an empty slice both have length 0. range is fine on both. JSON writes null for nil and [] for empty. Prefer len(s) == 0 when you only care that there are no elements.
Interview Q&A
nil slice or empty slice?
Answer
Both have length 0. The nil slice compares equal to nil. The empty slice does not. Encoders treat them differently. Check length when emptiness is the question.
What are the three header fields?
Answer
Pointer, length, and capacity.
Does append always allocate?
Answer
No. It allocates when the new length would exceed capacity. Otherwise it writes the existing array.
What is the full slice expression?
Answer
low, high, and max. max becomes the capacity limit so a later append reallocates instead of clobbering.
Why is a missing map key dangerous?
Answer
The zero value is also a legal value. comma-ok is the presence test.
Are maps safe for concurrent read and write?
Answer
No. Maps are not safe when one goroutine writes while others read or write (mutex or sync.Map). The runtime crashes on that conflict.
What does escape analysis decide?
Answer
Whether a variable can live on the stack or must be on the heap because a pointer outlives the function.
How do TypeScript and Python differ on slice?
Answer
Their slice operations copy the elements into a new list. A later push or append does not write the original.
Pitfalls
b := a[:1]thenappendwhilecap(a)is larger.- Treating a zero map value as absent.
- Writing a nil map.
- Sharing a map across goroutines because it is a reference.
Given a four-element slice, write the expression that views the middle two elements and cannot append over the last element.