Irbisa · Cheatsheet

Slices, Maps & Strings

Slice headers, append aliasing, map internals, runes and strings.Builder

Junior Developer · 24 questions

  1. 01

    What is the difference between an array and a slice in Go, and what does a slice header contain?

    An array is a fixed-size value whose length is part of its type, so assigning or passing it copies every element; a slice is a small header (pointer to an array, len and cap) that views part of an array it shares with others.

    arr := [4]int{1, 2, 3, 4}cp := arr // array: full copycp[0] = 99fmt.Println(arr[0]) // 1 s := arr[1:3] // slice over arr: len 2, cap 3…
  2. 02

    What is the difference between a nil slice and an empty slice, and what can you do with a nil slice?

    The zero value of a slice is nil: a header with a nil pointer, len 0 and cap 0.

  3. 03

    What is the difference between make([]T, n) and make([]T, 0, n), and what goes wrong when you append to the first one?

    make([]T, n) creates a slice that already holds n zero values (len n); make([]T, 0, n) is empty with room for n (len 0, cap n).

    in := []string{"a", "b", "c"} // wrong: three zero Rows first, then the three real onesout := make([]Row, len(in))for _, v := range in {	out = append(out, Row{Key: v})…
  4. 04

    How do you make an independent copy of a slice, and why does copy into make([]T, 0, n) copy nothing?

    b := a copies only the header, so both still share one array; a real copy needs new memory: slices.Clone(a) (Go 1.21+), or make with the right length plus copy.

    a := []int{1, 2, 3} b := a // header copy: same arrayb[0] = 99fmt.Println(a[0]) // 99…
  5. 05

    What does this loop print, and how do you change the elements of a slice inside a range loop?

    type Item struct {	Name  string	Count int} items := []Item{{"a", 1}, {"b", 2}}for _, it := range items {	it.Count++}fmt.Println(items)

    It prints [{a 1} {b 2}]: the range variable it is a copy of each element, so it.Count++ changes the copy and the slice stays as it was.

  6. 06

    Why does len("Дмитрий") return 14, and how do you count, index and iterate the characters of a Go string?

    A Go string is an immutable sequence of bytes, usually UTF-8, so len counts bytes and s[0] is one byte.

    name := "Дмитрий" fmt.Println(len(name))                    // 14 (bytes)fmt.Println(utf8.RuneCountInString(name)) // 7 (runes)fmt.Println(name[0])                      // 208 (one byte, not "Д")…
  7. 07

    How do you check whether a key exists in a map, and what happens when you write to a map you never initialised?

    Reading a map never fails: a missing key returns the zero value, so presence is asked separately with the comma-ok form v, ok := m[k], where ok is true only if the key is really there; writing to a nil map panics, reading from it is fine.

    orders := map[string]int{"ada": 0} fmt.Println(orders["grace"]) // 0 (missing key, no panic)n, ok := orders["ada"]fmt.Println(n, ok) // 0 true (a stored zero)…
  8. 08

    Why is the iteration order of a Go map random, and how do you get a stable order?

    The spec does not define map order, and the runtime deliberately starts each range at a random position, so no code can come to rely on one.

    m := map[string]int{"b": 2, "a": 1, "c": 3} for k, v := range m { // the order differs between runs	fmt.Println(k, v)}…
  9. 09

    How do you sort a slice of structs by one field, and by two fields?

    Use slices.SortFunc(s, cmp) with a function that returns a negative number, zero or a positive number, usually built from cmp.Compare; cmp.Or adds a second key that is used only when the first one is equal.

    type User struct {	Name string	Age  int} users := []User{{"Bob", 30}, {"Alice", 30}, {"Eve", 25}}…
  10. 10

    Is a slice passed to a function by value or by reference? What does this program print?

    func add(s []int) {	s[0] = 100	s = append(s, 4)	s[1] = 200} func main() {	a := make([]int, 3, 10)	add(a)	fmt.Println(a, len(a), a[:4])}

    Go passes everything by value, and a slice's value is its header: the function gets its own copy of pointer, len and cap, pointing at the caller's array.

  11. 11

    How does append grow a slice, and when does it allocate a new array?

    If the new elements fit in cap, append writes into the existing array and allocates nothing; otherwise it allocates a bigger array, copies the elements over and returns a header pointing at it.

    var sink []int var s []intprev := -1for i := 0; i < 2000; i++ {	s = append(s, i)…
  12. 12

    Two slices are appended from the same base slice. What does this code print, and why?

    base := make([]int, 2, 3) // [0 0], one free slotx := append(base, 1)y := append(base, 2)fmt.Println(x, y) z := append(x, 3) // x is already fullz[0] = 7fmt.Println(x[0], z[0])

    It prints [0 0 2] [0 0 2] and then 0 7.

  13. 13

    What does the three-index slice expression s[low:high:max] do, and when do you need it?

    It slices like s[low:high] but also sets the capacity to max - low, so the result cannot reach the elements past max.

    buf := []int{1, 2, 3, 4, 5} head := buf[:2]         // len 2, cap 5head = append(head, 99) // overwrites buf[2]fmt.Println(buf)        // [1 2 99 4 5]…
  14. 14

    Why is building a string with s += part in a loop slow, and when do you use strings.Builder, strings.Join or bytes.Buffer?

    Strings are immutable, so every s += part allocates a new string and copies everything built so far into it: n appends copy O(n²) bytes.

    // wrong: each += copies everything built so fars := ""for _, line := range lines {	s += line + "\n"}…
  15. 15

    Is it safe to delete from or add to a map while ranging over it?

    Deleting is safe and well defined: an entry removed before the loop reaches it is simply not produced.

  16. 16

    Why does m["a"].Count++ not compile when m is a map[string]Item, and how do you update a struct stored in a map?

    Map elements are not addressable (the map may move entries when it grows), so you cannot assign to a field of a map value or take its address (cannot assign to struct field m["a"].Count in map).

    type Item struct{ Count int } m := map[string]Item{"a": {Count: 1}}m["a"].Count++ // wrong: cannot assign to struct field m["a"].Count in map it := m["a"] // right: copy out, change, store back…
  17. 17

    How is a Go map implemented, and what changed when Go 1.24 switched to Swiss tables?

    A map is a hash table: the key's hash, with a random per-map seed, decides where to look, so get, put and delete are O(1) on average.

  18. 18

    Is a Go map safe for concurrent use? What happens on concurrent writes, and how do you protect a shared map?

    No. Concurrent reads are fine, but a write alongside any other access is a data race, and the runtime may stop the process with fatal error: concurrent map writes, which is a fatal error, not a panic, so recover() cannot catch it.

    type Cache struct {	mu sync.RWMutex	m  map[string]Entry} func NewCache() *Cache {…
  19. 19

    What do the slices and maps packages replace, and which slices functions change the slice you pass in?

    Since Go 1.21 they cover the loops everyone used to write by hand (slices.Contains, Index, Sort, BinarySearch, Compact, maps.Clone).

    ids := []int{3, 1, 3, 2}fmt.Println(slices.Contains(ids, 2), slices.Index(ids, 3)) // true 0 slices.Sort(ids)                // in place: [1 2 3 3]ids = slices.Compact(ids)       // [1 2 3]ids = slices.Insert(ids, 1, 10) // [1 10 2 3]…
  20. 20

    Find the bug: after removeExpired, some expired sessions are still in the slice.

    type Session struct {	ID        string	ExpiresAt time.Time} // removeExpired drops every session that has expired.func removeExpired(sessions []Session, now time.Time) []Session {	for i := 0; i < len(sessions); i++ {		if sessions[i].ExpiresAt.Before(now) {			sessions = append(sessions[:i], sessions[i+1:]...)		}	}	return sessions}

    The loop advances i after removing an element, but append(sessions[:i], sessions[i+1:]...) has just shifted the next session into index i, so it is never checked: of two adjacent expired sessions, the second one survives.

  21. 21

    Does s = s[:5] free the rest of a large slice? How can a small subslice keep a big array in memory?

    No. Reslicing only changes the header; the backing array stays allocated while any slice points into it, so keeping body[:64] of a 4 MB request body keeps all 4 MB alive.

    var keep []byte keep = body[:64] // wrong: pins the whole 4 MB bodykeep = bytes.Clone(body[:64]) // right: copy what you keep clear(s[n:]) // slice of pointers: drop the stale references…
  22. 22

    Removing an element with s = append(s[:i], s[i+1:]...) works, so how can it leak memory, and what does slices.Delete do differently?

    The append shifts the tail left inside the same array and returns a header one shorter, but the old last slot still holds its value.

    // the old last pointer stays in the slot past len(sessions)sessions = append(sessions[:i], sessions[i+1:]...) // Go 1.22+: the same shift, the vacated slot zeroedsessions = slices.Delete(sessions, i, i+1)…
  23. 23

    Can several goroutines use the same slice or []byte buffer, and how do you do it safely?

    Reading a slice nobody writes is safe; a write while another goroutine reads or writes the same element is a data race, and append races too, because it updates the header and may write into a shared array.

    func process(buf []byte, workers int) {	size := (len(buf) + workers - 1) / workers	var wg sync.WaitGroup	for lo := 0; lo < len(buf); lo += size {		hi := min(lo+size, len(buf))		wg.Add(1)…
  24. 24

    Find the bug: Rename returns without error, but List still shows the old name for some users.

    type Registry struct {	users []User        // keeps insertion order for List	byID  map[int]*User // fast lookup by ID} func (r *Registry) Add(u User) {	r.users = append(r.users, u)	r.byID[u.ID] = &r.users[len(r.users)-1]} func (r *Registry) Rename(id int, name string) {	r.byID[id].Name = name} func (r *Registry) List() []User { return r.users }

    Add stores a pointer into the slice's current backing array, but when append runs out of capacity it copies users into a new array; older pointers still aim at the old copy, so Rename writes into memory that List never reads.