Back to Notes

Go Quiz & Coding Challenges

How to use: Go section by section. For "What's the output?" — reason through it before expanding. For coding challenges — write the solution first, then verify.


Section 1 — Variables & Types

Q1 — What's the output?

package main

import "fmt"

var x = 10

func main() {
    x := 20
    fmt.Println(x)

    {
        x := 30
        fmt.Println(x)
    }

    fmt.Println(x)
}

[!success]- Answer

20
30
20

x := 20 shadows the package-level x inside main. The inner block creates another shadow. The outer main x is unaffected throughout.


Q2 — Will this compile? If not, why?

package main

const MaxItems = 100

func main() {
    MaxItems = 200
    x, y := 1, 2
    fmt.Println(x)
}

[!success]- Answer No — two compile errors:

  1. MaxItems = 200 — constants cannot be reassigned
  2. y declared but not used — Go requires all declared variables to be used

Q3 — What does this print?

func main() {
    var a int
    var b float64
    var c string
    var d bool

    fmt.Printf("%v %v %q %v\n", a, b, c, d)
}

[!success]- Answer

0 0 "" false

Zero values: int0, float640, string"" (printed quoted with %q), boolfalse.


Q4 — Fix the bug

func main() {
    x := 5
    y := 2.5
    fmt.Println(x + y)
}

[!success]- Answer

fmt.Println(float64(x) + y)
// or
fmt.Println(x + int(y))

Go has no implicit type conversion. int + float64 is a compile error — you must convert explicitly.


Q5 — Coding Challenge

Write a function celsiusToFahrenheit(c float64) float64 and use iota to define constants for three temperature scales: Celsius = 0, Fahrenheit = 1, Kelvin = 2.

[!success]- Answer

type TempScale int

const (
    Celsius    TempScale = iota // 0
    Fahrenheit                  // 1
    Kelvin                      // 2
)

func celsiusToFahrenheit(c float64) float64 {
    return c*9/5 + 32
}

Section 2 — Control Flow (if / switch)

Q6 — What's the output?

func classify(n int) string {
    switch {
    case n < 0:
        return "negative"
    case n == 0:
        return "zero"
    case n < 10:
        return "small"
    default:
        return "large"
    }
}

func main() {
    fmt.Println(classify(-3))
    fmt.Println(classify(0))
    fmt.Println(classify(7))
    fmt.Println(classify(100))
}

[!success]- Answer

negative
zero
small
large

switch with no condition evaluates each case as a boolean — equivalent to switch true.


Q7 — What's the output?

func main() {
    x := 2
    switch x {
    case 1:
        fmt.Println("one")
        fallthrough
    case 2:
        fmt.Println("two")
        fallthrough
    case 3:
        fmt.Println("three")
    case 4:
        fmt.Println("four")
    }
}

[!success]- Answer

two
three

Execution starts at case 2 (matched). fallthrough unconditionally continues to case 3. No fallthrough in case 3, so it stops. case 4 is never reached.


Q8 — Fix the bug

func main() {
    if score := 85
    score >= 90 {
        fmt.Println("A")
    } else {
        fmt.Println("B")
    }
}

[!success]- Answer

if score := 85; score >= 90 {
    fmt.Println("A")
} else {
    fmt.Println("B")
}

The init statement and condition must be separated by a semicolon ;, not a newline.


Q9 — Coding Challenge

Write dayType(day string) string that returns "weekend" for Saturday/Sunday and "weekday" for everything else. Use a switch with multiple values per case.

[!success]- Answer

func dayType(day string) string {
    switch day {
    case "Saturday", "Sunday":
        return "weekend"
    default:
        return "weekday"
    }
}

Section 3 — Functions

Q10 — What's the output?

func mystery() (result int) {
    defer func() {
        result++
    }()
    return 10
}

func main() {
    fmt.Println(mystery())
}

[!success]- Answer

11

return 10 sets the named return result = 10. Then the deferred function runs and increments result to 11. Named returns let defers modify the return value after return.


Q11 — What's the output?

func main() {
    for i := 0; i < 3; i++ {
        defer fmt.Println(i)
    }
}

[!success]- Answer

2
1
0

Defer is LIFO — last registered runs first. Arguments are evaluated immediately at the defer statement, so each defer captures its current value of i (0, 1, 2). They run in reverse: 2, 1, 0.


Q12 — Is there a bug? What does it print?

func makeMultiplier(factor int) func(int) int {
    return func(n int) int {
        return n * factor
    }
}

func main() {
    double := makeMultiplier(2)
    triple := makeMultiplier(3)

    fmt.Println(double(5))
    fmt.Println(triple(5))
    fmt.Println(double(triple(4)))
}

[!success]- Answer No bug. Output:

10
15
24

double(triple(4)) = double(12) = 24. Each call to makeMultiplier creates an independent closure that captures its own factor.


Q13 — Coding Challenge

Write a variadic function stats(nums ...float64) (min, max, avg float64) that returns the minimum, maximum, and average of any number of floats.

[!success]- Answer

func stats(nums ...float64) (min, max, avg float64) {
    if len(nums) == 0 {
        return
    }
    min, max = nums[0], nums[0]
    sum := 0.0
    for _, n := range nums {
        if n < min { min = n }
        if n > max { max = n }
        sum += n
    }
    avg = sum / float64(len(nums))
    return
}

mn, mx, av := stats(3, 1, 4, 1, 5, 9, 2, 6)
fmt.Printf("min=%.0f max=%.0f avg=%.2f\n", mn, mx, av)
// min=1 max=9 avg=3.88

Section 4 — Structs

Q14 — What's the output?

type Point struct{ X, Y int }

func move(p Point, dx, dy int) {
    p.X += dx
    p.Y += dy
}

func movePtr(p *Point, dx, dy int) {
    p.X += dx
    p.Y += dy
}

func main() {
    p := Point{1, 2}
    move(p, 5, 5)
    fmt.Println(p)

    movePtr(&p, 5, 5)
    fmt.Println(p)
}

[!success]- Answer

{1 2}
{6 7}

move receives a copy — original unchanged. movePtr receives the address — modifies the original.


Q15 — What's the output?

type Animal struct{ Sound string }
type Dog struct {
    Animal
    Name string
}

func (a Animal) Speak() string { return a.Sound }
func (d Dog) Speak() string    { return d.Name + " says " + d.Sound }

func main() {
    d := Dog{Animal: Animal{Sound: "woof"}, Name: "Rex"}
    fmt.Println(d.Sound)
    fmt.Println(d.Animal.Speak())
    fmt.Println(d.Speak())
}

[!success]- Answer

woof
woof
Rex says woof

d.Sound — promoted field from embedded Animal. d.Animal.Speak() — calls Animal's method directly. d.Speak() — Dog's own method takes priority over the promoted one.


Q16 — Coding Challenge

Define a Rectangle struct with Width and Height float64. Add methods: Area(), Perimeter(), IsSquare() bool, and Scale(factor float64) that mutates the receiver.

type Rectangle struct {
	Width, Height float64
}

func (r Rectangle) Area() float64 {
	return r.Width * r.Height
}

func (r Rectangle) Perimeter float64 {
	return 2 * (r.Width + r.Height)
}

func (r Rectangle) IsSquare bool {
	return r.Width == r.Height
}

func (r *Rectangle) Scale(factor float64) {
	r.Width *= factor
	r.Height *= factor
}

[!success]- Answer

type Rectangle struct {
    Width, Height float64
}

func (r Rectangle) Area() float64      { return r.Width * r.Height }
func (r Rectangle) Perimeter() float64 { return 2 * (r.Width + r.Height) }
func (r Rectangle) IsSquare() bool     { return r.Width == r.Height }
func (r *Rectangle) Scale(factor float64) {
    r.Width *= factor
    r.Height *= factor
}

r := Rectangle{Width: 4, Height: 6}
fmt.Println(r.Area())      // 24
fmt.Println(r.IsSquare())  // false
r.Scale(2)
fmt.Println(r.Width)       // 8

Section 5 — Interfaces

Q17 — Will this compile? Why?

type Speaker interface{ Speak() string }

type Cat struct{ Name string }
func (c Cat) Speak() string { return c.Name + " says meow" }

type Robot struct{ ID int }
func (r *Robot) Speak() string { return fmt.Sprintf("Robot %d beeping", r.ID) }

func makeNoise(s Speaker) { fmt.Println(s.Speak()) }

func main() {
    makeNoise(Cat{Name: "Luna"})
    makeNoise(Robot{ID: 1})    // line A
    makeNoise(&Robot{ID: 2})   // line B
}

[!success]- Answer Line A does NOT compile. Robot (value type) does not satisfy Speaker because Speak has a pointer receiver — only *Robot satisfies the interface. Line B compiles fine.

Fix: makeNoise(&Robot{ID: 1})


Q18 — What's the output?

func typeCheck(i interface{}) {
    switch v := i.(type) {
    case int:
        fmt.Printf("int: %d\n", v*2)
    case string:
        fmt.Printf("string: %q (len %d)\n", v, len(v))
    case bool:
        fmt.Printf("bool: %v\n", !v)
    default:
        fmt.Printf("other: %T\n", v)
    }
}

func main() {
    typeCheck(21)
    typeCheck("hello")
    typeCheck(true)
    typeCheck(3.14)
}

[!success]- Answer

int: 42
string: "hello" (len 5)
bool: false
other: float64

Q19 — Coding Challenge

Define a Shape interface with Area() float64. Implement it for Circle (field Radius) and Triangle (fields Base, Height). Write totalArea(shapes []Shape) float64.

import "math"
type Shape interface { Area() float64 }

type Circle struct {
	Radius float64
}
type Traingle struct {
	Base float64
	Height float64
}

func (c Circle) Area() float64 {
	return math.Pi * c.Radius * c.Radius
}
func (t Traingle) Area() float64 {
	return 0.5 * t.Base * t.Height
}

func totalArea(shapes []Shape) float64 {
	ans := 0.0
	for _, s := range shapes {
		ans += s.Area()
	}
	return ans	
}

[!success]- Answer

import "math"

type Shape interface{ Area() float64 }

type Circle   struct{ Radius float64 }
type Triangle struct{ Base, Height float64 }

func (c Circle) Area() float64   { return math.Pi * c.Radius * c.Radius }
func (t Triangle) Area() float64 { return 0.5 * t.Base * t.Height }

func totalArea(shapes []Shape) float64 {
    total := 0.0
    for _, s := range shapes {
        total += s.Area()
    }
    return total
}

shapes := []Shape{
    Circle{Radius: 3},
    Triangle{Base: 4, Height: 6},
    Circle{Radius: 1},
}
fmt.Printf("%.2f\n", totalArea(shapes))  // 31.56

Section 6 — Errors

Q20 — What's the output?

import "errors"

var ErrDivByZero = errors.New("division by zero")

func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, fmt.Errorf("divide(%.0f, %.0f): %w", a, b, ErrDivByZero)
    }
    return a / b, nil
}

func main() {
    _, err := divide(10, 0)
    fmt.Println(err)
    fmt.Println(errors.Is(err, ErrDivByZero))
}

[!success]- Answer

divide(10, 0): division by zero
true

%w wraps ErrDivByZero inside the formatted error. errors.Is traverses the chain and finds it.


Q21 — What's the output?

func riskyOp() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("recovered:", r)
        }
    }()
    panic("boom")
    fmt.Println("after panic")
}

func main() {
    riskyOp()
    fmt.Println("main continues")
}

[!success]- Answer

recovered: boom
main continues

recover() inside a deferred function catches the panic. "after panic" is never reached. Execution resumes normally in main after riskyOp returns.


Q22 — Coding Challenge

Create a custom error type ValidationError with fields Field and Message string. Write validateUsername(name string) error — returns an error if name is empty or shorter than 3 chars. The caller should use errors.As to extract the field name.

[!success]- Answer

type ValidationError struct {
    Field   string
    Message string
}

func (e *ValidationError) Error() string {
    return fmt.Sprintf("field %q: %s", e.Field, e.Message)
}

func validateUsername(name string) error {
    if name == "" {
        return &ValidationError{Field: "username", Message: "cannot be empty"}
    }
    if len(name) < 3 {
        return &ValidationError{Field: "username", Message: "must be at least 3 characters"}
    }
    return nil
}

err := validateUsername("ab")
var ve *ValidationError
if errors.As(err, &ve) {
    fmt.Printf("invalid field: %s — %s\n", ve.Field, ve.Message)
}
// invalid field: username — must be at least 3 characters

Section 7 — Loops

Q23 — What's the output?

func main() {
    s := []int{1, 2, 3, 4, 5}
    for _, v := range s {
        v *= 10
    }
    fmt.Println(s)
}

[!success]- Answer

[1 2 3 4 5]

v is a copy of each element — modifying it doesn't affect the original slice. To modify in place use s[i] *= 10.


Q24 — What's the output?

func main() {
outer:
    for i := 0; i < 3; i++ {
        for j := 0; j < 3; j++ {
            if j == 2 {
                continue outer
            }
            fmt.Printf("%d%d ", i, j)
        }
    }
}

[!success]- Answer

00 01 10 11 20 21

When j == 2, continue outer skips the rest of the inner loop and jumps to the next i iteration. So j=2 is never printed for any i.


Q25 — Coding Challenge

Write fizzbuzz(n int) []string — returns a slice where multiples of 3 are "Fizz", multiples of 5 are "Buzz", multiples of both are "FizzBuzz", otherwise the number as a string.

[!success]- Answer

import "strconv"

func fizzbuzz(n int) []string {
    result := make([]string, 0, n)
    for i := 1; i <= n; i++ {
        switch {
        case i%15 == 0:
            result = append(result, "FizzBuzz")
        case i%3 == 0:
            result = append(result, "Fizz")
        case i%5 == 0:
            result = append(result, "Buzz")
        default:
            result = append(result, strconv.Itoa(i))
        }
    }
    return result
}

Section 8 — Slices

Q26 — What's the output?

func main() {
    a := []int{1, 2, 3, 4, 5}
    b := a[1:4]

    b[0] = 99

    fmt.Println(a)
    fmt.Println(b)
    fmt.Println(len(b), cap(b))
}

[!success]- Answer

[1 99 3 4 5]
[99 3 4]
3 4

b is a window into a — they share the same backing array. Modifying b[0] changes a[1]. cap(b) = 4 because there are 4 elements from b's start pointer to the end of a.


Q27 — What's the output?

func main() {
    s := make([]int, 3, 5)
    s = append(s, 1)
    s = append(s, 2)
    fmt.Println(len(s), cap(s))

    s = append(s, 3)
    fmt.Println(len(s), cap(s))
}

[!success]- Answer

5 5
6 10

make([]int, 3, 5) → len=3, cap=5. Two appends fill to len=5 within capacity (no realloc). The third append exceeds cap=5 so Go allocates a new array (typically 2× → cap=10).


Q28 — Coding Challenge

Write removeDuplicates(s []int) []int — returns a new slice with duplicates removed, preserving original order.

[!success]- Answer

func removeDuplicates(s []int) []int {
    seen   := make(map[int]struct{})
    result := make([]int, 0, len(s))
    for _, v := range s {
        if _, exists := seen[v]; !exists {
            seen[v] = struct{}{}
            result = append(result, v)
        }
    }
    return result
}

fmt.Println(removeDuplicates([]int{1, 2, 2, 3, 1, 4, 3}))
// [1 2 3 4]

Q29 — Coding Challenge

Write rotate(s []int, k int) []int that rotates the slice left by k positions in-place using the reverse trick.

[!success]- Answer

func reverse(s []int) {
    for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
        s[i], s[j] = s[j], s[i]
    }
}

func rotate(s []int, k int) []int {
    n := len(s)
    if n == 0 { return s }
    k = k % n
    reverse(s[:k])
    reverse(s[k:])
    reverse(s)
    return s
}

s := []int{1, 2, 3, 4, 5}
fmt.Println(rotate(s, 2))  // [3 4 5 1 2]

Section 9 — Maps

Q30 — What's the output?

func main() {
    m := map[string]int{"a": 1, "b": 2}

    v1 := m["a"]
    v2 := m["z"]
    v3, ok := m["z"]

    fmt.Println(v1, v2, ok, v3)
}

[!success]- Answer

1 0 false 0

m["z"] returns 0 (zero value for int) — not an error. The comma-ok form distinguishes "key missing" (ok=false) from "key present with zero value".


Q31 — Will this panic? Why?

func main() {
    var m map[string]int
    fmt.Println(m["key"])
    m["key"] = 1
}

[!success]- Answer Yes — panics on the write.

m["key"] read is safe — returns 0. m["key"] = 1 panics: assignment to entry in nil map.

Fix: m = make(map[string]int) before writing.


Q32 — Coding Challenge

Write wordCount(s string) map[string]int that returns how many times each word appears in the string.

[!success]- Answer

import "strings"

func wordCount(s string) map[string]int {
    counts := make(map[string]int)
    for _, word := range strings.Fields(s) {
        counts[word]++
    }
    return counts
}

fmt.Println(wordCount("go is great and go is fast"))
// map[and:1 fast:1 go:2 great:1 is:2]

strings.Fields splits on any whitespace. counts[word]++ works even when the key is absent — zero value 0 is incremented to 1.


Q33 — Coding Challenge

Write groupByLength(words []string) map[int][]string that groups words by their character length.

[!success]- Answer

func groupByLength(words []string) map[int][]string {
    groups := make(map[int][]string)
    for _, w := range words {
        groups[len(w)] = append(groups[len(w)], w)
    }
    return groups
}

words := []string{"go", "is", "fun", "and", "fast", "cool"}
fmt.Println(groupByLength(words))
// map[2:[go is] 3:[fun and] 4:[fast cool]]

Appending to a nil slice value in a map is safe — append(nil, w) creates a new slice.


Section 10 — Pointers

Q34 — What's the output?

func increment(n *int) {
    *n++
}

func main() {
    x := 5
    increment(&x)
    increment(&x)
    fmt.Println(x)
}

[!success]- Answer

7

increment receives the address of x and increments the actual value through the pointer. Called twice → 5 + 1 + 1 = 7.


Q35 — What's the output?

type Counter struct{ n int }

func (c Counter) Inc()   { c.n++ }
func (c *Counter) PInc() { c.n++ }

func main() {
    c := Counter{}
    c.Inc()
    c.Inc()
    fmt.Println(c.n)

    c.PInc()
    c.PInc()
    fmt.Println(c.n)
}

[!success]- Answer

0
2

Inc has a value receiver — operates on a copy, original unchanged. PInc has a pointer receiver — modifies the original. After two Inc calls, c.n is still 0. After two PInc calls, c.n is 2.


Q36 — Is there a bug?

func newInt(n int) *int {
    return &n
}

func main() {
    p := newInt(42)
    fmt.Println(*p)
}

[!success]- Answer No bug. Output: 42

In Go, returning a pointer to a local variable is safe — the compiler detects that the value escapes the function and allocates it on the heap automatically. Unlike C, this is perfectly valid.


Q37 — Coding Challenge

Write swap(a, b *int) that swaps two integers using pointers. Then show the idiomatic Go way to swap without pointers.

[!success]- Answer

// Using pointers
func swap(a, b *int) {
    *a, *b = *b, *a
}

x, y := 10, 20
swap(&x, &y)
fmt.Println(x, y)  // 20 10

// Idiomatic Go — no pointers needed
x, y = y, x
fmt.Println(x, y)  // 10 20

Section 11 — Packages & Modules

Q38 — Concept Questions

a) What's the difference between a package and a module?

b) Why does import _ "image/png" not cause an "imported and not used" error?

c) Is process in package util exported? What about Process?

d) Can two packages import each other?

[!success]- Answer a) A package is a directory of .go files sharing a package declaration — the unit of code organisation. A module is a collection of packages defined by a go.mod file — the unit of versioning and distribution.

b) The blank import _ explicitly discards the package name. It's a deliberate signal: "run this package's init() for side effects only" (e.g., registering a database driver or image decoder).

c) process — unexported (lowercase), only usable within package util. Process — exported (uppercase), usable by any package that imports util.

d) No. Circular imports are a compile error in Go. Fix by extracting shared types into a third package that both can import.


Q39 — Will this compile?

package main

import (
    "fmt"
    "math"
)

func main() {
    fmt.Println("Pi is approximately 3.14")
}

[!success]- Answer No — compile error: "math" imported and not used.

Go requires every import to be used. Remove "math" to fix it.


Mixed Challenges

Q40 — Put it all together

Write a complete program that:

  1. Defines a Student struct with Name string and Grades []float64
  2. Adds Average() (float64, error) — returns ErrNoGrades if slice is empty
  3. Adds Grade() string returning "A"/"B"/"C"/"F" based on average (90+ / 80+ / 70+ / below)
  4. Defines a Reporter interface with Report() string and implements it on Student
  5. Writes printReports(reporters []Reporter) that prints each report
import "errors"

type Student struct {
	Name string
	Grades []float64
}

type Reporter interface {
	Report() string
}

func (s Student) Average() (float64, error) {
	if len(s.Grades) == 0 {
		return 0.0, errors.New("ErrNoGrades")
	}
	
	total := 0.0
	for _, grade := range s.Grades {
		total += grade
	}
	
	return total / float64(len(s.Grades)), nil
}

func (s Student) Grade() string {
	avg, err = s.Average()
	if err != nil {
		return "N/A"
	}
	switch {
		case avg >= 90:
			return "A"
		case avg >= 80:
			return "B"
		case avg >= 70:
			return "C"
		default:
			return "F"
	}
}

func (s Student) Report() string {
	avg, err := s.Average() 
	if err != nil {
		return fmt.Sprintf("%s: no grades", s.Name)
	}
	
	return fmt.Sprintf("%s: avg=%.1f grade:%v", s.Name, avg, s.Grade())
}

func printReports(reporters []Reporter) {
	for _, r := range reporters {
		fmt.Println(r.Report())
	}
}

[!success]- Answer

package main

import (
    "errors"
    "fmt"
)

var ErrNoGrades = errors.New("no grades recorded")

type Student struct {
    Name   string
    Grades []float64
}

func (s *Student) Average() (float64, error) {
    if len(s.Grades) == 0 {
        return 0, ErrNoGrades
    }
    sum := 0.0
    for _, g := range s.Grades {
        sum += g
    }
    return sum / float64(len(s.Grades)), nil
}

func (s *Student) Grade() string {
    avg, err := s.Average()
    if err != nil { return "N/A" }
    switch {
    case avg >= 90: return "A"
    case avg >= 80: return "B"
    case avg >= 70: return "C"
    default:        return "F"
    }
}

func (s *Student) Report() string {
    avg, err := s.Average()
    if err != nil {
        return fmt.Sprintf("%s: no grades", s.Name)
    }
    return fmt.Sprintf("%s: avg=%.1f grade=%s", s.Name, avg, s.Grade())
}

type Reporter interface{ Report() string }

func printReports(reporters []Reporter) {
    for _, r := range reporters {
        fmt.Println(r.Report())
    }
}

func main() {
    students := []Reporter{
        &Student{Name: "Alice", Grades: []float64{92, 88, 95}},
        &Student{Name: "Bob",   Grades: []float64{72, 68, 75}},
        &Student{Name: "Carol", Grades: []float64{}},
    }
    printReports(students)
    // Alice: avg=91.7 grade=A
    // Bob:   avg=71.7 grade=C
    // Carol: no grades
}

Q41 — Spot All the Bugs

func main() {
    var m map[string][]int
    m["scores"] = append(m["scores"], 95)

    s := make([]int, 5)
    s[5] = 10

    var p *int
    *p = 42

    const limit = 100
    limit = 200
}

[!success]- Answer Four bugs:

  1. m["scores"] = ... — write to nil map → panic. Fix: m = make(map[string][]int)
  2. s[5] = 10 — index out of bounds, valid indices are 0–4 → panic. Fix: make([]int, 6) or append(s, 10)
  3. *p = 42 — nil pointer dereference → panic. Fix: n := 42; p = &n
  4. limit = 200 — cannot assign to a constant → compile error. Fix: use var limit = 100

Section 12 — Channels & Goroutines

Q42 — What's the output?

func main() {
    ch := make(chan int)
    go func() {
        ch <- 1
        ch <- 2
        ch <- 3
        close(ch)
    }()

    for v := range ch {
        fmt.Println(v)
    }
    fmt.Println("done")
}

[!success]- Answer

1
2
3
done

range over a channel reads until the channel is closed. The goroutine sends 3 values then closes — range exits cleanly and execution continues.


Q43 — Will this deadlock? Why?

func main() {
    ch := make(chan string)
    ch <- "hello"
    fmt.Println(<-ch)
}

[!success]- Answer Yes — deadlocks immediately.

ch is unbuffered. ch <- "hello" blocks waiting for a receiver, but main is the only goroutine and it's stuck on the send — there's nobody to receive. Go detects this and panics: all goroutines are asleep - deadlock!

Fix: launch the send in a goroutine, or use make(chan string, 1).


Q44 — What's the output?

func main() {
    ch := make(chan int, 2)
    ch <- 10
    ch <- 20

    select {
    case v := <-ch:
        fmt.Println("received:", v)
    default:
        fmt.Println("nothing ready")
    }

    select {
    case v := <-ch:
        fmt.Println("received:", v)
    default:
        fmt.Println("nothing ready")
    }

    select {
    case v := <-ch:
        fmt.Println("received:", v)
    default:
        fmt.Println("nothing ready")
    }
}

[!success]- Answer

received: 10
received: 20
nothing ready

The buffered channel has 2 values. The first two selects receive them. The third select finds the channel empty — default runs immediately instead of blocking.


Q45 — What's the output?

func main() {
    var c chan int
    select {
    case v := <-c:
        fmt.Println("received:", v)
    default:
        fmt.Println("nil channel, skipped")
    }
}

[!success]- Answer

nil channel, skipped

A receive from a nil channel blocks forever — but select with a default case never blocks. Since the nil channel case is never ready, default fires immediately.


Q46 — Coding Challenge

Write a pipeline function: one goroutine generates squares of 1–5 on a channel, a second goroutine reads them and doubles each value, the main goroutine prints the results. Use channel direction types on each stage.

[!success]- Answer

func generate(out chan<- int) {
    for i := 1; i <= 5; i++ {
        out <- i * i
    }
    close(out)
}

func double(in <-chan int, out chan<- int) {
    for v := range in {
        out <- v * 2
    }
    close(out)
}

func main() {
    squares := make(chan int)
    doubled := make(chan int)

    go generate(squares)
    go double(squares, doubled)

    for v := range doubled {
        fmt.Println(v)  // 2 8 18 32 50
    }
}

Section 13 — Mutexes

Q47 — What's the bug?

type SafeMap struct {
    mu   sync.Mutex
    data map[string]int
}

func (m SafeMap) Set(key string, val int) {
    m.mu.Lock()
    defer m.mu.Unlock()
    m.data[key] = val
}

[!success]- Answer Two bugs:

  1. Value receiver copies the mutexSafeMap is copied on every call to Set, which copies sync.Mutex too. A copied mutex is always unlocked regardless of the original's state. go vet flags this.
  2. Value receiver copies the map header — while maps are reference types, the copy of SafeMap has an independent header; if data were ever replaced (e.g. m.data = make(...)) the caller wouldn't see it.

Fix: use a pointer receiver — func (m *SafeMap) Set(...).


Q48 — What's the output?

type RWCounter struct {
    mu    sync.RWMutex
    value int
}

func (c *RWCounter) Inc() {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.value++
}

func (c *RWCounter) Get() int {
    c.mu.RLock()
    defer c.mu.RUnlock()
    return c.value
}

func main() {
    c := &RWCounter{}
    var wg sync.WaitGroup

    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            c.Inc()
        }()
    }

    wg.Wait()
    fmt.Println(c.Get())
}

[!success]- Answer

5

5 goroutines each call Inc once. sync.Mutex inside Inc serialises the writes — no race condition. Get uses RLock (safe for concurrent reads). Result is always 5.


Q49 — Coding Challenge

Write a Cache[K comparable, V any] generic struct with Set(key K, val V) and Get(key K) (V, bool) methods, safe for concurrent use.

[!success]- Answer

type Cache[K comparable, V any] struct {
    mu    sync.RWMutex
    items map[K]V
}

func NewCache[K comparable, V any]() *Cache[K, V] {
    return &Cache[K, V]{items: make(map[K]V)}
}

func (c *Cache[K, V]) Set(key K, val V) {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.items[key] = val
}

func (c *Cache[K, V]) Get(key K) (V, bool) {
    c.mu.RLock()
    defer c.mu.RUnlock()
    v, ok := c.items[key]
    return v, ok
}

c := NewCache[string, int]()
c.Set("age", 30)
v, ok := c.Get("age")
fmt.Println(v, ok) // 30 true

Section 14 — Generics

Q50 — Will this compile? Why?

func Max[T any](a, b T) T {
    if a > b {
        return a
    }
    return b
}

[!success]- Answer No — compile error: invalid operation: a > b (operator > not defined on T)

The constraint any permits every type, including ones that don't support >. The compiler can't guarantee > is valid. Fix: use a constraint that includes only ordered types:

type Ordered interface {
    ~int | ~float64 | ~string // etc.
}

func Max[T Ordered](a, b T) T { ... }

Q51 — What's the output?

func Map[T, U any](s []T, f func(T) U) []U {
    out := make([]U, len(s))
    for i, v := range s {
        out[i] = f(v)
    }
    return out
}

func main() {
    nums := []int{1, 2, 3, 4, 5}
    strs := Map(nums, func(n int) string {
        return fmt.Sprintf("item-%d", n)
    })
    fmt.Println(strs)
}

[!success]- Answer

[item-1 item-2 item-3 item-4 item-5]

Type inference figures out T=int and U=string from the arguments — no explicit type params needed. Map builds a new []string by applying the function to each element.


Q52 — What's the difference?

// A
type Numeric interface {
    int | float64
}

// B
type Numeric interface {
    ~int | ~float64
}

When would A fail but B succeed?

[!success]- Answer With constraint A, only the exact types int and float64 satisfy it.

With constraint B, any type whose underlying type is int or float64 also satisfies it — including custom types like:

type Celsius float64
type UserID  int

A rejects Celsius and UserID. B accepts them. Use ~T whenever you want your generic to work with domain types built on top of primitives.


Q53 — Coding Challenge

Write a generic Filter[T any](s []T, keep func(T) bool) []T and a generic Reduce[T, U any](s []T, initial U, fn func(U, T) U) U. Use them to: filter a []int to evens only, then sum the result.

[!success]- Answer

func Filter[T any](s []T, keep func(T) bool) []T {
    out := make([]T, 0, len(s))
    for _, v := range s {
        if keep(v) {
            out = append(out, v)
        }
    }
    return out
}

func Reduce[T, U any](s []T, initial U, fn func(U, T) U) U {
    acc := initial
    for _, v := range s {
        acc = fn(acc, v)
    }
    return acc
}

nums := []int{1, 2, 3, 4, 5, 6, 7, 8}
evens := Filter(nums, func(n int) bool { return n%2 == 0 })
sum   := Reduce(evens, 0, func(acc, n int) int { return acc + n })
fmt.Println(evens) // [2 4 6 8]
fmt.Println(sum)   // 20

Section 15 — Enums

Q54 — What does this print?

type Weekday int

const (
    Monday Weekday = iota + 1
    Tuesday
    Wednesday
    Thursday
    Friday
    Saturday
    Sunday
)

func main() {
    fmt.Println(int(Monday), int(Wednesday), int(Sunday))
}

[!success]- Answer

1 3 7

iota + 1 starts the sequence at 1 (skipping 0). Each subsequent constant increments by 1: Monday=1, Tuesday=2, ..., Sunday=7.


Q55 — What's the output?

type Permission uint

const (
    Read    Permission = 1 << iota // 1
    Write                          // 2
    Execute                        // 4
)

func main() {
    perm := Read | Execute
    fmt.Println(perm)
    fmt.Println(perm&Write != 0)
    fmt.Println(perm&Read != 0)
}

[!success]- Answer

5
false
true

Read | Execute = 1 | 4 = 5. perm & Write = 5 & 2 = 0 (Write bit not set). perm & Read = 5 & 1 = 1 (Read bit is set).


Q56 — What's the bug?

type Direction int

const (
    North Direction = iota
    East
    South
    West
)

func describe(d Direction) string {
    switch d {
    case North: return "north"
    case East:  return "east"
    case South: return "south"
    }
    return ""
}

func main() {
    fmt.Println(describe(West))
    fmt.Println(describe(Direction(99)))
}

[!success]- Answer No compile error, but two logic bugs:

  1. West is not handled — describe(West) silently returns "". Go's compiler does not enforce exhaustive switches on enums.
  2. Direction(99) is a valid cast — any int can be coerced to your enum type. describe returns "" with no indication of the invalid value.

Fix: add case West: return "west" and a default: panic(...) or error return to catch invalid values.


Q57 — Coding Challenge

Define a Status enum (Pending, Active, Closed) starting from 1. Add a String() method. Write ParseStatus(s string) (Status, error) and demonstrate that fmt.Println uses the String() method automatically.

[!success]- Answer

type Status int

const (
    Pending Status = iota + 1
    Active
    Closed
)

func (s Status) String() string {
    switch s {
    case Pending: return "Pending"
    case Active:  return "Active"
    case Closed:  return "Closed"
    default:      return fmt.Sprintf("Status(%d)", int(s))
    }
}

func ParseStatus(s string) (Status, error) {
    switch s {
    case "Pending": return Pending, nil
    case "Active":  return Active, nil
    case "Closed":  return Closed, nil
    default:
        return 0, fmt.Errorf("unknown status: %q", s)
    }
}

func main() {
    fmt.Println(Active)          // Active  ← String() called by fmt
    fmt.Printf("%v\n", Closed)   // Closed

    s, err := ParseStatus("Pending")
    fmt.Println(s, err)          // Pending <nil>

    _, err = ParseStatus("Unknown")
    fmt.Println(err)             // unknown status: "Unknown"
}