Skip to content

Catalog

All 138 exercises in one place — filter by check mode or topic, or search. Click any row for the run command and the hint; expand a topic header for what it teaches.

  • 138Exercises
  • 46Topics
  • 26🔨 Compile
  • 112🧪 Test
Two check modes: 🔨 Compile — the exercise passes once it builds (and the linter is happy). 🧪 Test — a test file drives it; make the failing tests pass. Every exercise ships broken on purpose — remove the I AM NOT DONE marker once it's fixed.
Mode Topic Exercise Description
Compile Variables variables1 Give the variable a name so it can be declared.
Compile Variables variables2 Declare a variable before assigning to it.
Compile Variables variables3 A variable declaration needs a type or an initial value.
Compile Variables variables4 See what happens when a block reuses a name with a new type.
Compile Variables variables5 Constants must be given a value when declared.
Compile Variables variables6 Constants cannot be reassigned after declaration.
Test Primitive Types primitive_types1 Toggle a boolean before the second check. A bool can be reassigned a new value; here the store opens, then closes, so each check fires exactly once.
Compile Primitive Types primitive_types2 Declare the string before formatting it.
Compile Primitive Types primitive_types3 Declare the variables passed to Printf.
Compile Primitive Types primitive_types4 Store a character in a byte value.
Compile Primitive Types primitive_types5 Use Go's real numeric type names.
Test If if1 Return the larger of two numbers using if.
Test If if2 Map each input string to an output with if/else.
Compile Switch switch1 Switch on a variable instead of bare values.
Compile Switch switch2 A conditionless switch needs a boolean case or default.
Test Switch switch3 Return a weekday name for a number using a switch.
Compile Functions functions1 Define the function that main calls.
Compile Functions functions2 Function parameters must declare their types.
Compile Functions functions3 Call a function with the argument it expects.
Compile Functions functions4 A function returning a value must declare its return type.
Test More Functions morefn1 morefn1 — recursion A function may call itself. Every recursion needs a base case to stop. Implement factorial recursively.
Test More Functions morefn2 morefn2 — variadic functions A variadic parameter (nums ...int) accepts any number of arguments and is seen inside the function as a slice. A slice can be spread with slice... .
Test Strings strings1 The strings package holds the everyday string utilities.
Test Strings strings2 A Go string is bytes, not characters. len(s) counts BYTES; one multibyte UTF-8 character (like é or 世) is several bytes. Count runes instead.
Test Arrays arrays1 Arrays are zero-indexed — fix the out-of-range access. The first element is at index 0 and the last of a length-3 array is at index 2.
Compile Arrays arrays2 An array can't mix types; every element shares one type.
Compile Slices slices1 Create a slice with make, giving its element type.
Test Slices slices2 Take a sub-slice using [low:high] bounds. A sub-slice includes low up to but NOT including high, so the last two of a length-4 array are [2:4].
Test Slices slices3 Add elements to a slice with append. append returns a new slice with the extra elements added to the end.
Test Slices slices4 Index a slice within its bounds.
Compile Maps maps1 Declare a map's key and value types.
Test Maps maps2 Initialize a map with a literal. A map literal builds and fills a map in one expression: map[K]V{k: v, ...}.
Test Maps maps3 Look up the correct key in a map.
Compile Range range1 Iterate a slice with range.
Compile Range range2 Iterate a map's keys and values with range.
Test Range range3 Use range to collect the even numbers.
Test Structs structs1 Define the struct's fields. A struct groups named fields, each with its own type.
Test Structs structs2 Embed a struct instead of adding a plain field. Embedding promotes the inner struct's fields onto the outer one, so they can be read directly — person.phone rather than person.contact.phone.
Test Structs structs3 Attach a method to a struct. A method returning a formatted string is easy to get subtly wrong — a missing space between the names still compiles. The test pins the exact result.
Test Pointers pointers1 A pointer holds the address of a value. &x takes the address; *p reads or writes the value at that address. Modify the caller's int through a pointer.
Test Pointers pointers2 Passing a *struct lets a function mutate the caller's struct. Go auto-dereferences, so you write a.Field even when a is a pointer.
Test Pointers pointers3 A value parameter receives a COPY, so changes don't reach the caller. Only a pointer parameter can mutate the caller's data. Make incPointer work (incValue is intentionally a no-op from the caller's view).
Test Methods methods1 Methods attach behavior to a type. A value receiver (r Rectangle) works on a copy and can only read; a pointer receiver (r *Rectangle) can mutate the original. Implement both.
Test Methods methods2 You can define methods on ANY named type you declare, not just structs.
Test Interfaces interfaces1 An interface is a set of method signatures. A type satisfies it IMPLICITLY, just by having those methods — there is no "implements" keyword. Give Rectangle and Circle an Area method so both satisfy Shape.
Test Interfaces interfaces2 Implementing fmt.Stringer (String() string) controls how a value prints. The `var _ fmt.Stringer = p` line is a compile-time check that Point satisfies the interface.
Test Interfaces interfaces3 Recover the concrete type held by an interface value with a type switch (or a comma-ok type assertion).
Test Interfaces interfaces4 Interfaces can embed other interfaces. A type satisfies the combined interface by implementing every embedded method. Implement *Buffer.
Test Enums enums1 Go has no enum keyword; the idiom is a named integer type plus a const block using iota, which yields 0, 1, 2, ... for successive constants.
Test Enums enums2 Pair iota constants with a String() method to get readable names (and free pretty-printing via fmt, since this satisfies fmt.Stringer).
Compile Type Aliases typealias1 A defined type like `type Celsius float64` is DISTINCT from float64 — Go will not mix the two without an explicit conversion, which stops accidental unit mix-ups at compile time.
Test Type Aliases typealias2 Methods can be attached to a defined type. Give Celsius a String() method and the fmt package will use it automatically (the fmt.Stringer interface).
Test Type Aliases typealias3 A type ALIAS (`type MyByte = byte`, note the = sign) is not a new type — it is a second name for the SAME type. Values are interchangeable with no conversion, unlike a defined type.
Compile Anonymous Functions anonymous_functions1 Pass an argument to an anonymous function.
Compile Anonymous Functions anonymous_functions2 Match a closure's signature and use its argument.
Compile Anonymous Functions anonymous_functions3 Return a closure that remembers state across calls.
Test Defer defer1 Deferred calls run when the surrounding function returns, in LIFO order (last deferred runs first). Using a named return value, the defers can even shape what is returned.
Test Defer defer2 defer is the idiomatic way to guarantee cleanup runs on every return path (the classic `f, _ := os.Open(...); defer f.Close()` pattern).
Test Errors errors1 In Go, errors are ordinary values returned alongside results. Make divide return an error when the divisor is zero.
Test Errors errors2 Wrapping an error with %w keeps the original reachable via errors.Is. Make lookup wrap the ErrNotFound sentinel.
Test Errors errors3 A custom error type is any type implementing the error interface. errors.As extracts a wrapped error of a concrete type. Implement ValidationError.Error so the message is built from its field.
Test Errors errors4 panic/recover is Go's last-resort mechanism, not normal error handling. Use a deferred function to recover from a panic and turn it into an error.
Test Errors errors5 errors.Join (Go 1.20) bundles several errors into one. errors.Is then matches ANY of the joined errors — handy for collecting validation failures.
Test Errors errors6 errors.Is walks the ENTIRE wrap chain: fmt.Errorf("...: %w", err) links a new message onto an existing error, and errors.Is finds a sentinel no matter how many layers deep it sits. Using %v instead of %w flattens the error to plain text and severs the chain.
Compile Generics generics1 Write one function that works for any type.
Test Generics generics2 Constrain a generic to add numbers of any numeric type. A type constraint lists the types a parameter may take; a generic function then works for every one of them while keeping the same body.
Test Generics generics3 Build a generic data structure: a Stack[T] that works for any element type.
Test Generics generics4 Write a generic Reduce that folds any slice into a single accumulated value.
Test Modern modern1 Go 1.21 added built-in min and max (any ordered type, two or more args) and clear (empties a map or zeroes a slice). No import needed.
Test Modern modern2 Go 1.22: a for-range loop can iterate over an integer directly — `for i := range n` runs with i = 0, 1, ..., n-1.
Test Modern modern3 Go 1.23: for-range can iterate over an iterator function. An iter.Seq[V] is func(yield func(V) bool) — call yield for each value; stop if it returns false. Implement the iterator's body.
Test Modern modern4 Go 1.25: WaitGroup.Go(f) launches f in its own goroutine and takes care of Add(1) and Done() for you. It replaces the classic — and easy to get wrong — wg.Add(1); go func() { defer wg.Done(); ... }() dance.
Test Iterators iter1 iter.Seq[V] is func(yield func(V) bool) — the shape of a range-over-func iterator. A Filter wraps a source sequence and re-yields only the values that match a predicate.
Test Iterators iter2 iter.Seq2[K, V] is func(yield func(K, V) bool) — an iterator over pairs, like ranging a map or a slice's index/value. Enumerate pairs each element with its index, so the FIRST value yielded is the index and the SECOND is the element.
Test Iterators iter3 slices.Collect(seq) drains an iter.Seq[V] into a []V. It does NOT accept an iter.Seq2[K, V]; to collect the values of a pair-iterator you first project it down to a single-value iterator. valuesOf is that adapter.
Test Dependency Injection di1 Inject an io.Writer so the output can be captured and asserted in a test.
Test Dependency Injection di2 Inject behaviour through a small interface to make time-dependent code testable.
Test Dependency Injection di3 Constructor injection: a struct holds a dependency interface and delegates to it.
Test Mocking mock1 A spy is a test double that records how it was called, so you can assert on it.
Test Mocking mock2 A stub/fake returns canned data so you can test logic, including error paths.
Test Concurrent concurrent1 Print from goroutines and wait with a WaitGroup.
Test Concurrent concurrent2 Safely increment a counter from many goroutines.
Test Concurrent concurrent3 Send values over a channel and receive them.
Test Channels channels1 A buffered channel — make(chan T, n) — holds up to n values without a receiver ready. An unbuffered channel blocks the sender until someone receives, which would deadlock the single-goroutine loop below.
Test Channels channels2 Channel direction in a signature documents and enforces intent: chan<- T is send-only, <-chan T is receive-only.
Test Select select1 select waits on multiple channel operations and proceeds with whichever one is ready. Return the first value available from either channel.
Test Select select2 Pairing a receive with time.After in a select implements a timeout.
Test Select select3 A `default` case makes a select non-blocking: it runs when no other case is ready instead of waiting.
Test Sync sync1 A sync.Mutex guards shared state so concurrent goroutines don't race. Run with `go test -race` to see the unsynchronized version flagged.
Test Sync sync2 sync.Once runs a function exactly once, even when called from many goroutines.
Test Sync sync3 sync/atomic provides lock-free atomic operations on integers — cheaper than a mutex for a simple counter. Run with `go test -race` to catch the bug.
Test Context context1 A context.Context lets a caller signal cancellation to running work. Make countUntilCancelled return once the context is cancelled.
Test Context context2 context.WithTimeout cancels itself after a duration. Make doWork abort early and return ctx.Err() when the context is done.
Test Context context3 Contexts can carry request-scoped values down a call chain. Read a value out of the context, falling back to a default when absent.
Test Concurrency Patterns concpat1 concpat1 — worker pool A fixed set of worker goroutines pulls jobs from one channel and pushes results to another. Implement the worker so the pool produces every result.
Test Concurrency Patterns concpat2 concpat2 — fan-in Fan-in merges values from several input channels into one output channel. Implement merge so it copies every input into out and closes out when done.
Test Concurrency Patterns concpat3 concpat3 — pipeline A pipeline chains stages connected by channels: each stage reads from the previous stage and emits to the next. Implement the `double` stage.
Test Goroutine Safety safety1 A value shared across goroutines needs synchronization. sync.RWMutex allows many concurrent readers (RLock) but an exclusive writer (Lock) — a good fit for read-heavy state. With no lock at all, concurrent access is a data race.
Test Goroutine Safety safety2 sync.Map is a concurrent map built for stable keys and many reads. Its Store/Load methods are safe for simultaneous use — a plain map is not, and concurrent writes to one crash with "fatal error: concurrent map writes".
Test Goroutine Safety safety3 Channel ownership: the goroutine that OWNS a channel closes it — exactly once. Consumers must never close a channel they only receive from. Several workers each closing the same channel panics with "close of closed channel".
Test Synctest synctest1 testing/synctest (Go 1.25) runs test code in a "bubble". synctest.Wait() blocks until every goroutine in the bubble is idle — a deterministic replacement for time.Sleep-and-hope. But Wait only works inside a bubble; call it outside one and it panics. The body must be wrapped in synctest.Test.
Test Synctest synctest2 Inside a synctest bubble time is virtual: when EVERY goroutine (including the test's own) is blocked, the fake clock jumps to the next timer. So a one-hour time.Sleep finishes instantly — but only once the test goroutine actually blocks waiting for it. Read the clock without waiting and no time passes.
Test Stdlib Essentials stdlib1 stdlib1 — encoding/json Struct field tags control the JSON key names used during (un)marshaling. Add tags so the JSON keys map onto the struct fields.
Test Stdlib Essentials stdlib2 stdlib2 — io.Reader / io.Writer These two interfaces are the backbone of Go I/O. io.Copy streams bytes from any Reader to any Writer. Drain the reader into the buffer.
Test Stdlib Essentials stdlib3 stdlib3 — slices The slices package (Go 1.21+) provides generic helpers over slices. Sort the numbers in DESCENDING order.
Test Stdlib Essentials stdlib4 stdlib4 — time Go formats and parses time using a REFERENCE date, not strftime codes: Mon Jan 2 15:04:05 MST 2006 (i.e. 01/02 03:04:05PM '06 -0700) Parse a date string using the layout "2006-01-02".
Test Stdlib Essentials stdlib5 stdlib5 — strconv strconv converts between strings and numbers (and other base types).
Test Stdlib Essentials stdlib6 stdlib6 — regexp regexp matches and extracts text using regular expressions.
Test Maps Package mapspkg1 The maps package (Go 1.21+) has helpers for whole-map operations. maps.Clone returns an independent shallow copy — unlike assigning a map value, where both names refer to the SAME underlying map and a write through one is seen by the other.
Test Maps Package mapspkg2 maps.DeleteFunc(m, pred) (Go 1.21) removes every entry for which pred(k, v) is true, in a single pass — no collect-the-keys-then-delete two-step needed.
Test Structured Logging slog1 log/slog (Go 1.21) is structured logging in the stdlib. A handler has a minimum level and drops records below it. Here the handler is pinned to LevelWarn, so Info messages vanish.
Test Structured Logging slog2 logger.With(attrs...) returns a NEW logger that carries those attributes on every record it writes. It does not modify the receiver — you have to use the logger it returns.
Test Structured Logging slog3 A slog.Handler decides what happens to each record. Implementing one lets you capture logs in memory for tests. Every method is provided except Handle, which must record the message.
Test Reflection reflect1 Inspect a value's kind at runtime with the reflect package.
Test Reflection reflect2 Walk a struct's fields with reflection, calling a function for each string.
Test Unsafe Pkg unsafe1 unsafe.Offsetof(x.Field) reports the byte offset of Field within its struct — layout introspection with zero runtime cost and no reflection.
Test Unsafe Pkg unsafe2 unsafe.String(ptr, len) builds a string that SHARES the backing array of a []byte — a zero-copy conversion for hot paths, unlike string(b) which allocates and copies. The bytes must not be mutated afterwards.
Test Files files1 files1 — reading and writing files os.WriteFile and os.ReadFile handle whole-file I/O in a single call. (The test uses t.TempDir(), a temp directory cleaned up automatically.)
Test Files files2 files2 — reading line by line with bufio.Scanner bufio.Scanner reads any io.Reader one line at a time, without loading the whole input into memory — the usual way to process files or stdin.
Test Http Client http1 http1 — HTTP client net/http makes requests. http.Get performs a GET; always close the response body. The test spins up a local httptest.Server so the call is deterministic.
Test Http Server httpsrv1 Write an http.HandlerFunc and test it with httptest, no real network needed.
Test Http Server httpsrv2 Route requests with http.ServeMux using Go 1.22 method + path patterns.
Test Http Server httpsrv3 Decode a JSON request body and encode a JSON response with encoding/json.
Test Http Server httpsrv4 Wrap a handler with middleware (a func(http.Handler) http.Handler).
Test Http Server Advanced httpadv1 net/http's CrossOriginProtection (Go 1.25) blocks cross-site, state-changing requests using the browser's Sec-Fetch-Site metadata — CSRF defense with no tokens or cookies. You opt in by wrapping your handler with it.
Test Http Server Advanced httpadv2 Go 1.22's ServeMux supports method + wildcard patterns like "GET /users/{id}/posts/{postID}". r.PathValue("postID") returns that path segment — but ONLY if the name matches a {wildcard} in the pattern exactly.
Test Http Server Advanced httpadv3 httputil.ReverseProxy.Rewrite is the modern hook (replacing the deprecated Director field) for retargeting a request. Inside it, pr.SetURL(target) points the outbound request at the backend while preserving the inbound path.
Test Cli cli1 Parse command-line flags with the flag package.
Test Cli cli2 Read the positional arguments left after flag parsing with FlagSet.Args.
Test Testing Advanced testadv1 testadv1 — table-driven tests with t.Run Table-driven tests pair a slice of cases with t.Run subtests, so each case reports independently. Implement fizzbuzz to make the table pass.
Test Testing Advanced testadv2 testadv2 — fuzzing A fuzz test (func FuzzXxx(*testing.F)) feeds generated inputs to find edge cases; its seed corpus also runs during a normal `go test`. This reverse is buggy: it reverses BYTES, corrupting multibyte UTF-8. Fix it to reverse RUNES so the seed "Hello, 世界" stays valid.
Test Testing Advanced testadv3 testadv3 — httptest net/http/httptest exercises HTTP handlers without a real network: build a request, record the response, assert on it. Implement the handler.
Test Testing Advanced testadv4 testadv4 — benchmarks A benchmark (func BenchmarkXxx(*testing.B)) measures performance; run it with `go test -bench=.`. The Go 1.24+ form is `for b.Loop() { ... }`. The benchmark below is complete — your job is to implement sumSlice so the correctness test passes (normal `go test` runs the Test, not the Benchmark).
Test Profiling pprof1 runtime/pprof writes a CPU profile you later inspect with `go tool pprof`. StartCPUProfile(w) begins sampling; the profile is only flushed to w when you call StopCPUProfile. Forget it and the output stays empty.
Test Profiling pprof2 runtime.ReadMemStats fills a MemStats snapshot of the heap. To measure how much memory a piece of code retains, read stats before and after — calling runtime.GC() first so previously-freed garbage is not counted.
Test Profiling pprof3 Importing net/http/pprof for its side effects registers the /debug/pprof/ handlers on http.DefaultServeMux. A server that uses its OWN mux won't expose them unless it forwards that path prefix to the default mux.
Test Applied applied1 applied1 — sort.Interface Implementing sort.Interface (Len, Less, Swap) lets sort.Sort order ANY type. Len is provided; add Less and Swap.
Test Applied applied2 applied2 — a concurrency-safe store Integrate several ideas at once: a map for storage, a sync.Mutex for safety, methods for the API, and a sentinel error for the missing-key case. Implement Set.

▶ Run it

From the repo root: — or mise run watch to work through everything in order with instant feedback.