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
129: container/ring
I was working on a small internal tool the other day where I needed to rotate through a set of API keys. You know the drill: if the first key hits a rate limit, move to the second; if the last one hits a limit, go back to the first. My first instinct was to use a slice and a modulo operator on an integer index. It works, but it feels like I'm managing too much state manually.
That's when I remembered container/ring. It's one of those packages that stays in the shadows, but for circular data structures, it's exactly what we need. Let's dive in and see how it actually behaves, because the API is a bit quirkier than a standard list.
The "Wait, where does the data go?" moment
I'll start by creating a ring of three elements to represent my API keys. I'm expecting something like a Push() method, but looking at the docs, ring.New(n) just gives me a ring of size n with empty values.
package main
import (
"fmt"
"container/ring"
)
func main() {
r := ring.New(3)
fmt.Println("Initial ring value:", r.Value)
}
Running this, I get Initial ring value: <nil>. Right. The ring is already built; it's just empty. To actually put my keys in, I have to manually traverse the ring and assign values to the Value field of each element. This feels a bit tedious at first, but it's how the structure is designed.
r := ring.New(3)
r.Value = "Key_A"
r = r.Next()
r.Value = "Key_B"
r = r.Next()
r.Value = "Key_C"
// Now I'm at Key_C. If I call Next() again...
r = r.Next()
fmt.Println("Back to:", r.Value) // Should be Key_A
That works. It's a perfect circle. But here is where I almost tripped up: the Next() method doesn't "advance" the ring object itself; it returns a pointer to the next element. If I want to keep track of my "current" key, I have to reassign my variable to the result of Next().
Moving without losing my place
What if I want to jump forward a few steps? I don't want to call Next() in a loop. There's a Move(n) method, but I noticed something strange when I first tried it. Let's see.
r := ring.New(3)
r.Value = "A"
r.Next().Value = "B"
r.Next().Next().Value = "C"
fmt.Println("Start:", r.Value) // A
r.Move(1)
fmt.Println("Move 1:", r.Value) // B
r.Move(2)
fmt.Println("Move 2:", r.Value) // A (Wait, why A?)
I paused there for a second. I moved 1 (to B), then I moved 2 more. B $\rightarrow$ C $\rightarrow$ A. So I'm back at A. The Move method is relative to the current position of the ring pointer. It doesn't reset to the "start" of the ring because, well, in a circular list, there is no start. It's just a loop.
Splicing the circle
Now, let's say one of my API keys gets revoked. I need to remove it from the rotation. I tried looking for a Remove() method, but it doesn't exist. Instead, we have Unlink(n).
Unlink(n) removes the element n steps ahead of the current one. If I'm at "A" and I want to remove "B" (which is 1 step ahead), I call r.Unlink(1).
r := ring.New(3)
r.Value = "A"
r.Next().Value = "B"
r.Next().Next().Value = "C"
r.Unlink(1) // Remove "B"
// Let's see what's left
fmt.Println(r.Value) // A
fmt.Println(r.Next().Value) // C
fmt.Println(r.Next().Next().Value) // A (Circle closed!)
The ring automatically heals itself. "A" now points directly to "C", and "C" points back to "A". It's a very clean way to handle a dynamic pool of resources without having to slice-and-dice arrays and worry about off-by-one errors when wrapping around the end of the list.
One final tip: if you ever need to check if you've come full circle while iterating, you can't just check for nil because the ring never ends. You have to store the original pointer to the starting element and compare your current pointer to it.
📋 Practical Task
Exercise: Round-Robin Load Balancer Simulator
Build a small program that simulates a load balancer distributing requests across a set of backend servers using container/ring.
- Initialize a ring with 4 server names (e.g., "Server-1", "Server-2", etc.).
- Create a function
getNextServer(r *ring.Ring) stringthat returns the current server's name and then moves the ring pointer forward by one for the next call. - Simulate 10 incoming requests by calling
getNextServerin a loop and printing which server handled each request. - Halfway through the simulation (after 5 requests), use
Unlinkto remove "Server-2" from the rotation. - Observe and print the output to verify that the requests now cycle through only the remaining 3 servers.
There are no comments for now.