Introduction
Data structures provide ways to manage, organize and access data efficiently. Choosing the right structure for a given task can have a big impact on your program’s performance and memory usage.
In this comprehensive guide, you’ll learn about the most important built-in and custom data structures in Go:
- Arrays
- Slices
- Maps
- Structs
- Linked Lists
- Stacks
- Queues
- Trees
- Graphs
With concrete examples and code snippets, you’ll understand which structures to use and when. Let’s dive in!
Built-In Data Structures
Go provides robust implementations of several foundational data structures. These should be your go-to tools before building custom structures.
Arrays
Arrays allow storing a fixed-size sequential collection of elements of the same type.
var myArray [3]int
myArray[0] = 1
myArray[1] = 2
myArray[2] = 3
The size is set at creation and cannot be changed. Useful when you know exactly how many elements you need and want efficiency.
Slices
Slices are dynamic arrays that handle appending and extending automatically.
mySlice := []int{1,2,3} // Create new slice
mySlice = append(mySlice, 4) // Append new value
Slices abstract away managing the underlying array and pointers. Use them for most array use cases for convenience.
Maps
Maps store key-value pairs efficiently. Keys must be hashable types like strings, integers, or arrays.
ages := map[string]int{}
ages["John"] = 30
ages["Mary"] = 25
Maps are useful for lookups, counts, and any key-value scenario. Go’s maps have optimizations like hash tables for speed.
Structs
Structs are custom data types that group related fields together.
type Car struct {
Make string
Model string
Year int
}
ford := Car{Make: "Ford", Model: "F150", Year: 2022}
Use structs to represent common concepts in your domain with meaningful field names.
Custom Data Structures
Along with built-in types, we can build custom data structures like linked lists, trees, stacks, etc. These give more control for specific needs.
Linked Lists
Linked lists contain nodes that link to other nodes, forming a chain. Useful when fast insertion/removal at any position is needed.
type Node struct {
Value int
Next *Node
}
func NewList() *Node {
return &Node{}
}
func (l *Node) Insert(v int) {
// Insert new node
}
Nodes link to each other through the Next
pointer rather than residing in contiguous memory like arrays.
Stacks
Stacks operate in LIFO (Last In First Out) order. Items are added and removed from the top of the stack.
type Stack struct {
items []int
}
func (s *Stack) Push(i int) {
s.items = append(s.items, i)
}
func (s *Stack) Pop() int {
lastIndex := len(s.items) - 1
toRemove := s.items[lastIndex]
s.items = s.items[:lastIndex]
return toRemove
}
Stacks are useful for DFS graph algorithms, parsing, and undo/redo functionality.
Queues
Queues follow FIFO (First In First Out) ordering. Items are added to the back and removed from the front.
type Queue struct {
items []int
}
func (q *Queue) Enqueue(i int) {
q.items = append(q.items, i)
}
func (q *Queue) Dequeue() int {
toRemove := q.items[0]
q.items = q.items[1:]
return toRemove
}
Queues work well for breadth-first search, job scheduling, and rate limiting tasks.
Trees
Trees contain nodes that link to multiple child nodes in a hierarchy. Useful for nested, hierarchical data.
type TreeNode struct {
Value int
LeftChild *TreeNode
RightChild *TreeNode
}
func (n *TreeNode) Insert(v int) {
// Add new node as child
}
Trees shine for spatial partitioning, quick searches, sorting, and recursive algorithms.
Graphs
Graphs contain nodes and edges between nodes. Represent connections and relationships.
type Graph struct {
Nodes []*Node
Edges map[Node][]Node
}
type Node struct {
Value int
}
func (g *Graph) AddEdge(n1, n2 *Node) {
// Add edge between nodes
}
Model real-world networks like social networks, locations, recommendations using graphs. Useful for pathfinding and more.
When to Use Each Structure
Choosing the right data structure depends on your specific needs:
- Arrays – When you need a fixed collection of elements.
- Slices – Default flexible array, use extensively.
- Maps – Associating values with keys for quick lookup.
- Structs – Grouping related properties.
- Linked Lists – Fast inserts/removals from anywhere.
- Stacks – LIFO ordering.
- Queues – FIFO ordering.
- Trees – Hierarchical relationships.
- Graphs – Model connections & networks.
Consider space and time tradeoffs. Measure performance with actual usage data.
Tips for Data Structures in Go
Here are some tips for working with data structures effectively in Go:
- Use built-in structures first before custom ones.
- Store methods with the structures via receiver functions.
- Comment unexported fields so intentions are clear.
- Encapsulate implementation by exporting only necessary methods.
- Add utility methods for common operations like searching, sorting etc.
- Use interfaces to make structures interchangeable.
- Use generics for reusable, flexible structures.
Choosing the optimal data structure will come with experience. Don’t optimize prematurely but be ready to refactor when performance demands it.
Conclusion
Data structures provide powerful ways to organize and manage data in your Go programs. Take advantage of built-in options like slices and maps, as well as custom implementations like linked lists and graphs.
Consider the characteristics and tradeoffs of each structure. Optimize based on your specific access patterns and needs. Proper data design will improve your program’s efficiency, scalability, and maintainability.
Frequently Asked Questions
Here are some common questions about data structures in Go:
What’s the difference between arrays and slices in Go?
Arrays have a fixed size set at creation. Slices are dynamic arrays that handle resizing and additional capacity for you. Slices are more convenient in most cases.
When would I use a map vs. a struct in Go?
Use maps when you need fast lookup by a key. Use structs when you want to group related data under different fields. Structs are good for domain modeling.
How do Go maps work internally?
Go maps utilize hash tables for efficient key-value lookup. The key is hashed to find the associated bucket location containing the value.
What are some use cases for linked lists in Go?
Linked lists shine when you need fast insertion/removal from the middle of a list. They allow efficient reordering without moving whole blocks of memory.
How do stacks and queues differ?
Stacks follow LIFO (Last In First Out) ordering. Queues follow FIFO (First In First Out). Stacks add/remove from the end, queues add/remove from opposite ends.
What tree algorithms are useful in Go?
Binary search trees allow efficient insertion, searching and removal in O(log n) time. Heaps are useful priority queues. Tries provide fast prefix-based lookups.
When would I use a graph data structure in Go?
Graphs shine for modeling connections and relationships. Social networks, retail product linkages, network topologies, etc. Pathfinding algorithms work well on graph structures.
Should I always use built-in data structures first?
Generally yes. Built-in structures like slices and maps are well optimized and convenient. Only build custom structures like linked lists when needed.
What are some tips for picking the right data structure?
Here are some additional tips for picking the right data structure in Go:
- Consider the primary operations needed (lookups, searches, sorting etc), amount of data, order of access, and insertion/removal speed.
- Pay attention to memory usage – some structures like linked lists take up more memory than tightly packed arrays.
- Measure performance with realistic data loads and usage patterns. Optimizing prematurely can be a pitfall.
- Think about long term maintainability. Code relying on custom complex structures may be harder to modify later.
- Look at expected data growth – a structure good for small amounts of data may not scale well.
- Don’t optimize until you actually have a demonstrated performance bottleneck.
- Use profiling tools like pprof to identify actual hotspots needing optimization.
- Consider tradeoffs like speed vs memory usage. There is no “perfect” data structure.
- Reuse existing and proven data structure implementations when possible.
- Focus on clean interfaces and encapsulation so internals can change easily.
- Comment and document choices to help future developers.
The right choice often comes down to benchmarks and testing with real-world data. Let practical usage guide your data structure design.