Back to Go Examples
Lessons · Go · Computer Science

Data Structures and Algorithms

DSA stands for Data Structures and Algorithms: the ways a program can hold its data, and the step-by-step methods that work on it. This course covers them in Go, in the order that builds on itself — arrays first, dynamic programming last. Every module is “build it from scratch, then use it”: you implement the structure with no library help, then solve classic problems with it. Eighty tests across ten modules check your work, so you always know when you’re done.

The finish line — the course’s own progress report, after module 10
$ go run ./cmd/progress Data Structures and Algorithms in Go — progress 1 Arrays ████████████ 8/8 ✓ complete 2 Strings ████████████ 9/9 ✓ complete 3 Linked Lists ████████████ 7/7 ✓ complete 4 Stacks ████████████ 6/6 ✓ complete 5 Queues ████████████ 6/6 ✓ complete 6 Hashing ████████████ 9/9 ✓ complete 7 Trees ████████████ 10/10 ✓ complete 8 Heaps ████████████ 8/8 ✓ complete 9 Graphs ████████████ 8/8 ✓ complete 10 Dynamic Programming ████████████ 9/9 ✓ complete Overall: 80/80 tests passing (100%) Every test passes. Nice work.
Ten modules · tests 80
START

Get the course.

The course is a Go module: ten folders of lessons, exercise stubs, tests, and reference solutions, plus a small progress tool. Download it, unzip it, and work in your own editor — the tests run on your machine.

⬇ Download go-data-structures-and-algorithms.zip

Requirements: Go 1.22 or newer. No third-party dependencies. If go version doesn’t print anything yet, Install Go first.

Unzip it and check the reference solutions pass
unzip go-data-structures-and-algorithms.zip
cd go-data-structures-and-algorithms
go test -tags solutions ./...
$ go test -tags solutions ./... ok github.com/bozcode/go-dsa-course/01-arrays 0.008s ok github.com/bozcode/go-dsa-course/02-strings 0.013s ok github.com/bozcode/go-dsa-course/03-linked-lists 0.008s ok github.com/bozcode/go-dsa-course/04-stacks 0.004s ok github.com/bozcode/go-dsa-course/05-queues 0.009s ok github.com/bozcode/go-dsa-course/06-hashing 0.012s ok github.com/bozcode/go-dsa-course/07-trees 0.007s ok github.com/bozcode/go-dsa-course/08-heaps 0.010s ok github.com/bozcode/go-dsa-course/09-graphs 0.003s ok github.com/bozcode/go-dsa-course/10-dynamic-programming 0.004s ? github.com/bozcode/go-dsa-course/cmd/progress [no test files]
Then see where you stand — nothing done yet
$ go run ./cmd/progress Data Structures and Algorithms in Go — progress 1 Arrays ░░░░░░░░░░░░ 0/8 2 Strings ░░░░░░░░░░░░ 0/9 3 Linked Lists ░░░░░░░░░░░░ 0/7 4 Stacks ░░░░░░░░░░░░ 0/6 5 Queues ░░░░░░░░░░░░ 0/6 6 Hashing ░░░░░░░░░░░░ 0/9 7 Trees ░░░░░░░░░░░░ 0/10 8 Heaps ░░░░░░░░░░░░ 0/8 9 Graphs ░░░░░░░░░░░░ 0/8 10 Dynamic Programming ░░░░░░░░░░░░ 0/9 Overall: 0/80 tests passing (0%) Next up: Arrays → TestDynamicArrayPushGet go test -run '^TestDynamicArrayPushGet$' -v ./01-arrays/
Prefer reading on the web?

Every lesson, exercise file, test file, and solution is also on this site, one page per module, with copy buttons on the code. The module pages are listed below.

MODULES

Ten modules, each one built on the last.

Each module is build it from scratch, then use it: Part A has you implement the structure with no library help; later parts are classic problems that use it. Take them in order — each one leans on the ones before.

1

Arrays

The foundation. Contiguous memory, O(1) indexing, and the slice machinery Go builds on top of it.

Build: Dynamic array with doubling. Solve: Rotation, Kadane, prefix sums, two pointers.

2

Strings

Character manipulation, counting, sliding windows, and pattern search — with Go's bytes-versus-runes twist.

Solve: Palindromes, anagrams, sliding window, KMP.

3

Linked Lists

Nodes, pointers, and dynamic memory — the pointer-juggling skills trees and graphs depend on.

Build: Generic list with head/tail. Solve: Reverse, cycle detection, merge, n-th from end.

4

Stacks

Last in, first out. Matching, parsing, undo, and the monotonic-stack trick.

Build: Generic stack, min-stack. Solve: Brackets, RPN, monotonic stack.

5

Queues

First in, first out. Ring buffers, deques, and the queue behind every breadth-first search.

Build: Ring buffer, two-stack queue. Solve: Josephus, sliding-window max, rate counter.

6

Hashing

Hash tables, collisions, and load factors — then the map-powered patterns that turn O(n²) into O(n).

Build: Hash map with chaining + resize. Solve: Two Sum, group anagrams, top-k, subarray sums.

7

Trees

Hierarchies, recursion, traversals, and binary search trees.

Build: Binary search tree with delete. Solve: Traversals, validation, balance, LCA.

8

Heaps

Always know the smallest (or largest) item. Priority queues, top-k, and container/heap.

Build: Generic min-heap, heapify, heap sort. Solve: k-th largest, k-way merge, running median.

9

Graphs

Vertices and edges. BFS, DFS, topological order, Dijkstra, and union-find.

Build: Union-find. Solve: BFS, DFS, topological sort, Dijkstra, islands.

10

Dynamic Programming

Solve each subproblem once, remember the answer. Memoization, tabulation, and the classic DP families.

Solve: Coin change, LCS, edit distance, knapsack, LIS.

FILES

How each module works.

Every module folder has the same five files:

01-arrays/ ├── README.md ← the lesson: concepts, costs, Go specifics, a worked example, quiz ├── types.go ← structs and helpers you're given ├── exercises.go ← YOUR work: every function is a stub marked TODO ├── solution.go ← reference solutions (build tag: solutions) └── arrays_test.go ← tests — the same file checks yours or the reference

exercises.go and solution.go define the same functions behind opposite build tags, so only one is compiled at a time:

go test ./01-arrays/                     # test YOUR exercises
go test -tags solutions ./01-arrays/     # test the reference solutions
go test -run TestRotateRight -v ./01-arrays/   # one test, verbose
THE LOOP

Read, write, test, then compare.

  1. Read the module's README.md.
  2. Open exercises.go and work top to bottom. Each stub's comment gives the spec, an example, a hint at the pattern, and a target complexity.
  3. Run the tests. Make one pass, then the next.
  4. Answer the quiz at the bottom of the lesson.
  5. Then read solution.go and compare. Different-but-correct is fine; notice where the reference is simpler.
Don’t peek

Try not to open solution.go until your tests pass, or until you've been stuck for a solid half hour. If having it in the folder is too tempting, hide solution.go in your editor's file explorer (in VS Code: "files.exclude": {"**/solution.go": true}).

PROGRESS

Track your progress.

The course ships with a progress tool. It runs every module’s tests and shows how far along you are, and which test to do next:

$ go run ./cmd/progress Data Structures and Algorithms in Go — progress 1 Arrays ████████████ 8/8 ✓ complete 2 Strings █████░░░░░░░ 4/9 3 Linked Lists ░░░░░░░░░░░░ 0/7 4 Stacks ░░░░░░░░░░░░ 0/6 5 Queues ░░░░░░░░░░░░ 0/6 6 Hashing ░░░░░░░░░░░░ 0/9 7 Trees ░░░░░░░░░░░░ 0/10 8 Heaps ░░░░░░░░░░░░ 0/8 9 Graphs ░░░░░░░░░░░░ 0/8 10 Dynamic Programming ░░░░░░░░░░░░ 0/9 Overall: 12/80 tests passing (15%) Next up: Strings → TestCompress go test -run '^TestCompress$' -v ./02-strings/

That’s a real run, part-way through: module 1 done and the first four string exercises written. The Next up line gives you the exact command for the next failing test.

There’s also a Makefile with shortcuts: make test, make solutions, make progress, make vet.

▶ Makefile the shortcuts · 18 lines›
.PHONY: test solutions progress vet

# Test your exercises (every module). Expect failures until you implement them.
test:
	go test ./0* ./10*

# Test the reference solutions — should be all green.
solutions:
	go test -tags solutions ./...

# Show a per-module progress report.
progress:
	go run ./cmd/progress

# Vet both builds.
vet:
	go vet ./...
	go vet -tags solutions ./...
▶ go.mod one module, no dependencies · 3 lines›
module github.com/bozcode/go-dsa-course

go 1.22
▶ cmd/progress/main.go the progress tool itself · 215 lines›
// Command progress runs the course tests and shows how far along you are.
//
//	go run ./cmd/progress                 # your exercises
//	go run ./cmd/progress -tags solutions # the reference solutions (all green)
//
// Any arguments are passed straight through to `go test`.
package main

import (
	"bufio"
	"bytes"
	"encoding/json"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"sort"
	"strings"
)

type module struct {
	dir      string   // e.g. "01-arrays"
	title    string   // e.g. "Arrays"
	tests    []string // top-level test names, in file order
	passed   map[string]bool
	failed   map[string]bool
	buildErr bool
}

var testFunc = regexp.MustCompile(`(?m)^func (Test\w+)\(t \*testing\.T\)`)

func main() {
	root, err := findRoot()
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	mods, err := discover(root)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	args := append([]string{"test", "-json", "-count=1"}, os.Args[1:]...)
	for _, m := range mods {
		args = append(args, "./"+m.dir+"/")
	}
	cmd := exec.Command("go", args...)
	cmd.Dir = root
	var out bytes.Buffer
	cmd.Stdout = &out
	cmd.Stderr = &out
	_ = cmd.Run() // a non-zero exit just means some tests fail

	byPkg := map[string]*module{}
	for _, m := range mods {
		byPkg[m.dir] = m
	}
	sc := bufio.NewScanner(&out)
	sc.Buffer(make([]byte, 1024*1024), 1024*1024)
	for sc.Scan() {
		var ev struct {
			Action  string
			Package string
			Test    string
			Output  string
		}
		if json.Unmarshal(sc.Bytes(), &ev) != nil {
			continue
		}
		m := byPkg[filepath.Base(ev.Package)]
		if m == nil {
			continue
		}
		if ev.Test == "" {
			if ev.Action == "output" && strings.Contains(ev.Output, "[build failed]") {
				m.buildErr = true
			}
			continue
		}
		if strings.Contains(ev.Test, "/") {
			continue // subtest
		}
		switch ev.Action {
		case "pass":
			m.passed[ev.Test] = true
		case "fail":
			m.failed[ev.Test] = true
		}
	}

	report(mods)
}

func report(mods []*module) {
	fmt.Println()
	fmt.Println("  Data Structures and Algorithms in Go — progress")
	fmt.Println()
	total, done := 0, 0
	var nextMod *module
	var nextTest string
	for i, m := range mods {
		n := len(m.tests)
		p := 0
		for _, t := range m.tests {
			if m.passed[t] {
				p++
			} else if nextMod == nil {
				nextMod, nextTest = m, t
			}
		}
		total += n
		done += p

		status := ""
		switch {
		case m.buildErr:
			status = "  ✗ doesn't compile — run: go vet ./" + m.dir + "/"
		case p == n:
			status = "  ✓ complete"
		}
		fmt.Printf("  %2d  %-20s %s  %2d/%-2d%s\n", i+1, m.title, bar(p, n, 12), p, n, status)
	}
	fmt.Println()
	fmt.Printf("  Overall: %d/%d tests passing (%d%%)\n", done, total, pct(done, total))
	if nextMod != nil {
		fmt.Printf("  Next up: %s → %s\n", nextMod.title, nextTest)
		fmt.Printf("           go test -run '^%s$' -v ./%s/\n", nextTest, nextMod.dir)
	} else {
		fmt.Println("  Every test passes. Nice work.")
	}
	fmt.Println()
}

func bar(p, n, width int) string {
	if n == 0 {
		return strings.Repeat("·", width)
	}
	filled := p * width / n
	return strings.Repeat("█", filled) + strings.Repeat("░", width-filled)
}

func pct(a, b int) int {
	if b == 0 {
		return 0
	}
	return a * 100 / b
}

// discover finds the numbered module directories and their test names.
func discover(root string) ([]*module, error) {
	entries, err := os.ReadDir(root)
	if err != nil {
		return nil, err
	}
	numbered := regexp.MustCompile(`^\d\d-`)
	var mods []*module
	for _, e := range entries {
		if !e.IsDir() || !numbered.MatchString(e.Name()) {
			continue
		}
		m := &module{
			dir:    e.Name(),
			title:  titleFor(e.Name()),
			passed: map[string]bool{},
			failed: map[string]bool{},
		}
		files, _ := filepath.Glob(filepath.Join(root, e.Name(), "*_test.go"))
		sort.Strings(files)
		for _, f := range files {
			src, err := os.ReadFile(f)
			if err != nil {
				return nil, err
			}
			for _, match := range testFunc.FindAllSubmatch(src, -1) {
				m.tests = append(m.tests, string(match[1]))
			}
		}
		mods = append(mods, m)
	}
	sort.Slice(mods, func(i, j int) bool { return mods[i].dir < mods[j].dir })
	if len(mods) == 0 {
		return nil, fmt.Errorf("no module directories found under %s", root)
	}
	return mods, nil
}

// titleFor turns "03-linked-lists" into "Linked Lists".
func titleFor(dir string) string {
	words := strings.Split(dir[3:], "-")
	for i, w := range words {
		words[i] = strings.ToUpper(w[:1]) + w[1:]
	}
	return strings.Join(words, " ")
}

// findRoot walks up from the working directory to the directory holding go.mod.
func findRoot() (string, error) {
	dir, err := os.Getwd()
	if err != nil {
		return "", err
	}
	for {
		if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil {
			return dir, nil
		}
		parent := filepath.Dir(dir)
		if parent == dir {
			return "", fmt.Errorf("go.mod not found — run this from inside the course folder")
		}
		dir = parent
	}
}
PACE

How long it takes.

At a few hours a week, plan on about a week per module, with Trees, Graphs, and Dynamic Programming taking longer. Going faster is fine — going in order matters more: queues power BFS in trees and graphs, heaps power Dijkstra, and hashing shows up in nearly everything after Module 6.

Commit after each module so you can see your own progress in git log.

Related guides

The course leans on Big O from the first page — every exercise names a target like O(n). If that notation is new, Big O Notation, by Example is a one-sitting primer. And when you finish, the Go Interview Questions are the same patterns under exam conditions.

Build it, test it, then read the solution. Ten times.