pencils ready ✎

Go MCQs multiple-choice questions with answers & explanations

All 20 Go quiz questions on one page. Pick an answer in your head, then open Show answer to check it and read why. Want a score and a timer? Take them as a quiz instead.

20 questions
  1. 1.

    What does this print?

    mid
    a := []int{1, 2, 3, 4}
    b := a[:2]
    b = append(b, 99)
    fmt.Println(a)
    1. A[1 2 3 4]
    2. B[1 2 99 4]
    3. C[1 2 99]
    4. D[1 2 3 4 99]
    Show answer

    Answer: B ([1 2 99 4])

    b has length 2 but capacity 4, because it shares a's backing array. append fits within that capacity, so it writes 99 into index 2 of the shared array and a sees it. Slicing with a[:2:2] would cap the capacity and force append to copy.

  2. 2.

    What does this print?

    easy
    s := []int{10, 20, 30, 40, 50}
    t := s[1:3]
    fmt.Println(len(t), cap(t))
    1. A2 2
    2. B2 4
    3. C3 5
    4. D2 5
    Show answer

    Answer: B (2 4)

    The length is 3 - 1 = 2. The capacity runs from the start of the slice to the end of the backing array, so it is 5 - 1 = 4. That hidden capacity is why appending to t would overwrite s[3].

  3. 3.

    What does this print?

    mid
    func add(s []int) {
    	s[0] = 9
    	s = append(s, 4)
    }
    
    func main() {
    	s := []int{1, 2, 3}
    	add(s)
    	fmt.Println(s)
    }
    1. A[1 2 3]
    2. B[9 2 3]
    3. C[9 2 3 4]
    4. D[1 2 3 4]
    Show answer

    Answer: B ([9 2 3])

    The function receives a copy of the slice header that points at the same array, so s[0] = 9 is visible to the caller. The literal has capacity 3, so append allocates a new array and only the local header sees 4. The caller's length is still 3. Return the slice when a function appends.

  4. 4.

    What does this print?

    easy
    for i := 0; i < 3; i++ {
    	defer fmt.Print(i)
    }
    1. A012
    2. B210
    3. C333
    4. D222
    Show answer

    Answer: B (210)

    The arguments of a deferred call are evaluated when the defer statement runs, so each call captures the current i. Deferred calls run in last-in, first-out order when the function returns, giving 210.

  5. 5.

    What does this print?

    easy
    x := 1
    defer fmt.Println("deferred:", x)
    x = 2
    fmt.Println("now:", x)
    1. Anow: 2 then deferred: 1
    2. Bnow: 2 then deferred: 2
    3. Cdeferred: 1 then now: 2
    4. Ddeferred: 2 then now: 2
    Show answer

    Answer: A (now: 2 then deferred: 1)

    defer evaluates x immediately (it is 1) and delays only the call, which runs after now: 2 when the function returns. To see the latest value, defer a closure: defer func() { fmt.Println(x) }().

  6. 6.

    What does fmt.Println(f()) print?

    mid
    func f() (n int) {
    	defer func() { n *= 2 }()
    	return 3
    }
    1. A3
    2. B6
    3. C0
    4. DIt does not compile
    Show answer

    Answer: B (6)

    return 3 first assigns 3 to the named result n, then deferred functions run, and only then does the function return. The closure doubles n, so the caller receives 6. This is how deferred code annotates a returned error.

  7. 7.

    What happens when this program runs?

    hard
    func handle() {
    	if r := recover(); r != nil {
    		fmt.Println("recovered:", r)
    	}
    }
    
    func main() {
    	defer func() { handle() }()
    	panic("boom")
    }
    1. AIt prints recovered: boom and exits normally
    2. BIt crashes with panic: boom
    3. CIt prints recovered: <nil>
    4. DIt does not compile
    Show answer

    Answer: B (It crashes with panic: boom)

    recover stops a panic only when it is called directly by a deferred function. Here the deferred function is the closure and recover runs one level deeper inside handle, so it returns nil and the panic continues. Writing defer handle() would recover.

  8. 8.

    What does this print?

    hard
    type MyErr struct{}
    
    func (*MyErr) Error() string { return "boom" }
    
    func find() error {
    	var e *MyErr
    	return e
    }
    
    func main() {
    	fmt.Println(find() == nil)
    }
    1. Atrue
    2. Bfalse
    3. CIt panics with a nil pointer dereference
    4. DIt does not compile
    Show answer

    Answer: B (false)

    An interface is nil only when both its dynamic type and value are nil. Returning a nil *MyErr as error stores the type *MyErr, so the interface is not nil. Return the literal nil on success.

  9. 9.

    What happens when you build this code?

    mid
    type Saver interface{ Save() }
    
    type Doc struct{}
    
    func (d *Doc) Save() {}
    
    func main() {
    	var s Saver = Doc{}
    	s.Save()
    }
    1. AIt compiles and runs
    2. BCompile error: Doc does not implement Saver (method has pointer receiver)
    3. CIt compiles but panics at run time
    4. DCompile error: Save is declared but not used
    Show answer

    Answer: B (Compile error: Doc does not implement Saver (method has pointer receiver))

    The method set of Doc contains only value-receiver methods, while Save has a pointer receiver, so only *Doc satisfies Saver. Use var s Saver = &Doc{}. Calling d.Save() on an addressable variable works, but that shortcut does not apply to interface satisfaction.

  10. 10.

    What does this print?

    mid
    ch := make(chan int, 2)
    ch <- 1
    ch <- 2
    close(ch)
    for i := 0; i < 3; i++ {
    	v, ok := <-ch
    	fmt.Println(v, ok)
    }
    1. A1 true, 2 true, 0 false
    2. B1 true, 2 true, then a panic
    3. C0 false three times
    4. D1 true, 2 true, then a deadlock
    Show answer

    Answer: A (1 true, 2 true, 0 false)

    Closing does not discard buffered values: receivers drain them first with ok == true. After that, receives return the zero value and ok == false immediately, without blocking or panicking. Only sending on a closed channel panics.

  11. 11.

    What happens when this program runs?

    easy
    func main() {
    	ch := make(chan int)
    	ch <- 1
    	fmt.Println(<-ch)
    }
    1. AIt prints 1
    2. BIt prints 0
    3. Cfatal error: all goroutines are asleep - deadlock!
    4. DIt blocks forever with no output
    Show answer

    Answer: C (fatal error: all goroutines are asleep - deadlock!)

    A send on an unbuffered channel waits for a receiver, and the only receive is on the next line of the same goroutine. No other goroutine exists, so the runtime detects that nothing can make progress and aborts. A buffer of 1, or sending from another goroutine, fixes it.

  12. 12.

    What happens when this runs?

    easy
    ch := make(chan int, 1)
    close(ch)
    ch <- 1
    fmt.Println("sent")
    1. AIt prints sent
    2. BIt panics: send on closed channel
    3. CThe send is silently dropped and sent is printed
    4. DIt deadlocks
    Show answer

    Answer: B (It panics: send on closed channel)

    Sending on a closed channel always panics, even when the buffer has room. That is why only the sender, which knows when it has finished, should close a channel.

  13. 13.

    What does this print?

    easy
    ch := make(chan int)
    select {
    case v := <-ch:
    	fmt.Println("got", v)
    default:
    	fmt.Println("empty")
    }
    1. Agot 0
    2. Bempty
    3. CIt deadlocks
    4. DIt prints nothing
    Show answer

    Answer: B (empty)

    No goroutine is sending, so the receive case is not ready. A select with a default case runs default immediately instead of blocking. Without default, this would deadlock.

  14. 14.

    What happens when this runs?

    easy
    var m map[string]int
    fmt.Println(m["a"])
    m["a"] = 1
    1. APrints 0, then panics: assignment to entry in nil map
    2. BPanics on the first line that reads m
    3. CPrints 0 and stores the value
    4. DCompile error: m is not initialised
    Show answer

    Answer: A (Prints 0, then panics: assignment to entry in nil map)

    Reading from a nil map is allowed and returns the zero value, but writing panics because there is no hash table to write into. Initialise with make(map[string]int) or a literal.

  15. 15.

    What happens when you build this?

    mid
    type Counter struct{ N int }
    
    m := map[string]Counter{"a": {}}
    m["a"].N++
    1. AIt compiles and m["a"].N becomes 1
    2. BIt compiles but the increment is lost
    3. CCompile error: cannot assign to struct field m["a"].N in map
    4. DIt panics at run time
    Show answer

    Answer: C (Compile error: cannot assign to struct field m["a"].N in map)

    Map elements are not addressable, because the map may move them when it grows, so you cannot assign to a field of a struct stored by value. Read the value, change it and store it back, or use map[string]*Counter.

  16. 16.

    The module's go.mod declares go 1.23. What does this print?

    mid
    var fs []func()
    for i := range 3 {
    	fs = append(fs, func() { fmt.Print(i) })
    }
    for _, f := range fs {
    	f()
    }
    1. A012
    2. B333
    3. C222
    4. DCompile error: cannot range over 3
    Show answer

    Answer: A (012)

    Since Go 1.22, each loop iteration has its own i, so every closure captures a different variable. Go 1.22 also allows ranging over an integer. In a module whose go line is older than 1.22, the closures would share one variable and print 333.

  17. 17.

    What does this print?

    easy
    s := "héllo"
    fmt.Println(len(s), utf8.RuneCountInString(s))
    1. A5 5
    2. B6 5
    3. C5 6
    4. D6 6
    Show answer

    Answer: B (6 5)

    len counts bytes. In UTF-8, é (U+00E9) takes two bytes, so the string is 6 bytes long but contains 5 runes. Use utf8.RuneCountInString or range to work with code points.

  18. 18.

    What does this print?

    mid
    var ErrNotFound = errors.New("not found")
    
    e1 := fmt.Errorf("get user: %w", ErrNotFound)
    e2 := fmt.Errorf("get user: %v", ErrNotFound)
    fmt.Println(errors.Is(e1, ErrNotFound), errors.Is(e2, ErrNotFound))
    1. Atrue true
    2. Btrue false
    3. Cfalse false
    4. Dfalse true
    Show answer

    Answer: B (true false)

    Both errors have the same message, but only %w makes the new error unwrap to ErrNotFound. %v just formats the text, so errors.Is cannot find the sentinel in e2.

  19. 19.

    What does this print?

    mid
    ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
    defer cancel()
    
    select {
    case <-time.After(time.Second):
    	fmt.Println("finished")
    case <-ctx.Done():
    	fmt.Println(ctx.Err())
    }
    1. Afinished
    2. Bcontext canceled
    3. Ccontext deadline exceeded
    4. D<nil>
    Show answer

    Answer: C (context deadline exceeded)

    The 50 ms deadline passes before the one-second timer, so ctx.Done() closes first and ctx.Err() returns context.DeadlineExceeded. context canceled would appear only if cancel() ran before the deadline.

  20. 20.

    What happens when this program runs with go run?

    hard
    func worker(wg sync.WaitGroup) {
    	defer wg.Done()
    	fmt.Println("work")
    }
    
    func main() {
    	var wg sync.WaitGroup
    	wg.Add(1)
    	go worker(wg)
    	wg.Wait()
    	fmt.Println("done")
    }
    1. AIt prints work then done
    2. BIt prints work, then fails with all goroutines are asleep - deadlock!
    3. CIt panics: sync: negative WaitGroup counter
    4. DIt prints done then work
    Show answer

    Answer: B (It prints work, then fails with all goroutines are asleep - deadlock!)

    worker receives a copy of the WaitGroup, so Done decrements the copy and the original counter stays at 1. Once the worker exits, main is blocked in Wait with nothing left to wake it, and the runtime reports a deadlock. Pass *sync.WaitGroup; go vet flags the copy.

esc