Table of Contents
Implementing a Simple Queue in Go: A Step-by-Step Guide
When working with data structures in Go, understanding how to implement a basic queue is a crucial skill. A queue operates on a First In, First Out (FIFO) principle, meaning the first element added is the first one to be removed. In this post, we’ll walk through how to implement and use a simple queue in Go, covering enqueue (adding elements), dequeue (removing elements), and handling edge cases like attempting to dequeue from an empty queue.
What is a Queue?
A queue is a linear data structure that supports two primary operations:
- Enqueue: Add an item to the end of the queue.
- Dequeue: Remove an item from the front of the queue.
This kind of data structure is useful in many real-world scenarios, such as task scheduling, breadth-first search algorithms, and handling asynchronous jobs.
Go’s Built-in Slice: Perfect for a Queue
In Go, we can leverage the built-in slice data structure to create a queue. Slices provide dynamic resizing, allowing us to add or remove elements efficiently.
Golang code for Queue
Here’s the final code that implements a basic queue with enqueue, dequeue, and empty-check functionality:
package main
import (
"errors"
"fmt"
)
type queue struct {
q []int
}
func (mq *queue) enQueue(item int) {
mq.q = append(mq.q, item)
return
}
func (mq *queue) deQueue() (item int, err error) {
if mq.isEmpty() {
return -1, errors.New("Queue is empty")
}
item = mq.q[0]
mq.q = mq.q[1:]
return item, nil
}
func (mq *queue) isEmpty() bool {
return len(mq.q) == 0
}
func createQueue() (mq *queue) {
return &queue{q: []int{}}
}
func main() {
mq := createQueue()
// Enqueuing multiple items one after another
mq.enQueue(1)
fmt.Printf("After enqueue 1 isEmpty()=%t, len=%d, queue=%#v\n", mq.isEmpty(), len(mq.q), mq.q)
mq.enQueue(2)
fmt.Printf("After enqueue 2 isEmpty()=%t, len=%d, queue=%#v\n", mq.isEmpty(), len(mq.q), mq.q)
mq.enQueue(3)
fmt.Printf("After enqueue 3 isEmpty()=%t, len=%d, queue=%#v\n", mq.isEmpty(), len(mq.q), mq.q)
// Dequeue operations
var item int
var err error
item, err = mq.deQueue()
if err != nil {
fmt.Printf("Error during deQueue operation: %s\n", err)
} else {
fmt.Printf("After Dequeue: item=%d, isEmpty()=%t, len=%d, queue=%#v\n", item, mq.isEmpty(), len(mq.q), mq.q)
}
item, err = mq.deQueue()
if err != nil {
fmt.Printf("Error during dequeue: %s\n", err)
} else {
fmt.Printf("After Dequeue: item=%d, isEmpty()=%t, len=%d, queue=%#v\n", item, mq.isEmpty(), len(mq.q), mq.q)
}
item, err = mq.deQueue()
if err != nil {
fmt.Printf("Error during dequeue: %s\n", err)
} else {
fmt.Printf("After Dequeue: item=%d, isEmpty()=%t, len=%d, queue=%#v\n", item, mq.isEmpty(), len(mq.q), mq.q)
}
item, err = mq.deQueue()
if err != nil {
fmt.Printf("Error during dequeue: %s\n", err)
} else {
fmt.Printf("After Dequeue: item=%d, isEmpty()=%t, len=%d, queue=%#v\n", item, mq.isEmpty(), len(mq.q), mq.q)
}
}
Example Output
Running this code would produce the following output
After enqueue 1 isEmpty()=false, len=1, queue=[]int{1}
After enqueue 2 isEmpty()=false, len=2, queue=[]int{1, 2}
After enqueue 3 isEmpty()=false, len=3, queue=[]int{1, 2, 3}
After Dequeue: item=1, isEmpty()=false, len=2, queue=[]int{2, 3}
After Dequeue: item=2, isEmpty()=false, len=1, queue=[]int{3}
After Dequeue: item=3, isEmpty()=true, len=0, queue=[]int{}
Error during dequeue: Queue is empty
Program exited.
How It Works
1. Enqueue Operation
The enQueue
function appends a new item to the end of the queue (which is a slice in Go). The slice’s dynamic nature makes it easy to add new elements with minimal overhead.
func (mq *queue) enQueue(item int) {
mq.q = append(mq.q, item)
return
}
2. Dequeue Operation
The dequeue
function checks whether the queue is empty. If not, it removes the first item by slicing off the first element of the slice. If the queue is empty, it returns an error.
func (mq *queue) deQueue() (item int, err error) {
if mq.isEmpty() {
return -1, errors.New("Queue is empty")
}
item = mq.q[0]
mq.q = mq.q[1:]
return item, nil
}
By returning a tuple of the item, the modified queue, and an error, we handle both the success and failure of the dequeue operation gracefully.
3. isEmpty Function
The isEmpty
function simply checks if the length of the queue slice is zero, returning a boolean.
func (mq *queue) isEmpty() bool {
return len(mq.q) == 0
}
Edge Case: Empty Queue
One of the most important aspects of this implementation is handling the scenario where you attempt to dequeue from an empty queue. In the dequeue
function, this case is checked first, and an error is returned to prevent unexpected behaviour.
if mq.isEmpty() {
return -1, errors.New("Queue is empty")
}
As you can see:
- We successfully enqueue three elements.
- Each dequeue operation removes the front item from the queue.
- When the queue is empty, attempting another dequeue triggers an error.
Conclusion
By leveraging Go’s slice operations, we’ve built a simple yet functional queue that handles edge cases and manages elements efficiently. The key takeaway is the importance of managing empty queue states and returning appropriate errors to avoid unexpected crashes.
This implementation can easily be extended for more advanced use cases, such as limiting the queue size or prioritizing elements.
Whether you’re preparing for coding interviews or building a real-world system, understanding the inner workings of simple data structures like queues will give you a solid foundation for more complex algorithms and systems.
Visit https: https://codeandalgo.com for more such contents
Leave a Reply