Go
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions and Methods
-
Section 4: Concurrency
-
Section 5: Packages and Tooling
-
Section 6: More Standard Library
-
Section 7: Building Services
-
Section 8: Advanced Go
-
Section 9: Go in the Cloud-Native Ecosystem
-
Section 10: Data Structures and Algorithms in Go
-
Section 11: Testing and Deployment
-
Section 12: Practical Projects
-
Section 13: More Standard Library Practice
-
Section 14: More Practice Projects
-
Section 15: Design Patterns in Go
-
Section 16: Interview Practice
-
Section 17: Package fmt In Depth
-
Section 18: Package strings and strconv
-
Section 19: Package os and io
-
Section 20: Package time
-
Section 21: Package sort and container
-
Section 22: Package encoding
-
Section 23: Package net/http In Depth
-
Section 24: Package context
-
Section 25: Package regexp and bytes
-
Section 26: Package errors In Depth
-
Section 27: Package crypto and hash
-
Section 28: Package flag and log
-
Section 29: Package sync In Depth
-
Section 30: More Practice Exercises
-
Section 31: Go Modules and Workspaces In Depth
-
Section 32: Generics Deep Dive (Go 1.18+)
-
Section 33: Testing Package In Depth
-
Section 34: More Interview and Whiteboard Practice
-
Section 35: Package math and unicode
-
Section 36: Package path and filepath
-
Section 37: Package database/sql
-
Section 38: Package text/template and html/template
-
Section 39: Package archive and compress
-
Section 40: Lower-Level net Package
-
Section 41: Package runtime and reflect
-
Section 42: Package embed and io/fs
-
Section 43: Go Assembly and CGO Basics
-
Section 44: Building CLIs and TUIs
-
Section 45: Go Performance Tuning
-
Section 46: More Real-World Projects
-
Section 47: Go in Production
-
Section 48: Go Design Patterns
-
Section 49: Go Interfaces Deep Dive
-
Section 50: Final Practice and Review
72: Implementing a Graph and BFS/DFS in Go
When I first started implementing graphs in Go, I fell into the "Java Trap." I spent hours designing these elaborate Node structs that held pointers to a slice of other Node structs. It felt "object-oriented" and intuitive. But in practice, it's a nightmare. Managing those pointers leads to circular reference headaches, makes debugging a slog because you're staring at memory addresses in the debugger, and frankly, it's not how we do things in Go.
Stop trying to build graphs with pointer-heavy Node structs
If you try to build a graph by linking objects together via pointers, you'll find that simple tasks—like checking if a node has been visited—require you to add a Visited bool field to your struct. But then what happens when you want to run two searches concurrently? You're stuck. You can't just reset the boolean without locking the whole graph.
// The "Wrong" Way (Don't do this)
type Node struct {
Value string
Children []*Node
Visited bool // This is a disaster for concurrency or multiple passes
}
The better way—the Go way—is to decouple the topology of the graph from the state of your search. We use an adjacency list. In Go, the most flexible version of this is a map where the keys are your unique identifiers (like strings or ints) and the values are slices of those identifiers.
The Adjacency List: Leveraging Maps for Flexibility
By using a map[string][]string, the graph becomes a simple lookup table. If you need to know who "Station A" is connected to, you just hit the map. It's clean, it's fast, and it keeps your data separate from your logic.
type Graph struct {
nodes map[string][]string
}
func NewGraph() *Graph {
return &Graph{nodes: make(map[string][]string)}
}
func (g *Graph) AddEdge(u, v string) {
g.nodes[u] = append(g.nodes[u], v)
// For an undirected graph, add the reverse edge too:
// g.nodes[v] = append(g.nodes[v], u)
}
BFS: Finding the Shortest Path with a Queue
Breadth-First Search is all about layers. Imagine you're dropping a pebble in a pond; the ripples move outward. In Go, we implement this using a slice as a queue. The key here is the visited map. Notice how it's created inside the search function, not stored on the graph itself. This allows multiple goroutines to search the same graph simultaneously without stepping on each other's toes.
func (g *Graph) BFS(start string) {
visited := make(map[string]bool)
queue := []string{start}
visited[start] = true
for len(queue) > 0 {
// Dequeue the first element
curr := queue[0]
queue = queue[1:]
fmt.Println("Visited:", curr)
for _, neighbor := range g.nodes[curr] {
if !visited[neighbor] {
visited[neighbor] = true
queue = append(queue, neighbor)
}
}
}
}
DFS: Diving Deep with Recursion
While BFS is about breadth, Depth-First Search is about exploration. It's the "go down this hallway until you hit a wall, then backtrack" approach. Recursion is the most natural way to write this in Go, though you can use a stack if you're worried about extremely deep graphs and stack overflows (though for most real-world data, recursion is just fine).
func (g *Graph) DFS(start string) {
visited := make(map[string]bool)
g.dfsRecursive(start, visited)
}
func (g *Graph) dfsRecursive(curr string, visited map[string]bool) {
visited[curr] = true
fmt.Println("Visited:", curr)
for _, neighbor := range g.nodes[curr] {
if !visited[neighbor] {
g.dfsRecursive(neighbor, visited)
}
}
}
I'll leave you with a tip: if you're implementing these for a production system, consider using a map[int][]int if your nodes can be mapped to integers. It's significantly faster than using strings as keys. But for most logic problems, the string map is the gold standard for readability.
📋 Practical Task
Exercise: Implementing a Social Network Connection Validator
You are building a simplified "Connection Validator" for a professional networking site. Your goal is to determine if two users are connected through any chain of mutual connections.
Requirements:
- Implement a
Networkstruct using an adjacency list (map). - Implement a method
AddConnection(user1, user2 string)that creates a bidirectional link between two users. - Implement a method
AreConnected(startUser, targetUser string) bool. This method should use either BFS or DFS to returntrueif there is a path between the two users, andfalseotherwise.
Test Case:
net := NewNetwork()
net.AddConnection("Alice", "Bob")
net.AddConnection("Bob", "Charlie")
net.AddConnection("David", "Eve")
fmt.Println(net.AreConnected("Alice", "Charlie")) // Expected: true
fmt.Println(net.AreConnected("Alice", "Eve")) // Expected: false
There are no comments for now.