Go Exercises

Practise what you've learned. Type the missing piece into each highlighted gap and press Check. Further down are programming challenges with editors and model solutions — copy them into a main.go file and run them with go run.

Fill in the blanks

Basics

Every executable program's files belong to this package.

package 

Import the formatting package.

import ""

Declare and initialise a variable inside a function, letting Go infer its type.

count  10

Declare a variable with an explicit type and its zero value.

 total float64

Print a value's type with Printf.

fmt.Printf("\n", x)

Convert an int to a float64 before dividing.

avg := (sum) / float64(n)

A counter that starts at 0 in a const block.

const (
	Small = 
	Medium
	Large
)

Print a float with two decimal places.

fmt.Printf("\n", price)

Print a string with quotes and escapes, useful for debugging.

fmt.Printf("\n", s)

Control flow and strings

Declare a variable that exists only inside the if statement.

if n, err := strconv.Atoi(s) err != nil {
	return err
}

Count from 0 to 4 with the shortest loop form.

for i :=  5 {
	fmt.Println(i)
}

Go's only loop keyword, used here as a while loop.

 balance < 2000 {
	balance *= 1.07
}

Continue into the next case of a switch.

case 2:
	fmt.Println("reports")
	

Leave both loops at once.

outer:
	for _, row := range grid {
		for _, v := range row {
			if v < 0 {
				break 
			}
		}
	}

Count the characters (not bytes) in a string.

n := utf8.(s)

Split a string around the first "=".

key, value, ok := strings.(line, "=")

Convert text to an int.

n, err := strconv.("42")

The efficient way to build a string in a loop.

var b strings.

Collections and functions

Add an element to a slice.

names = (names, "Ana")

Make a slice with length 0 and room for 100 elements.

buf := make([]byte, 0, )

Check whether a key is in a map.

price,  := prices["apple"]

Remove a key from a map.

(stock, "pens")

Sort a slice of ints in place.

slices.(nums)

Accept any number of int arguments.

func sum(nums int) int

Close the file when the function returns.

 f.Close()

Types, methods and errors

Get the address of a variable.

p := score

A method that can modify its receiver.

func (a Account) Deposit(n int) { a.Balance += n }

Declare an interface type.

type Shape  {
	Area() float64
}

The method that makes fmt print your type nicely.

func (d Weekday) () string

Branch on the dynamic type of an interface value.

switch v := x.() {
case int:
	// ...
}

Create a simple error value.

return .New("not found")

Wrap an error so it can still be inspected.

return fmt.Errorf("loading config: ", err)

Check for a sentinel error anywhere in the chain.

if errors.(err, os.ErrNotExist) {

Stop a panic inside a deferred function.

if r := (); r != nil {

Generics and concurrency

A type parameter for values that can be compared with == .

func Index[T ](items []T, v T) int

A constraint for values that support < and >.

func Largest[T cmp.](items []T) T

The type of an iterator that yields single values.

func Countdown(n int) iter.[int]

Start a function in a new goroutine.

 worker(jobs)

Start a goroutine and track it in a WaitGroup.

wg.(func() { process(item) })

Create an unbuffered channel of strings.

ch := (chan string)

Tell receivers no more values are coming.

(results)

Wait on several channel operations at once.

 {
case msg := <-ch:
	fmt.Println(msg)
case <-time.After(time.Second):
	fmt.Println("timeout")
}

Lock a mutex and unlock it when the function returns.

c.mu.Lock()
defer c.mu.()

A context that is cancelled automatically after two seconds.

ctx, cancel := context.(ctx, 2*time.Second)
defer cancel()

Standard library and tools

Read a whole file into memory.

data, err := os.("notes.txt")

Encode a value as JSON.

data, err := json.(order)

The reference layout for a date like 2026-10-08.

t.Format("")

Route only GET requests with an id wildcard.

mux.HandleFunc(" /tasks/{id}", getTask)

The name of every test function starts with this word.

func Initials(t *testing.T)

Run tests with the race detector.

go test  ./...

Programming challenges

1. Word frequencies

Count how often each word appears in a text, ignoring case and punctuation, and print the words with their counts — most frequent first, alphabetical for ties.

Try it yourself · main.go
Edit the code, copy it into main.go in a new folder and run it with go run main.go (Go 1.27 or later). How to install Go
Show a model solution
GO
package main

import (
	"cmp"
	"fmt"
	"maps"
	"slices"
	"strings"
	"unicode"
)

func main() {
	text := "The cat and the hat. The bat and the cat!"

	words := strings.FieldsFunc(strings.ToLower(text), func(r rune) bool {
		return !unicode.IsLetter(r)
	})
	counts := map[string]int{}
	for _, w := range words {
		counts[w]++
	}

	keys := slices.SortedFunc(maps.Keys(counts), func(a, b string) int {
		return cmp.Or(cmp.Compare(counts[b], counts[a]), cmp.Compare(a, b))
	})
	for _, w := range keys {
		fmt.Printf("%-4s %d\n", w, counts[w])
	}
}
Output
the  4
and  2
cat  2
bat  1
hat  1

2. A bank account

Write an Account type with Deposit and Withdraw methods that return errors for non-positive amounts and insufficient funds (use sentinel errors), and a transaction history.

Try it yourself · main.go
Edit the code, copy it into main.go in a new folder and run it with go run main.go (Go 1.27 or later). How to install Go
Show a model solution
GO
package main

import (
	"errors"
	"fmt"
)

var (
	ErrInvalidAmount = errors.New("amount must be positive")
	ErrInsufficient  = errors.New("insufficient funds")
)

type Account struct {
	Owner   string
	balance int
	history []string
}

func (a *Account) Deposit(n int) error {
	if n <= 0 {
		return ErrInvalidAmount
	}
	a.balance += n
	a.history = append(a.history, fmt.Sprintf("+%d", n))
	return nil
}

func (a *Account) Withdraw(n int) error {
	if n <= 0 {
		return ErrInvalidAmount
	}
	if n > a.balance {
		return fmt.Errorf("withdrawing %d from %d: %w", n, a.balance, ErrInsufficient)
	}
	a.balance -= n
	a.history = append(a.history, fmt.Sprintf("-%d", n))
	return nil
}

func (a *Account) Balance() int { return a.balance }

func main() {
	a := &Account{Owner: "Ana"}
	fmt.Println(a.Deposit(100), a.Withdraw(30), a.Deposit(-5))
	err := a.Withdraw(500)
	fmt.Println(err, errors.Is(err, ErrInsufficient))
	fmt.Println(a.Balance(), a.history)
}
Output
<nil> <nil> amount must be positive
withdrawing 500 from 70: insufficient funds true
70 [+100 -30]

3. A generic Filter and Map

Write generic Filter and Map functions for slices, and use them to turn a list of temperatures into labels for the days above 20°C.

Try it yourself · main.go
Edit the code, copy it into main.go in a new folder and run it with go run main.go (Go 1.27 or later). How to install Go
Show a model solution
GO
package main

import "fmt"

func Filter[T any](items []T, keep func(T) bool) []T {
	var out []T
	for _, v := range items {
		if keep(v) {
			out = append(out, v)
		}
	}
	return out
}

func Map[T, U any](items []T, f func(T) U) []U {
	out := make([]U, 0, len(items))
	for _, v := range items {
		out = append(out, f(v))
	}
	return out
}

func main() {
	temps := []float64{18.5, 22.1, 19.9, 25.0, 20.4}
	warm := Filter(temps, func(t float64) bool { return t > 20 })
	labels := Map(warm, func(t float64) string { return fmt.Sprintf("%.1f°C", t) })
	fmt.Println(labels)
}
Output
[22.1°C 25.0°C 20.4°C]

4. Concurrent URL checker

Check a list of "URLs" concurrently with at most two checks running at once, and print the results in the original order. Simulate each check with a function that sleeps and reports whether the name contains "down".

Try it yourself · main.go
Edit the code, copy it into main.go in a new folder and run it with go run main.go (Go 1.27 or later). How to install Go
Show a model solution
GO
package main

import (
	"fmt"
	"strings"
	"sync"
	"time"
)

func check(url string) bool {
	time.Sleep(30 * time.Millisecond)
	return !strings.Contains(url, "down")
}

func main() {
	urls := []string{"a.example", "down.example", "b.example", "c.example"}
	ok := make([]bool, len(urls))
	sem := make(chan struct{}, 2) // at most two at a time

	var wg sync.WaitGroup
	for i, u := range urls {
		wg.Go(func() {
			sem <- struct{}{}
			defer func() { <-sem }()
			ok[i] = check(u)
		})
	}
	wg.Wait()

	for i, u := range urls {
		status := "up"
		if !ok[i] {
			status = "DOWN"
		}
		fmt.Printf("%-13s %s\n", u, status)
	}
}
Output
a.example     up
down.example  DOWN
b.example     up
c.example     up

5. A tested function

Write IsPalindrome(s string) bool that ignores case, spaces and punctuation and works with accented letters, plus a table-driven test. Run it with go test -v.

Try it yourself · main.go
Edit the code, copy it into main.go in a new folder and run it with go run main.go (Go 1.27 or later). How to install Go
Show a model solution
GO palindrome.go
package main

import (
	"strings"
	"unicode"
)

func IsPalindrome(s string) bool {
	var letters []rune
	for _, r := range strings.ToLower(s) {
		if unicode.IsLetter(r) || unicode.IsDigit(r) {
			letters = append(letters, r)
		}
	}
	for i, j := 0, len(letters)-1; i < j; i, j = i+1, j-1 {
		if letters[i] != letters[j] {
			return false
		}
	}
	return true
}
GO palindrome_test.go
package main

import "testing"

func TestIsPalindrome(t *testing.T) {
	tests := []struct {
		in   string
		want bool
	}{
		{"racecar", true},
		{"A man, a plan, a canal: Panama!", true},
		{"Été", true},
		{"gopher", false},
		{"", true},
	}
	for _, tt := range tests {
		if got := IsPalindrome(tt.in); got != tt.want {
			t.Errorf("IsPalindrome(%q) = %v, want %v", tt.in, got, tt.want)
		}
	}
}
Output
=== RUN   TestIsPalindrome
--- PASS: TestIsPalindrome (0.00s)
PASS
ok  	example	1.691s