December Code

Go maps and strings interview questions, with answers

Maps and strings look familiar to anyone coming from another language, which is exactly why interviewers ask about them: the parts that differ in Go are the parts people get wrong. Map iteration order is deliberately random, a missing key returns a zero value rather than an error, and maps are not safe for concurrent use. Strings are immutable byte sequences, so indexing gives bytes, ranging gives runes, and building a long string with + in a loop is slow.

The answers below cover each of those rules with code built and run on Go 1.27, starting with how a map is created and used and ending with how strings are stored and built. Then take the free Go diagnostic — ten questions across every Go topic in the bank — to see which of these you can explain but not yet predict.

The questions, with answers

  1. 1.How do maps work in Go?

    In short: A map is a built-in hash table from keys of a comparable type to values, created with make or a literal, and read and written with m[key].

    map[K]V is Go's built-in hash table. The key type must be comparable with ==, so strings, numbers, pointers, arrays and structs of comparable fields work, while slices, maps and functions do not. A map is created with make(map[string]int) or a composite literal, written with m[k] = v, removed from with delete(m, k), and len(m) gives the number of entries. Lookups, inserts and deletes take constant time on average. Like a slice, a map value is a reference to underlying data, so passing it to a function or assigning it to another variable shares the same entries rather than copying them.

    ages := map[string]int{
        "asha": 31,
    }
    ages["ravi"] = 28
    delete(ages, "asha")
    fmt.Println(len(ages))
    // 1
  2. 2.How do you check whether a key exists in a Go map?

    In short: Use the two-value form, v, ok := m[k]: ok is true only when the key is present, which tells a stored zero value apart from a missing key.

    Indexing a map with a key that is not there does not fail: it returns the value type's zero value. That makes a plain m[k] ambiguous when zero is a legitimate value, such as a stock count of 0. The two-value form, v, ok := m[k], adds a bool that reports whether the key was found, and it is often written inside an if statement so that v and ok are scoped to the check, as below. Deleting a key that is not present is also allowed and does nothing, so there is no need to check before calling delete.

    stock := map[string]int{
        "pen": 0,
    }
    if n, ok := stock["pen"]; ok {
        fmt.Println("have", n)
    }
    // have 0
  3. 3.Why is the iteration order of a Go map random?

    In short: The language leaves it unspecified and the runtime randomises it on purpose, so code cannot come to depend on an order that may change.

    The Go specification says map iteration order is not specified, and the runtime makes that concrete by starting each range over a map at a random position, so two loops over the same map can visit keys in different orders. The randomisation was added so that programs could not quietly rely on an order that happens to hold for one version or one map size. When order matters, collect the keys, sort them and iterate over the sorted slice. Since Go 1.23, slices.Sorted(maps.Keys(m)) does both steps in one expression. fmt.Println prints maps with their keys sorted, which helps when debugging but does not change iteration order.

    m := map[string]int{
        "b": 2, "c": 3, "a": 1,
    }
    keys := slices.Sorted(
        maps.Keys(m))
    for _, k := range keys {
        fmt.Print(k, m[k], " ")
    }
    fmt.Println()
    // a1 b2 c3
  4. 4.Are Go maps safe for concurrent use?

    In short: No: concurrent writes, or a write alongside reads, are a data race, and the runtime usually stops the program; protect the map with a mutex or use sync.Map.

    Maps have no internal locking. Any number of goroutines may read one map at the same time, but a write concurrent with any other access is a data race, and the runtime detects many of them and ends the program with fatal error: concurrent map writes, which recover cannot catch. The usual fix is a sync.Mutex, or a sync.RWMutex when reads dominate, held around every access, as below. sync.Map is a specialised alternative for keys that are written once and read many times, or for goroutines working on disjoint keys. Running the tests with go test -race finds unprotected access that has not crashed yet.

    var mu sync.Mutex
    var wg sync.WaitGroup
    hits := map[string]int{}
    for range 50 {
        wg.Add(1)
        go func() {
            defer wg.Done()
            mu.Lock()
            hits["home"]++
            mu.Unlock()
        }()
    }
    wg.Wait()
    fmt.Println(hits["home"])
    // 50
  5. 5.Why are strings immutable in Go, and how do you change one?

    In short: A string is a read-only sequence of bytes, so it can be shared freely; to change it, convert it to a []byte or []rune, edit that, and convert back.

    A Go string is a read-only slice of bytes, and the compiler rejects s[0] = 'G'. Immutability lets strings be shared without copying: substrings, map keys and values passed between goroutines all refer to the same bytes safely. To produce a modified string, convert to a mutable slice, change it and convert back, as below; each conversion copies, so s itself is unchanged. For text with non-ASCII characters, convert to []rune instead, so that each element is a whole character. For repeated edits, the strings package offers Replace, ToUpper and friends, and strings.Builder builds new strings efficiently.

    s := "go"
    b := []byte(s)
    b[0] = 'G'
    fmt.Println(s, string(b))
    // go Go
  6. 6.What is the difference between a byte and a rune in Go?

    In short: A byte is one 8-bit unit of a UTF-8 string; a rune is one Unicode code point, which can take one to four bytes, and ranging over a string yields runes.

    Go strings hold UTF-8, where ASCII characters take one byte and others take two to four. A byte, an alias for uint8, is a single one of those units, and indexing a string, s[i], returns a byte. A rune, an alias for int32, is a whole Unicode code point. for i, r := range s decodes the string as it goes, giving each rune together with the byte offset where it starts, which is why the offsets below jump from 1 to 3 around the two-byte ñ. len(s) counts bytes, so counting characters needs utf8.RuneCountInString or a conversion to []rune.

    s := "año"
    fmt.Println(len(s))
    // 4
    for i, r := range s {
        fmt.Printf("%d:%c ", i, r)
    }
    fmt.Println()
    // 0:a 1:ñ 3:o
  7. 7.How do you build a string efficiently in Go?

    In short: Use strings.Builder, which grows one buffer, rather than + in a loop, which copies the whole string every time.

    Because strings are immutable, s += piece allocates a new string and copies everything built so far, so building a string of n pieces that way does quadratic work. strings.Builder keeps a growable byte buffer: WriteString, WriteByte and fmt.Fprintf append to it without copying what is already there, and String returns the result without a final copy. When the pieces are already in a slice, strings.Join does the same in one call. For two or three pieces, + is perfectly fine and the compiler handles it well; the Builder matters inside loops.

    var sb strings.Builder
    for i := range 3 {
        fmt.Fprintf(&sb, "%d,", i)
    }
    fmt.Println(sb.String())
    // 0,1,2,

How the diagnostic asks it

One question from the Go bank, exactly as a sitting would show it. The bank has 3 on maps & strings and 30 across Go.

Maps & Strings · mediumGO-013

What does this Go code print?

m := map[string]int{"a": 0}
v, ok := m["a"]
w, ok2 := m["b"]
fmt.Println(v, ok, w, ok2)
  1. 10 true 0 falsecorrect
  2. 20 true 0 true
  3. 30 false 0 false
  4. 4It panics: key b is missing

Indexing a map always returns the value type's zero value for a missing key, so a plain m["b"] cannot tell a missing key from one stored with the value 0. The two-value form, v, ok := m[key], adds a bool that is true only when the key is present. "a" is present with the value 0, so v, ok is 0 true, and "b" is absent, so w, ok2 is 0 false: the output is 0 true 0 false. 0 false for "a" decides presence by the value. A missing key never panics in Go, unlike a missing key in Python's dict, which raises KeyError.

Measure it

Reading answers tells you what’s true. A diagnostic tells you what you get wrong.

10 Go questions across its topics, easy to hard, about fifteen minutes. You get a readiness figure with the arithmetic shown, the topics you missed named, and a practice set sized for today. Free: 1 diagnostic a month and 15 problems a day. No card.

What the readiness test measures · how the score is computed

By Harshit · updated