In a previous article—Arrays, Slices, and Maps in Go: a Quick Guide to Collection Types—we explored Go’s three built-in collection types and how they work under the hood. That gave us the foundation for storing and accessing data efficiently.
But in real programs, having the data is only the start. You usually need to sort a slice, search for an element, clone or compare collections, or even reach for higher-level data structures like heaps or rings. Writing all that by hand is tedious and error-prone.
If arrays, slices, and maps are the nouns of Go’s collections, then the standard library helpers are the verbs. They let you do things with your data: sorting, searching, cloning, filtering, and transforming it in predictable and efficient ways.
Modern additions like the slices
and maps
packages (introduced in Go 1.21 and improved further in 1.25) give you type-safe, generic operations, while long-standing packages like sort
and container/heap
handle essentials such as ordering, searching, and priority queues.
In this article, we’ll walk through these helpers with examples and case studies. By the end, you’ll know how to manipulate collections idiomatically in Go, using the full power of the standard library.
What We’ll Cover:
Prerequisites
To follow along, you should:
Be comfortable with Go basics like variables, functions, and structs.
Have read the previous article on arrays, slices, and maps, or already understand how these core collection types work.
Have Go 1.25 or later installed on your system, so you can try out the modern slices and maps helpers as well as recent language improvements (at the time of writing).
You don’t need any prior knowledge of algorithms or data structures—everything we use from the standard library will be explained step by step.
Sorting & Searching Collections
Sorting and searching are among the most common operations you’ll perform on slices and arrays in Go. The standard library provides robust tools to make these tasks simple and efficient. In this section, we’ll explore the sort
and slices
packages, with examples showing how to use them in practical scenarios.
Sorting with sort
The sort
package provides functions for sorting slices of basic types (int
, string
, float64
) and a generic sort.Slice
for custom types.
Sorting a slice of integers
<span class="hljs-keyword">package</span> main
<span class="hljs-keyword">import</span> (
<span class="hljs-string">"fmt"</span>
<span class="hljs-string">"sort"</span>
)
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
scores := []<span class="hljs-keyword">int</span>{<span class="hljs-number">42</span>, <span class="hljs-number">23</span>, <span class="hljs-number">17</span>, <span class="hljs-number">99</span>, <span class="hljs-number">56</span>}
<span class="hljs-comment">// Sort in ascending order</span>
sort.Ints(scores)
fmt.Println(<span class="hljs-string">"Sorted scores:"</span>, scores)
}
Output:
Sorted scores: [17 23 42 56 99]
Sorting a slice of strings
names := []<span class="hljs-keyword">string</span>{<span class="hljs-string">"Alice"</span>, <span class="hljs-string">"Bob"</span>, <span class="hljs-string">"Charlie"</span>, <span class="hljs-string">"Diana"</span>}
sort.Strings(names)
fmt.Println(<span class="hljs-string">"Sorted names:"</span>, names)
Output:
Sorted names: [Alice Bob Charlie Diana]
Reverse sorting
To sort in reverse order, first you have to convert the slice to a type that implements sort.Interface
, such as sort.IntSlice
or sort.StringSlice
, and then use sort.Reverse
:
sort.Sort(sort.Reverse(sort.StringSlice(names)))
fmt.Println(<span class="hljs-string">"Reverse sorted names:"</span>, names)
sort.StringSlice
is a type that wraps a []string
and implements the sort.Interface
, allowing you to use it with the sort
package functions. Then, sort.Reverse
takes that and provides a reversed ordering. Finally, sort.Sort
performs the actual sorting.
Note that sort.Reverse
doesn’t actually reverse your slice first, it just tells Go to sort it in the opposite direction.
Output:
Reverse sorted names: [Diana Charlie Bob Alice]
Sorting by a custom criterion
For slices of structs or custom logic, use sort.Slice
and provide a comparison function:
<span class="hljs-keyword">type</span> Player <span class="hljs-keyword">struct</span> {
Name <span class="hljs-keyword">string</span>
Score <span class="hljs-keyword">int</span>
}
players := []Player{
{<span class="hljs-string">"Alice"</span>, <span class="hljs-number">42</span>},
{<span class="hljs-string">"Bob"</span>, <span class="hljs-number">99</span>},
{<span class="hljs-string">"Charlie"</span>, <span class="hljs-number">17</span>},
}
<span class="hljs-comment">// Sort by Score descending</span>
sort.Slice(players, <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(i, j <span class="hljs-keyword">int</span>)</span> <span class="hljs-title">bool</span></span> {
<span class="hljs-keyword">return</span> players[i].Score > players[j].Score
})
fmt.Println(<span class="hljs-string">"Players sorted by score:"</span>, players)
Output:
Players sorted by score: [{Bob 99} {Alice 42} {Charlie 17}]
Here, we pass the slice that we want to sort (players
) and a comparison function to sort.Slice
. The sorting works by calling your comparison function with two indices (i
and j
) repeatedly during the sorting process. Your function returns true if the element at index i
should come before the element at index j
in the final sorted order. In this case, players[i].Score > players[j].Score
creates a descending sort because elements with higher scores are placed before elements with lower scores.
Sometimes you may want to sort a slice but keep the original order of equal elements. For that, use sort.SliceStable
:
sort.SliceStable(players, <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(i, j <span class="hljs-keyword">int</span>)</span> <span class="hljs-title">bool</span></span> {
<span class="hljs-keyword">return</span> players[i].Score > players[j].Score
})
This ensures that if two players have the same score, their original order in the slice is preserved.
Searching with sort.Search
Once a slice is sorted, sort.Search
provides a convenient way to perform a binary search to find the index of the first element that satisfies a condition.
Binary search is a fast algorithm for finding a target value in a sorted array or list. It works by repeatedly dividing the search interval in half, compare the middle element to the target, then continue searching in the left or right half depending on the result. This approach reduces the search space quickly and finds the target in O(log n) time, making it much more efficient than linear search for large datasets. Here is a simple diagram to illustrate the binary search process:
This provides a significant performance boost over linear search, especially for large datasets. Some common use cases include:
Finding a threshold: Which player first reached a score of 50?
Inserting while maintaining order: Where should we insert a new score so the leaderboard stays sorted?
Filtering ranges: What is the first element above a certain value?
Example: Finding the first score above a threshold
scores := []<span class="hljs-keyword">int</span>{<span class="hljs-number">17</span>, <span class="hljs-number">23</span>, <span class="hljs-number">42</span>, <span class="hljs-number">56</span>, <span class="hljs-number">99</span>}
threshold := <span class="hljs-number">50</span>
<span class="hljs-comment">// Find where the first score >= threshold occurs</span>
index := sort.Search(<span class="hljs-built_in">len</span>(scores), <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(i <span class="hljs-keyword">int</span>)</span> <span class="hljs-title">bool</span></span> {
<span class="hljs-keyword">return</span> scores[i] >= threshold
})
<span class="hljs-keyword">if</span> index < <span class="hljs-built_in">len</span>(scores) {
fmt.Printf(<span class="hljs-string">"First score >= %d is at index %d with value %dn"</span>,
threshold, index, scores[index])
}
<span class="hljs-keyword">else</span> {
fmt.Printf(<span class="hljs-string">"No scores >= %d foundn"</span>, threshold)
}
Output:
First score >= 50 is at index 3 with value 56
The index is practical: it tells us which player crosses the threshold first. We can use it to highlight that player, insert new scores, or extract a sublist.
What happens here if the threshold is higher than any score in the list? sort.Search
will return the length of the slice, which is an out-of-bounds index. Always check the returned index before using it to avoid runtime panics.
Sorting and Searching with slices
The slices
package provides convenient functions that simplify common slice operations, reduce boilerplate, and work with generic types.
<span class="hljs-keyword">import</span> (
<span class="hljs-string">"fmt"</span>
<span class="hljs-string">"slices"</span>
)
scores := []<span class="hljs-keyword">int</span>{<span class="hljs-number">42</span>, <span class="hljs-number">23</span>, <span class="hljs-number">17</span>, <span class="hljs-number">99</span>, <span class="hljs-number">56</span>}
<span class="hljs-comment">// Sort in-place</span>
slices.Sort(scores)
fmt.Println(<span class="hljs-string">"Sorted scores:"</span>, scores)
<span class="hljs-comment">// Binary search</span>
index := slices.BinarySearch(scores, <span class="hljs-number">56</span>)
<span class="hljs-keyword">if</span> index >= <span class="hljs-number">0</span> {
fmt.Println(<span class="hljs-string">"Found 56 at index:"</span>, index)
} <span class="hljs-keyword">else</span> {
fmt.Println(<span class="hljs-string">"56 not found"</span>)
}
Output:
Sorted scores: [17 23 42 56 99]
Found 56 at index: 3
The slices.Sort
function sorts the slice in place, while slices.BinarySearch
performs a binary search on the sorted slice. If the element is found, it returns its index; otherwise, it returns a negative value indicating the element is not present.
Why is slices.Sort
more convenient? With the newer (Go 1.18+) slices
package, you can sort and search using a single import, and it works with any ordered type – thanks to Go’s generics. The API is also simpler for basic types, since you don’t need to provide a comparison function.
Go’s generics feature allows you to write functions that work with many types while maintaining type safety. The slices
package uses generics so you can sort or search slices of int
, float64
, string
, or any other ordered type, all with the same function call.
Sorting custom types
<span class="hljs-keyword">type</span> Player <span class="hljs-keyword">struct</span> {
Name <span class="hljs-keyword">string</span>
Score <span class="hljs-keyword">int</span>
}
players := []Player{
{<span class="hljs-string">"Alice"</span>, <span class="hljs-number">42</span>},
{<span class="hljs-string">"Bob"</span>, <span class="hljs-number">99</span>},
{<span class="hljs-string">"Charlie"</span>, <span class="hljs-number">17</span>},
}
<span class="hljs-comment">// Sort by Score descending</span>
slices.SortFunc(players, <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(a, b Player)</span> <span class="hljs-title">int</span></span> {
<span class="hljs-keyword">return</span> b.Score - a.Score
})
fmt.Println(<span class="hljs-string">"Players sorted by score:"</span>, players)
Output:
Players sorted by score: [{Bob 99} {Alice 42} {Charlie 17}]
Here, slices.SortFunc
takes a comparison function that returns a negative value if a
should come before b
, zero if they are equal, and a positive value if a
should come after b
. This allows for flexible sorting criteria.
Practical Example: Sorting a Leaderboard
<span class="hljs-keyword">type</span> Player <span class="hljs-keyword">struct</span> {
Name <span class="hljs-keyword">string</span>
Score <span class="hljs-keyword">int</span>
Date <span class="hljs-keyword">string</span> <span class="hljs-comment">// date achieved</span>
}
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
leaderboard := []Player{
{<span class="hljs-string">"Alice"</span>, <span class="hljs-number">42</span>, <span class="hljs-string">"2023-01-01"</span>},
{<span class="hljs-string">"Bob"</span>, <span class="hljs-number">99</span>, <span class="hljs-string">"2023-01-02"</span>},
{<span class="hljs-string">"Charlie"</span>, <span class="hljs-number">17</span>, <span class="hljs-string">"2023-01-03"</span>},
{<span class="hljs-string">"Diana"</span>, <span class="hljs-number">56</span>, <span class="hljs-string">"2023-01-04"</span>},
}
<span class="hljs-comment">// Sort descending by score</span>
slices.SortFunc(leaderboard, <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(a, b Player)</span> <span class="hljs-title">int</span></span> {
<span class="hljs-keyword">return</span> b.Score - a.Score
})
fmt.Println(<span class="hljs-string">"Top players:"</span>)
<span class="hljs-keyword">for</span> i, p := <span class="hljs-keyword">range</span> leaderboard {
fmt.Printf(<span class="hljs-string">"%d: %s (%d points) - %sn"</span>, i+<span class="hljs-number">1</span>, p.Name, p.Score, p.Date)
}
}
Output:
Top players:
1: Bob (99 points) - 2023-01-02
2: Diana (56 points) - 2023-01-04
3: Alice (42 points) - 2023-01-01
4: Charlie (17 points) - 2023-01-03
In this example, we define a Player
struct with a name, score, and date. We create a slice of players representing a leaderboard. Using slices.SortFunc
, we sort the players in descending order by their scores. Finally, we print out the sorted leaderboard.
Key Takeaways
Use
sort.Ints
,sort.Strings
, orsort.Slice
for classic sorting tasks.Use
sort.Search
for binary searches on sorted slices.The
slices
package simplifies sorting and searching with generic, type-safe helpers.For structs,
SortFunc
orslices.SortFunc
provides a clean way to define custom sort logic.Sorting is often the first step before applying other helpers like filtering, mapping, or priority queues.
Collection Helpers: slices
& maps
Once you know how to store, sort, and search collections, the next step is manipulating them efficiently. Go’s standard library provides modern, type-safe helpers in the slices
and maps
packages, which simplify common operations like cloning, filtering, deleting, and extracting keys or values.
Slice Helpers
The slices
package offers a variety of functions to work with slices in a more convenient way. You could see slices.Sort
and slices.BinarySearch
in the previous chapter – here are some other useful ones:
Cloning a slice
<span class="hljs-keyword">import</span> <span class="hljs-string">"slices"</span>
original := []<span class="hljs-keyword">int</span>{<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>}
<span class="hljs-built_in">copy</span> := slices.Clone(original)
<span class="hljs-built_in">copy</span>[<span class="hljs-number">0</span>] = <span class="hljs-number">99</span>
fmt.Println(<span class="hljs-string">"Original:"</span>, original)
fmt.Println(<span class="hljs-string">"Copy:"</span>, <span class="hljs-built_in">copy</span>)
Output:
Original: [1 2 3 4]
Copy: [99 2 3 4]
Remember, slices are reference types in Go. Cloning avoids accidental mutation of the original slice when passing slices around.
Checking for containment and equality
<span class="hljs-keyword">import</span> <span class="hljs-string">"slices"</span>
a := []<span class="hljs-keyword">int</span>{<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>}
b := []<span class="hljs-keyword">int</span>{<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>}
c := []<span class="hljs-keyword">int</span>{<span class="hljs-number">4</span>, <span class="hljs-number">5</span>, <span class="hljs-number">6</span>}
fmt.Println(<span class="hljs-string">"Contains:"</span>, slices.Contains(a, <span class="hljs-number">2</span>))
fmt.Println(<span class="hljs-string">"Equal:"</span>, slices.Equal(a, b))
fmt.Println(<span class="hljs-string">"Equal:"</span>, slices.Equal(a, c))
Output:
Contains: <span class="hljs-literal">true</span>
Equal: <span class="hljs-literal">true</span>
Equal: <span class="hljs-literal">false</span>
slices.Contains
checks if a slice contains a specific element.
slices.Equal
checks if two slices are equal in length and content. Note that a == b
would return false
because they are different slice headers, even though their contents are the same.
Inserting and deleting elements
names := []<span class="hljs-keyword">string</span>{<span class="hljs-string">"Alice"</span>, <span class="hljs-string">"Bob"</span>, <span class="hljs-string">"Charlie"</span>}
<span class="hljs-comment">// Insert "Diana" at index 1</span>
names = slices.Insert(names, <span class="hljs-number">1</span>, <span class="hljs-string">"Diana"</span>)
fmt.Println(names) <span class="hljs-comment">// [Alice Diana Bob Charlie]</span>
<span class="hljs-comment">// Remove element at index 2</span>
names = slices.Delete(names, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>)
fmt.Println(names) <span class="hljs-comment">// [Alice Diana Charlie]</span>
Output:
[Alice Diana Bob Charlie]
[Alice Diana Charlie]
slices.Insert
adds an element at a specified index, shifting subsequent elements to the right.
slices.Delete
removes elements in the range [start, end)
(inclusive of start
and exclusive of end
), shifting subsequent elements to the left.
Finding min, max, sorting, and using binary search
Slices of ordered types can be queried for their minimum or maximum values, sorted, or searched using binary search.
Ordered types in Go are types that support comparison operators like <
, <=
, >
, and >=
. This includes built-in types such as integers (int
, int8
, int16
, int32
, int64
), unsigned integers (uint
, uint8
, uint16
, uint32
, uint64
), floating-point numbers (float32
, float64
), and strings. These types can be compared directly using these operators.
scores := []<span class="hljs-keyword">int</span>{<span class="hljs-number">42</span>, <span class="hljs-number">23</span>, <span class="hljs-number">17</span>, <span class="hljs-number">99</span>, <span class="hljs-number">56</span>}
fmt.Println(slices.Min(scores))
fmt.Println(so are there any accuracy issues?
x(scores))
Output:
17
99
slices.Min
and slices.Max
return the minimum and maximum values in a slice of ordered types.
For sorting and binary search, we already saw slices.Sort
and slices.BinarySearch
in the previous chapter.
Practical Example: Filtering a Slice
Suppose you want to remove all players with a score below 50:
<span class="hljs-keyword">type</span> Player <span class="hljs-keyword">struct</span> {
Name <span class="hljs-keyword">string</span>
Score <span class="hljs-keyword">int</span>
Date <span class="hljs-keyword">string</span> <span class="hljs-comment">// date achieved</span>
}
players := []Player{
{<span class="hljs-string">"Alice"</span>, <span class="hljs-number">42</span>, <span class="hljs-string">"2023-01-01"</span>},
{<span class="hljs-string">"Bob"</span>, <span class="hljs-number">99</span>, <span class="hljs-string">"2023-01-02"</span>},
{<span class="hljs-string">"Charlie"</span>, <span class="hljs-number">17</span>, <span class="hljs-string">"2023-01-03"</span>},
{<span class="hljs-string">"Diana"</span>, <span class="hljs-number">56</span>, <span class="hljs-string">"2023-01-04"</span>},
}
<span class="hljs-comment">// Filter out low scores</span>
filtered := players[:<span class="hljs-number">0</span>] <span class="hljs-comment">// use the same underlying array</span>
<span class="hljs-keyword">for</span> _, p := <span class="hljs-keyword">range</span> players {
<span class="hljs-keyword">if</span> p.Score >= <span class="hljs-number">50</span> {
filtered = <span class="hljs-built_in">append</span>(filtered, p)
}
}
fmt.Println(filtered)
Output:
[{Bob 99 2023-01-02} {Diana 56 2023-01-04}]
Here, we create a new slice filtered
that has zero length but shares the same underlying array as players
. This means you can efficiently build up filtered by appending elements, without allocating a new array. Then, we iterate over players
, appending only those with a score of 50 or higher to filtered
. This approach is memory efficient since it avoids allocating a new array.
Map Helpers
The maps
package provides generic functions for working with maps: cloning, comparing, extracting keys/values, and more.
Extracting keys and values
<span class="hljs-keyword">import</span> <span class="hljs-string">"maps"</span>
scores := <span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]<span class="hljs-keyword">int</span>{
<span class="hljs-string">"Alice"</span>: <span class="hljs-number">42</span>,
<span class="hljs-string">"Bob"</span>: <span class="hljs-number">99</span>,
<span class="hljs-string">"Charlie"</span>: <span class="hljs-number">17</span>,
}
<span class="hljs-comment">// Get all keys</span>
keys := maps.Keys(scores)
fmt.Println(keys) <span class="hljs-comment">// [Alice Bob Charlie] (order not guaranteed!)</span>
<span class="hljs-comment">// Get all values</span>
values := maps.Values(scores)
fmt.Println(values) <span class="hljs-comment">// [42 99 17] (order not guaranteed!)</span>
maps.Keys
returns a slice of all keys in the map, while maps.Values
returns a slice of all values.
Important to note: the order of keys and values returned by maps.Keys
and maps.Values
is not guaranteed, as Go maps do not maintain any specific order.
Cloning and comparing maps
clone := maps.Clone(scores)
fmt.Println(clone)
equal := maps.Equal(scores, clone)
fmt.Println(equal) <span class="hljs-comment">// true</span>
Output:
map[Alice:42 Bob:99 Charlie:17]
<span class="hljs-literal">true</span>
maps.Clone
creates a shallow copy of the map, meaning that the new map has its own set of keys and values, but if any of the values are reference types (like slices, pointers, or other maps), both maps will still point to the same underlying data for those values. Only the top-level map structure is duplicated, not the contents of any referenced objects.
maps.Equal
checks if two maps have the same keys and values. It returns true
if both maps contain exactly the same set of keys, and for each key, the corresponding value is also the same in both maps. The order of keys doesn’t matter, only the content does. If any key or value differs, the maps are not considered equal.
Deleting with a condition
Say we want to remove all players with a score below 50 from a map:
<span class="hljs-keyword">for</span> name, score := <span class="hljs-keyword">range</span> scores {
<span class="hljs-keyword">if</span> score < <span class="hljs-number">50</span> {
<span class="hljs-built_in">delete</span>(scores, name)
}
}
This iterates over the map and deletes entries that don’t meet the condition. Note that modifying a map while iterating over it is safe in Go.
maps.DeleteFunc
provides a functional-style alternative to the loop above:
maps.DeleteFunc(scores, <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(name <span class="hljs-keyword">string</span>, score <span class="hljs-keyword">int</span>)</span> <span class="hljs-title">bool</span></span> {
<span class="hljs-keyword">return</span> score < <span class="hljs-number">50</span>
})
Here, maps.DeleteFunc
takes a predicate function that returns true
for keys to delete. It abstracts away the loop and makes the intent clearer.
Practical Example: Combining Slices & Maps
Imagine you have a configuration map and need to process keys in sorted order:
config := <span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]<span class="hljs-keyword">string</span>{
<span class="hljs-string">"host"</span>: <span class="hljs-string">"localhost"</span>,
<span class="hljs-string">"port"</span>: <span class="hljs-string">"8080"</span>,
<span class="hljs-string">"protocol"</span>: <span class="hljs-string">"http"</span>,
<span class="hljs-string">"timeout"</span>: <span class="hljs-string">"30s"</span>,
<span class="hljs-string">"retries"</span>: <span class="hljs-string">"3"</span>,
<span class="hljs-string">"logLevel"</span>: <span class="hljs-string">"debug"</span>,
}
<span class="hljs-comment">// Extract and sort keys</span>
keys := maps.Keys(config)
slices.Sort(keys)
<span class="hljs-keyword">for</span> _, k := <span class="hljs-keyword">range</span> keys {
fmt.Printf(<span class="hljs-string">"%s = %sn"</span>, k, config[k])
}
Output:
host = localhost
logLevel = debug
port = 8080
protocol = http
retries = 3
timeout = 30s
This is a common pattern: maps.Keys
+ slices.Sort
to process maps deterministically.
Performance Notes
Both slices
and maps
functions are optimized for performance, but keep in mind:
Most operations are O(n) since they need to iterate over the entire collection.
Cloning creates a shallow copy, which is fast but be cautious with reference types.
Maps have average O(1) access time, but worst-case O(n) if many keys collide.
Key Takeaways
The
slices
package provides type-safe, concise operations for cloning, inserting, deleting, and searching slices.The
maps
package makes it easy to extract keys/values, clone, compare, and conditionally delete map entries.Combining these helpers allows you to write clean, idiomatic, and expressive Go code without boilerplate loops.
These helpers complement sorting/searching routines and prepare slices/maps for more advanced operations, like building priority queues or filtering datasets.
“Classical” Data Structures with container/*
While slices and maps cover most day-to-day needs, sometimes you need specialized data structures that provide predictable performance or specific behaviors. Go’s standard library includes a few such structures under the container/* packages:
container/list
– a doubly linked listcontainer/heap
– a priority queue (min-heap by default)container/ring
– a circular list
These aren’t as commonly used as slices or maps, but they’re valuable when you need efficient insertions, removals, or queue-like behavior.
Doubly Linked Lists with container/list
A linked list is a sequence of elements where each element points to the next (and in the case of a doubly linked list, also to the previous).
Inserting or removing elements is O(1) once you have a reference.
Access by index is O(n) (slower than slices).
Great for queues or when you need frequent insertions in the middle.
Basic usage
<span class="hljs-keyword">import</span> (
<span class="hljs-string">"container/list"</span>
<span class="hljs-string">"fmt"</span>
)
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
l := list.New()
l.PushBack(<span class="hljs-string">"Alice"</span>)
l.PushBack(<span class="hljs-string">"Bob"</span>)
l.PushFront(<span class="hljs-string">"Eve"</span>)
<span class="hljs-keyword">for</span> e := l.Front(); e != <span class="hljs-literal">nil</span>; e = e.Next() {
fmt.Println(e.Value)
}
}
Output:
Eve
Alice
Bob
Here, we create a new doubly linked list and add elements to the front and back. We then iterate over the list from front to back, printing each element’s value.
Removing elements
element := l.Front().Next() <span class="hljs-comment">// Alice</span>
l.Remove(element) <span class="hljs-comment">// remove Alice</span>
<span class="hljs-keyword">for</span> e := l.Front(); e != <span class="hljs-literal">nil</span>; e = e.Next() {
fmt.Println(e.Value)
}
Output:
Eve
Bob
We remove the element “Alice” from the list by first getting a reference to it using l.Front().Next()
, and then calling l.Remove(element)
. After removal, we iterate over the list again to print the remaining elements.
When to use list
When you need frequent insertions/removals in the middle of a sequence.
When you don’t care about random access by index.
Otherwise, slices are usually simpler and faster.
Priority Queues with container/heap
A heap is a specialized tree-based data structure that satisfies the heap property: in a min-heap, for any given node, the value of that node is less than or equal to the values of its children. This makes heaps ideal for implementing priority queues, where you want to efficiently retrieve and remove the highest (or lowest) priority element.
Implementing a priority queue
You define your own type that implements heap.Interface
:
<span class="hljs-keyword">import</span> (
<span class="hljs-string">"container/heap"</span>
<span class="hljs-string">"fmt"</span>
)
<span class="hljs-comment">// An Item holds a value and a priority</span>
<span class="hljs-keyword">type</span> Item <span class="hljs-keyword">struct</span> {
Value <span class="hljs-keyword">string</span>
Priority <span class="hljs-keyword">int</span>
}
<span class="hljs-comment">// A PriorityQueue implements heap.Interface</span>
<span class="hljs-keyword">type</span> PriorityQueue []*Item <span class="hljs-comment">// slice of pointers to Items</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(pq PriorityQueue)</span> <span class="hljs-title">Len</span><span class="hljs-params">()</span> <span class="hljs-title">int</span></span> { <span class="hljs-keyword">return</span> <span class="hljs-built_in">len</span>(pq) }
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(pq PriorityQueue)</span> <span class="hljs-title">Less</span><span class="hljs-params">(i, j <span class="hljs-keyword">int</span>)</span> <span class="hljs-title">bool</span></span> { <span class="hljs-keyword">return</span> pq[i].Priority < pq[j].Priority }
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(pq PriorityQueue)</span> <span class="hljs-title">Swap</span><span class="hljs-params">(i, j <span class="hljs-keyword">int</span>)</span></span> { pq[i], pq[j] = pq[j], pq[i] }
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(pq *PriorityQueue)</span> <span class="hljs-title">Push</span><span class="hljs-params">(x any)</span></span> { *pq = <span class="hljs-built_in">append</span>(*pq, x.(*Item)) }
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(pq *PriorityQueue)</span> <span class="hljs-title">Pop</span><span class="hljs-params">()</span> <span class="hljs-title">any</span></span> {
old := *pq
n := <span class="hljs-built_in">len</span>(old)
item := old[n<span class="hljs-number">-1</span>]
*pq = old[<span class="hljs-number">0</span> : n<span class="hljs-number">-1</span>]
<span class="hljs-keyword">return</span> item
}
Here, we define an Item
struct with a value and priority. The PriorityQueue
type is a slice of pointers to Item
. We implement the required methods for heap.Interface
: Len
, Less
, Swap
, Push
, and Pop
.
Using the priority queue
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
pq := &PriorityQueue{}
heap.Init(pq)
heap.Push(pq, &Item{<span class="hljs-string">"write report"</span>, <span class="hljs-number">3</span>})
heap.Push(pq, &Item{<span class="hljs-string">"fix bug"</span>, <span class="hljs-number">1</span>})
heap.Push(pq, &Item{<span class="hljs-string">"reply to emails"</span>, <span class="hljs-number">2</span>})
<span class="hljs-keyword">for</span> pq.Len() > <span class="hljs-number">0</span> {
item := heap.Pop(pq).(*Item)
fmt.Println(item.Priority, item.Value)
}
}
Output:
1 fix bug
2 reply to emails
3 write report
In this example, we create a new PriorityQueue
, initialize it with heap.Init
, and push several items with different priorities. When we pop items from the heap, they come out in order of priority (lowest number first).
By default, this is a min-heap (smallest priority first). You can flip Less
to reverse the order.
Circular Buffers with container/ring
A ring is a circular list where the end connects back to the beginning. This is useful for fixed-size buffers, round-robin scheduling, or when you want to cycle through elements repeatedly.
Basic usage
<span class="hljs-keyword">import</span> (
<span class="hljs-string">"container/ring"</span>
<span class="hljs-string">"fmt"</span>
)
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
r := ring.New(<span class="hljs-number">3</span>)
<span class="hljs-keyword">for</span> i := <span class="hljs-number">0</span>; i < r.Len(); i++ {
r.Value = i + <span class="hljs-number">1</span>
r = r.Next()
}
<span class="hljs-comment">// Iterate over the ring</span>
r.Do(<span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(x any)</span></span> {
fmt.Println(x)
})
}
Output:
1
2
3
In this example, we create a new ring of size 3 and populate it with values 1, 2, and 3. We then use the Do
method to iterate over the ring and print each value.
You can also move forward or backward with r.Move(n)
to cycle through the ring.
Example use-case: Fixed-size log buffer
<span class="hljs-keyword">import</span> (
<span class="hljs-string">"container/ring"</span>
<span class="hljs-string">"fmt"</span>
)
<span class="hljs-comment">// LogBuffer is a circular buffer for log messages</span>
<span class="hljs-keyword">type</span> LogBuffer <span class="hljs-keyword">struct</span> {
ring *ring.Ring
size <span class="hljs-keyword">int</span>
}
<span class="hljs-comment">// NewLogBuffer creates a new log buffer with the given size</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">NewLogBuffer</span><span class="hljs-params">(size <span class="hljs-keyword">int</span>)</span> *<span class="hljs-title">LogBuffer</span></span> {
<span class="hljs-keyword">return</span> &LogBuffer{
ring: ring.New(size),
size: size,
}
}
<span class="hljs-comment">// Add adds a log message to the buffer</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(lb *LogBuffer)</span> <span class="hljs-title">Add</span><span class="hljs-params">(msg <span class="hljs-keyword">string</span>)</span></span> {
lb.ring.Value = msg
lb.ring = lb.ring.Next()
}
<span class="hljs-comment">// All returns all log messages in order (oldest to newest)</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(lb *LogBuffer)</span> <span class="hljs-title">All</span><span class="hljs-params">()</span> []<span class="hljs-title">string</span></span> {
logs := <span class="hljs-built_in">make</span>([]<span class="hljs-keyword">string</span>, <span class="hljs-number">0</span>, lb.size)
lb.ring.Do(<span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(x any)</span></span> {
<span class="hljs-keyword">if</span> x != <span class="hljs-literal">nil</span> {
logs = <span class="hljs-built_in">append</span>(logs, x.(<span class="hljs-keyword">string</span>))
}
})
<span class="hljs-keyword">return</span> logs
}
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
lb := NewLogBuffer(<span class="hljs-number">3</span>)
lb.Add(<span class="hljs-string">"first"</span>)
lb.Add(<span class="hljs-string">"second"</span>)
lb.Add(<span class="hljs-string">"third"</span>)
lb.Add(<span class="hljs-string">"fourth"</span>) <span class="hljs-comment">// overwrites "first"</span>
fmt.Println(lb.All())
}
Output:
[second third fourth]
Trade-offs compared to slices/maps
Memory usage: The container types may use more memory than slices/maps due to their internal structures (for example, pointers for linked lists).
Performance: Access patterns matter. For example, slices are great for sequential access, while maps excel at lookups. Choose based on your use case.
Complexity: Using these types can add complexity. Weigh the benefits against the added cognitive load.
Key Takeaways
A list gives you a doubly linked list with efficient middle insertions and deletions.
A heap provides a priority queue abstraction — powerful for scheduling and ordered retrieval.
A ring implements a circular list, perfect for round-robin scenarios.
These structures aren’t everyday tools, but they fill important niches when slices and maps aren’t enough.
Specialized Utilities
Beyond slices, maps, and the classical container types, Go provides specialized utilities that make working with collections cleaner, safer, and more expressive. Two special collections are strings and byte slices, which have their own set of helper functions. Moreover, the reflect
package offers powerful tools for inspecting and manipulating arbitrary types at runtime.
Strings and Bytes as Collections
Strings and byte slices are a special type of collection in Go. The standard library provides numerous functions for searching, splitting, joining, and transforming these sequences.
Splitting, Joining, and Searching Strings
<span class="hljs-keyword">import</span> (
<span class="hljs-string">"fmt"</span>
<span class="hljs-string">"strings"</span>
)
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
csv := <span class="hljs-string">"Alice,Bob,Charlie,Diana"</span>
names := strings.Split(csv, <span class="hljs-string">","</span>)
fmt.Println(names) <span class="hljs-comment">// [Alice Bob Charlie Diana]</span>
joined := strings.Join(names, <span class="hljs-string">" & "</span>)
fmt.Println(joined) <span class="hljs-comment">// Alice & Bob & Charlie & Diana</span>
contains := strings.Contains(csv, <span class="hljs-string">"Bob"</span>)
fmt.Println(<span class="hljs-string">"Contains Bob?"</span>, contains) <span class="hljs-comment">// true</span>
}
Output:
[Alice Bob Charlie Diana]
Alice & Bob & Charlie & Diana
Contains Bob? <span class="hljs-literal">true</span>
Here, we use strings.Split
to break a CSV string into a slice of names, strings.Join
to concatenate them with “&”, and strings.Contains
to check if “Bob” is in the original string.
Transforming Strings
strings
also provides functions for transforming strings, such as changing case, trimming whitespace, and replacing substrings:
upper := strings.ToUpper(<span class="hljs-string">"hello world"</span>)
fmt.Println(upper)
trimmed := strings.TrimSpace(<span class="hljs-string">" padded string "</span>)
fmt.Println(trimmed)
replaced := strings.ReplaceAll(<span class="hljs-string">"Alice, Bob, Charlie, Diana"</span>, <span class="hljs-string">"Bob"</span>, <span class="hljs-string">"Brian"</span>)
fmt.Println(replaced)
Output:
HELLO WORLD
<span class="hljs-string">"padded string"</span>
[Alice Brian Charlie Diana]
Important note: the strings functions return new strings, as strings in Go are immutable.
For the complete list of string functions consult the strings package documentation.
Working with Byte Slices
The bytes
package provides similar functionality for byte slices ([]byte
), which are often used for binary data or when performance matters.
data := []<span class="hljs-keyword">byte</span>(<span class="hljs-string">"hello world"</span>)
upper := bytes.ToUpper(data)
fmt.Println(<span class="hljs-keyword">string</span>(upper))
index := bytes.Index(data, []<span class="hljs-keyword">byte</span>(<span class="hljs-string">"world"</span>))
fmt.Println(<span class="hljs-string">"Index of 'world':"</span>, index)
Output:
HELLO WORLD
Index of <span class="hljs-string">'world'</span>: <span class="hljs-number">6</span>
Strings and bytes are interchangeable via []byte(str)
and string(bytes)
conversions, making it easy to apply slice-style operations to text.
For the complete list of byte slice functions consult the bytes package documentation.
Reflection-Based Utilities
The reflect
package provides powerful tools for inspecting and manipulating arbitrary types at runtime. While reflection is more advanced and should be used sparingly due to performance costs and complexity, it can be invaluable for generic programming tasks.
<span class="hljs-keyword">import</span> (
<span class="hljs-string">"fmt"</span>
<span class="hljs-string">"reflect"</span>
)
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
slice := []<span class="hljs-keyword">int</span>{<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>}
v := reflect.ValueOf(slice)
fmt.Println(<span class="hljs-string">"Length:"</span>, v.Len())
fmt.Println(<span class="hljs-string">"First element:"</span>, v.Index(<span class="hljs-number">0</span>))
}
Output:
Length: 3
First element: 1
Here, we use reflect.ValueOf
to get a reflection object representing the slice. We can then call methods like Len
and Index
to inspect its properties.
Use reflection sparingly, it’s slower and less type-safe than direct slice operations, but sometimes necessary for truly generic functions.
Reflection is a deep topic. For more details, see the reflect package documentation.
Example: generic pretty-printer for any collection
<span class="hljs-keyword">import</span> (
<span class="hljs-string">"fmt"</span>
<span class="hljs-string">"reflect"</span>
)
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">PrettyPrint</span><span class="hljs-params">(col any)</span></span> {
v := reflect.ValueOf(col)
<span class="hljs-keyword">switch</span> v.Kind() {
<span class="hljs-keyword">case</span> reflect.Slice, reflect.Array:
fmt.Println(<span class="hljs-string">"Slice/Array:"</span>)
<span class="hljs-keyword">for</span> i := <span class="hljs-number">0</span>; i < v.Len(); i++ {
fmt.Printf(<span class="hljs-string">" [%d]: %vn"</span>, i, v.Index(i))
}
<span class="hljs-keyword">case</span> reflect.Map:
fmt.Println(<span class="hljs-string">"Map:"</span>)
<span class="hljs-keyword">for</span> _, key := <span class="hljs-keyword">range</span> v.MapKeys() {
fmt.Printf(<span class="hljs-string">" %v: %vn"</span>, key, v.MapIndex(key))
}
<span class="hljs-keyword">default</span>:
fmt.Println(<span class="hljs-string">"Unsupported type:"</span>, v.Kind())
}
}
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
PrettyPrint([]<span class="hljs-keyword">int</span>{<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>})
PrettyPrint(<span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]<span class="hljs-keyword">int</span>{<span class="hljs-string">"Alice"</span>: <span class="hljs-number">42</span>, <span class="hljs-string">"Bob"</span>: <span class="hljs-number">99</span>})
}
Output:
Slice/Array:
[0]: 1
[1]: 2
[2]: 3
Map:
Alice: 42
Bob: 99
In this example, PrettyPrint
uses reflection to handle both slices/arrays and maps generically. It inspects the kind of the input and prints its contents accordingly. This is a simple demonstration of how reflection can enable generic operations on collections.
Key Takeaways
Strings and byte slices are specialized collections; the standard library provides rich tools to manipulate them.
Reflection allows dynamic inspection of slices and maps, useful for generic code.
Case Study: A Simple Job Scheduler
To bring together what we’ve learned, let’s implement a mini job scheduler – the kind of system you might see in a continuous integration (CI) pipeline or a task runner.
This task manager will:
Store tasks with a title, due date, and priority.
Allow adding and removing tasks.
Support listing tasks sorted by due date or priority.
Provide a “next task” operation using a priority queue.
Defining the Job Type
First, we’ll need a Job
struct:
<span class="hljs-keyword">import</span> (
<span class="hljs-string">"time"</span>
)
<span class="hljs-keyword">type</span> Job <span class="hljs-keyword">struct</span> {
ID <span class="hljs-keyword">int</span>
Name <span class="hljs-keyword">string</span>
ETA time.Duration <span class="hljs-comment">// estimated completion time</span>
Priority <span class="hljs-keyword">int</span> <span class="hljs-comment">// lower number = higher priority</span>
}
Storing Jobs in a Map
We’ll use a map to store tasks by their title for quick lookups and deletions:
<span class="hljs-keyword">var</span> jobs = <span class="hljs-built_in">make</span>(<span class="hljs-keyword">map</span>[<span class="hljs-keyword">int</span>]*Job)
<span class="hljs-keyword">var</span> nextID <span class="hljs-keyword">int</span> <span class="hljs-comment">// auto-incrementing ID</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">AddJob</span><span class="hljs-params">(name <span class="hljs-keyword">string</span>, eta time.Duration, priority <span class="hljs-keyword">int</span>)</span> <span class="hljs-title">int</span></span> {
id := nextID
nextID++
job := &Job{ID: id, Name: name, ETA: eta, Priority: priority}
jobs[id] = job
<span class="hljs-keyword">return</span> id
}
We could store jobs in a slice, but using a map has a few advantages:
Stable IDs: each job has a unique ID that doesn’t change, even if other jobs are deleted or reordered.
Fast lookup: retrieving, updating, or deleting a job by ID is O(1).
Extensibility: maps align naturally with database IDs or external storage if jobs are persisted.
Sparse collections: frequent deletions don’t require shifting elements as with slices.
Also note that we store pointers to Job
in the map to avoid copying the struct on each access.
Snapshot Report with Slices
To generate a report ordered by ETA, we collect all jobs into a slice and sort them:
<span class="hljs-keyword">import</span> <span class="hljs-string">"slices"</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">JobsByETA</span><span class="hljs-params">()</span> []*<span class="hljs-title">Job</span></span> {
all := <span class="hljs-built_in">make</span>([]*Job, <span class="hljs-number">0</span>, <span class="hljs-built_in">len</span>(jobs))
<span class="hljs-keyword">for</span> _, j := <span class="hljs-keyword">range</span> jobs {
all = <span class="hljs-built_in">append</span>(all, j)
}
slices.SortFunc(all, <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(a, b *Job)</span> <span class="hljs-title">int</span></span> {
<span class="hljs-keyword">return</span> <span class="hljs-keyword">int</span>(a.ETA - b.ETA)
})
<span class="hljs-keyword">return</span> all
}
Here, we create a slice of job pointers, populate it from the map, and sort it by ETA using slices.SortFunc
. This gives us a snapshot view of jobs ordered by their estimated completion time.
Priority Queue for Scheduling
Now let’s implement a priority queue to efficiently get the next job based on priority. We could sort the entire list each time (just like we did for due dates), but that would be inefficient. Instead, we’ll use a min-heap.
<span class="hljs-keyword">type</span> JobQueue []*Job
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(jq JobQueue)</span> <span class="hljs-title">Len</span><span class="hljs-params">()</span> <span class="hljs-title">int</span></span> { <span class="hljs-keyword">return</span> <span class="hljs-built_in">len</span>(jq) }
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(jq JobQueue)</span> <span class="hljs-title">Less</span><span class="hljs-params">(i, j <span class="hljs-keyword">int</span>)</span> <span class="hljs-title">bool</span></span> { <span class="hljs-keyword">return</span> jq[i].Priority < jq[j].Priority }
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(jq JobQueue)</span> <span class="hljs-title">Swap</span><span class="hljs-params">(i, j <span class="hljs-keyword">int</span>)</span></span> { jq[i], jq[j] = jq[j], jq[i] }
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(jq *JobQueue)</span> <span class="hljs-title">Push</span><span class="hljs-params">(x any)</span></span> { *jq = <span class="hljs-built_in">append</span>(*jq, x.(*Job)) }
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(jq *JobQueue)</span> <span class="hljs-title">Pop</span><span class="hljs-params">()</span> <span class="hljs-title">any</span></span> {
old := *jq
n := <span class="hljs-built_in">len</span>(old)
item := old[n<span class="hljs-number">-1</span>]
*jq = old[:n<span class="hljs-number">-1</span>]
<span class="hljs-keyword">return</span> item
}
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">NextJob</span><span class="hljs-params">()</span> *<span class="hljs-title">Job</span></span> {
<span class="hljs-keyword">if</span> jobHeap.Len() == <span class="hljs-number">0</span> {
<span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>
}
<span class="hljs-keyword">return</span> heap.Pop(jobHeap).(*Job)
}
Here, JobQueue
implements heap.Interface
, allowing us to maintain a priority queue of jobs. The NextJob
function pops the highest-priority task from the heap.
Now we need to initialize and maintain the heap and update it when jobs are added:
<span class="hljs-keyword">var</span> jobHeap = &JobQueue{}
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">AddJob</span><span class="hljs-params">(name <span class="hljs-keyword">string</span>, eta time.Duration, priority <span class="hljs-keyword">int</span>)</span> <span class="hljs-title">int</span></span> {
id := nextID
nextID++
job := &Job{ID: id, Name: name, ETA: eta, Priority: priority}
jobs[id] = job
heap.Push(jobHeap, job)
<span class="hljs-keyword">return</span> id
}
Putting It All Together
Here’s a simple main function to demonstrate the task manager:
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
AddJob(<span class="hljs-string">"Compile assets"</span>, <span class="hljs-number">5</span>*time.Second, <span class="hljs-number">2</span>)
AddJob(<span class="hljs-string">"Run tests"</span>, <span class="hljs-number">10</span>*time.Second, <span class="hljs-number">1</span>)
AddJob(<span class="hljs-string">"Deploy"</span>, <span class="hljs-number">30</span>*time.Second, <span class="hljs-number">3</span>)
fmt.Println(<span class="hljs-string">"Jobs by ETA (snapshot view):"</span>)
<span class="hljs-keyword">for</span> _, j := <span class="hljs-keyword">range</span> JobsByETA() {
fmt.Println(<span class="hljs-string">"-"</span>, j.Name, <span class="hljs-string">"(ETA"</span>, j.ETA, <span class="hljs-string">")"</span>)
}
fmt.Println(<span class="hljs-string">"nExecuting jobs by priority:"</span>)
<span class="hljs-keyword">for</span> i := <span class="hljs-number">0</span>; i < <span class="hljs-number">2</span>; i++ {
j := NextJob()
fmt.Println(<span class="hljs-string">"-"</span>, j.Name, <span class="hljs-string">"(priority"</span>, j.Priority, <span class="hljs-string">")"</span>)
}
fmt.Println(<span class="hljs-string">"nAdding urgent hotfix job..."</span>)
AddJob(<span class="hljs-string">"Hotfix"</span>, <span class="hljs-number">2</span>*time.Second, <span class="hljs-number">0</span>)
fmt.Println(<span class="hljs-string">"nContinuing execution:"</span>)
<span class="hljs-keyword">for</span> jobHeap.Len() > <span class="hljs-number">0</span> {
j := NextJob()
fmt.Println(<span class="hljs-string">"-"</span>, j.Name, <span class="hljs-string">"(priority"</span>, j.Priority, <span class="hljs-string">")"</span>)
}
}
Output:
Jobs by ETA (snapshot view):
- Compile assets (ETA 5s)
- Run tests (ETA 10s)
- Deploy (ETA 30s)
Executing <span class="hljs-built_in">jobs</span> by priority:
- Run tests (priority 1)
- Compile assets (priority 2)
Adding urgent hotfix job...
Continuing execution:
- Hotfix (priority 0)
- Deploy (priority 3)
For this simple job scheduler, we combined maps for fast lookups, slices for snapshot reports, and a priority queue for efficient scheduling. This design is flexible, efficient, and easy to extend with additional features like job status tracking or persistence. Note that this is not production-ready code – error handling, concurrency, and other concerns would need to be addressed in a real system.
For reference, here is the complete code:
<span class="hljs-keyword">package</span> main
<span class="hljs-keyword">import</span> (
<span class="hljs-string">"container/heap"</span>
<span class="hljs-string">"fmt"</span>
<span class="hljs-string">"slices"</span>
<span class="hljs-string">"time"</span>
)
<span class="hljs-keyword">type</span> Job <span class="hljs-keyword">struct</span> {
ID <span class="hljs-keyword">int</span>
Name <span class="hljs-keyword">string</span>
ETA time.Duration <span class="hljs-comment">// estimated completion time</span>
Priority <span class="hljs-keyword">int</span> <span class="hljs-comment">// lower number = higher priority</span>
}
<span class="hljs-keyword">var</span> jobs = <span class="hljs-built_in">make</span>(<span class="hljs-keyword">map</span>[<span class="hljs-keyword">int</span>]*Job)
<span class="hljs-keyword">var</span> nextID <span class="hljs-keyword">int</span> <span class="hljs-comment">// auto-incrementing ID</span>
<span class="hljs-keyword">var</span> jobHeap = &JobQueue{}
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">AddJob</span><span class="hljs-params">(name <span class="hljs-keyword">string</span>, eta time.Duration, priority <span class="hljs-keyword">int</span>)</span> <span class="hljs-title">int</span></span> {
id := nextID
nextID++
job := &Job{ID: id, Name: name, ETA: eta, Priority: priority}
jobs[id] = job
heap.Push(jobHeap, job)
<span class="hljs-keyword">return</span> id
}
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">JobsByETA</span><span class="hljs-params">()</span> []*<span class="hljs-title">Job</span></span> {
all := <span class="hljs-built_in">make</span>([]*Job, <span class="hljs-number">0</span>, <span class="hljs-built_in">len</span>(jobs))
<span class="hljs-keyword">for</span> _, j := <span class="hljs-keyword">range</span> jobs {
all = <span class="hljs-built_in">append</span>(all, j)
}
slices.SortFunc(all, <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(a, b *Job)</span> <span class="hljs-title">int</span></span> {
<span class="hljs-keyword">return</span> <span class="hljs-keyword">int</span>(a.ETA - b.ETA)
})
<span class="hljs-keyword">return</span> all
}
<span class="hljs-keyword">type</span> JobQueue []*Job
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(jq JobQueue)</span> <span class="hljs-title">Len</span><span class="hljs-params">()</span> <span class="hljs-title">int</span></span> { <span class="hljs-keyword">return</span> <span class="hljs-built_in">len</span>(jq) }
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(jq JobQueue)</span> <span class="hljs-title">Less</span><span class="hljs-params">(i, j <span class="hljs-keyword">int</span>)</span> <span class="hljs-title">bool</span></span> { <span class="hljs-keyword">return</span> jq[i].Priority < jq[j].Priority }
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(jq JobQueue)</span> <span class="hljs-title">Swap</span><span class="hljs-params">(i, j <span class="hljs-keyword">int</span>)</span></span> { jq[i], jq[j] = jq[j], jq[i] }
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(jq *JobQueue)</span> <span class="hljs-title">Push</span><span class="hljs-params">(x any)</span></span> { *jq = <span class="hljs-built_in">append</span>(*jq, x.(*Job)) }
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(jq *JobQueue)</span> <span class="hljs-title">Pop</span><span class="hljs-params">()</span> <span class="hljs-title">any</span></span> {
old := *jq
n := <span class="hljs-built_in">len</span>(old)
item := old[n<span class="hljs-number">-1</span>]
*jq = old[:n<span class="hljs-number">-1</span>]
<span class="hljs-keyword">return</span> item
}
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">NextJob</span><span class="hljs-params">()</span> *<span class="hljs-title">Job</span></span> {
<span class="hljs-keyword">if</span> jobHeap.Len() == <span class="hljs-number">0</span> {
<span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>
}
<span class="hljs-keyword">return</span> heap.Pop(jobHeap).(*Job)
}
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
AddJob(<span class="hljs-string">"Compile assets"</span>, <span class="hljs-number">5</span>*time.Second, <span class="hljs-number">2</span>)
AddJob(<span class="hljs-string">"Run tests"</span>, <span class="hljs-number">10</span>*time.Second, <span class="hljs-number">1</span>)
AddJob(<span class="hljs-string">"Deploy"</span>, <span class="hljs-number">30</span>*time.Second, <span class="hljs-number">3</span>)
fmt.Println(<span class="hljs-string">"Jobs by ETA (snapshot view):"</span>)
<span class="hljs-keyword">for</span> _, j := <span class="hljs-keyword">range</span> JobsByETA() {
fmt.Println(<span class="hljs-string">"-"</span>, j.Name, <span class="hljs-string">"(ETA"</span>, j.ETA, <span class="hljs-string">")"</span>)
}
fmt.Println(<span class="hljs-string">"nExecuting jobs by priority:"</span>)
<span class="hljs-keyword">for</span> i := <span class="hljs-number">0</span>; i < <span class="hljs-number">2</span>; i++ {
j := NextJob()
fmt.Println(<span class="hljs-string">"-"</span>, j.Name, <span class="hljs-string">"(priority"</span>, j.Priority, <span class="hljs-string">")"</span>)
}
fmt.Println(<span class="hljs-string">"nAdding urgent hotfix job..."</span>)
AddJob(<span class="hljs-string">"Hotfix"</span>, <span class="hljs-number">2</span>*time.Second, <span class="hljs-number">0</span>)
fmt.Println(<span class="hljs-string">"nContinuing execution:"</span>)
<span class="hljs-keyword">for</span> jobHeap.Len() > <span class="hljs-number">0</span> {
j := NextJob()
fmt.Println(<span class="hljs-string">"-"</span>, j.Name, <span class="hljs-string">"(priority"</span>, j.Priority, <span class="hljs-string">")"</span>)
}
}
Practice Challenge
Implement a function to remove a job by ID. Ensure it updates both the map and the priority queue correctly.
Conclusion
Go keeps things simple – the language itself only gives you three basic collection types: arrays, slices, and maps. But simplicity doesn’t mean lack of power. As we’ve seen throughout this article, the standard library layers on a rich set of helpers that let you do most of what you’ll ever need in day-to-day programming:
Sorting and searching with sort and slices.
Convenient manipulation with slices and maps.
Specialized data structures like list, heap, and ring for when slices and maps aren’t enough.
Utilities for strings, bytes, and reflection that round out the toolbox.
These tools are designed to be composable. You can sort with slices.Sort
, then filter with a loop, then store the results in a map and grab keys with maps.Keys
. Or you can build higher-level abstractions like our job scheduler by combining heaps, maps, and slices in a few dozen lines of code.
That’s the real value of Go’s approach: you rarely need to reach for third-party libraries just to handle collections. Everything here is stable, battle-tested, and consistent across the ecosystem.
Note that we’ve only scratched the surface. Each of these packages has many more functions and options to explore. The best way to learn is by doing.
The next step is practice. Take a small side project, maybe a leaderboard, a log buffer, or a task queue, and see how far you can get with just the standard library helpers. Once you’ve worked through a few real-world examples, you’ll start to think in these patterns automatically, writing clean, idiomatic Go without even reaching for external dependencies.
Practice Challenge Solution
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">RemoveJob</span><span class="hljs-params">(id <span class="hljs-keyword">int</span>)</span></span> {
<span class="hljs-keyword">if</span> job, exists := jobs[id]; exists {
<span class="hljs-built_in">delete</span>(jobs, id)
<span class="hljs-comment">// Remove from priority queue</span>
<span class="hljs-keyword">for</span> i := <span class="hljs-number">0</span>; i < jobHeap.Len(); i++ {
<span class="hljs-keyword">if</span> jobHeap[i].ID == id {
heap.Remove(jobHeap, i)
<span class="hljs-keyword">break</span>
}
}
}
}
How it works:
We first check if the job with the given ID exists in the
jobs
map.If it does, we delete it from the map.
Next, we iterate over the
jobHeap
to find the job with the matching ID.Once found, we use
heap.Remove
to remove it from the priority queue, which maintains the heap property.
Source: freeCodeCamp Programming Tutorials: Python, JavaScript, Git & MoreÂ