How to Implement Queue in Golang [Step by Step Guide]

How to Implement Queue in Golang [Step by Step Guide]

In this tutorial, we will learn about “how to implement Queue in Golang using step by step guide“. Queue is one of the most commonly used data structure. To understand Queue in a lay man term, relate is with a real life example. For example, 5 man standing out a shop in a Queue will collect the order in sequential order i.e one who is standing in the front will be given the stuff first followed by rest. This is exactly how the Queue data structure works in programming world. We will dive deep into the concept and its implementation in Golang in the upcoming sections of this tutorial.

 

What is Queue?

A Queue is a linear data structure that follows the First in, First out principle which means that element that is added first is the one that gets removed first. We can understand the Queue by taking an example. Suppose there is a Queue made outside a grocery shop which has 10 person standing in the Queue. The shopkeeper will provide the stuffs to the person who joined the Queue first followed by second person and so on. We will understand the concept with more clarity when we discuss about different operations that can be done on a Queue in next section.

 

Queue Operations

Enqueue Operation

In Queue, Enqueue() is a method using which we add an element to the end (rear) of the Queue. When we add an element, the length of the Queue gets extended by 1. Therefore, if you add 10 elements to an empty Queue, the length will get extended to 10. I have taken an example to explain the enqueue() operation as shown below.

Consider we have a list of numbers (8, 4, 5, 9, 6, 1, 12) which must be enqueued. In this process, at index 0, the first element(8) from the list will get added. Next element from the list(4) will get added in the next index i.e 1 and so on.  The last element(12) from the list will get added in the last index i.e at index 6

 

 

Dequeue Operation

In Queue, Dequeue() is a method using which we can remove the element from the front (head) of the queue. In this method, the element at the front is removed and the next element in line becomes the new front. This operation follows the FIFO principle which ensures that the oldest element is dequeued first.

In the same example as we discussed above, we can perform the dequeue() operation. When we invoke the dequeue() method, the first element which gets removed from the queue will be the first element which was added in the queue. Similarly, the dequeue will happen for other elements in the queue. The last element that will be dequeued from the queue is the last element that was added in the queue. It is demonstrated in the below diagram.

 

 

Peek or Front Operation

In Queue, Peek() is a method which is used to retrieve the element at the front without removing it. It allows you to examine the element at the front of the queue without altering the queue itself. This operation becomes useful when you want to inspect the next element to be dequeued.

 

IsEmpty Operation

In Queue, isEmpty() is a method which checks if the queue is empty. It will return a Boolean value indicating whether the queue is devoid of elements. It helps in avoiding errors that might occur if attempting to dequeue from an empty queue.

 

IsFull Operation

In Queue, isFull() is a method which checks if the Queue has reached its maximum capacity. This method is relevant for bounded queues where a fixed size is imposed. This operation ensures that the queue does not overflow which prevents the addition of more elements.

 

How to Implement Queue in Golang [Step by Step Guide]

Also Read: How to Implement Stack in Golang [Step by Step Guide]

So far we have gained the understanding about the Queue data structure and all the supported operations. In this section, we will write a Golang code which will create an empty Queue. We will then write logics to create all the discussed methods to Queue. Save below code in a file say queue.go and go through the code by yourself once.

package main

import "fmt"

type queue struct {
    data []int
}

//Enqueue in the queue
func (q *queue) Enqueue(val int) {
    q.data = append(q.data, val)
}

//Dequeue from the queue
func (q *queue) Dequeue() {
    q.data = q.data[1:]
}

//Peek operation
func (q queue) Peek() {

    front := q.data[0]
    fmt.Println("Element present at the front of given Queue: ", front)
}

//isEmpty operation
func (q queue) isEmpty() {

    if q.data == nil {
        fmt.Println("Given Queue is empty")
    }
}

//Print elements of queue
func (q queue) PrintQueue() {
    fmt.Println("Queue Data: ", q.data)
}

func main() {

    q1 := queue{}
    addNum := []int{8, 4, 5, 9, 6, 1, 12}

    q1.isEmpty()

    for i := range addNum {
        q1.Enqueue(addNum[i])
    }
    q1.PrintQueue()

    q1.Peek()

    q1.Dequeue()
    q1.PrintQueue()

    q1.Dequeue()
    q1.PrintQueue()

    q1.Enqueue(19)
    q1.PrintQueue()
}

 

Code Explanation
1.  I have created a struct called “queue” which represents a dynamic queue.
2.  I have created separate methods for each Queue operation. These methods are, Enqueue(), Dequeue(), Peek(), isEmpty(), isFull() and PrintQueue(). Let us understand each function in details as given below.

 

Enqueue()
In this method, an int value is passed as function argument. We will append this value at the end of the Queue using the built-in method called append as shown below.
func (q *queue) Enqueue(val int) {
    q.data = append(q.data, val)
}

 

Dequeue()
In this method, one element at a time will be removed from the Queue. We have to make sure that every time the Dequeue() fucntion is called, element from the top is removed. To do so, we will always return the Queue from index 1 onwards. This means, every time this method is called, element present at index 0 (top element) will be removed.
func (q *queue) Dequeue() {
    q.data = q.data[1:]
}

 

Peek()
In this method, we have to return an element which is present at the front (top) of the Queue whenever called. I am printing the element present in the front instead of returning the element as function return but you can do otherwise as well.
func (q queue) Peek() {

    front := q.data[0]
    fmt.Println("Element present at the front of given Queue: ", front)
}

 

isEmpty()
In this method, we have defined a logic to check if the Queue is empty or not by comparing the Queue with the nil value. If the condition is true, we can either return true as function argument or print a custom message saying “Given Queue is empty“. I am doing the later operation as shown below.
func (q queue) isEmpty() {

    if q.data == nil {
        fmt.Println("Given Queue is empty")
    }
}

 

PrintQueue()
This is a supporting function which will display the Queue elements at any given time. It becomes helpful to check the currently present Queue elements after performing each operation to validate the implementation.
func (q queue) PrintQueue() {
    fmt.Println("Queue Data: ", q.data)
}

 

Now that we understand the functionality of each function and how they are implemented, let us now execute the code and check the output it returns. Please check the main function to know the sequence in which Queue methods are called.

 

OUTPUT
> go run .\queue.go
Given Queue is empty
Queue Data: [8 4 5 9 6 1 12]
Element present at the front of given Queue: 8
Queue Data: [4 5 9 6 1 12]
Queue Data: [5 9 6 1 12]
Queue Data: [5 9 6 1 12 19]

 

How to Implement isFull() Method?

As we know, the isFull() method of Queue works only with bounded Queue which means Queue with fixed size or capacity. In the previous example which we used to implement all the Queue methods except the isFull() method, we used slice of dynamic size. To implement the isFull() method, we have to create a slice with fixed capacity. Let us modify the same code which we used earlier and add the logic to implement isFull() method as shown below.

package main

import "fmt"

type queue struct {

    data     []int
    capacity int
}

// Enqueue in the queue
func (q *queue) Enqueue(val int) {

    if q.isFull() {
        fmt.Println("Queue is full. Cannot enqueue more elements.")
        return
    }

    q.data = append(q.data, val)
    fmt.Printf("Enqueued: %d\n", val)
}

// isFull operation
func (q *queue) isFull() bool {

    return len(q.data) == q.capacity
}

// Print elements of queue
func (q queue) PrintQueue() {
    fmt.Println("Queue Data:", q.data)
}

func main() {

    // Set the capacity of the queue
    q1 := queue{capacity: 5}

    addNum := [7]int{8, 4, 5, 9, 6, 1, 12}

    for i := range addNum {
        q1.Enqueue(addNum[i])
    }

    q1.PrintQueue()
}
OUTPUT
> go run .\queue.go
Enqueued: 8
Enqueued: 4
Enqueued: 5
Enqueued: 9
Enqueued: 6
Queue is full. Cannot enqueue more elements.
Queue is full. Cannot enqueue more elements.
Queue Data: [8 4 5 9 6]

 

NOTE:

I have highlighted the changes in above code with GREEN color.

 

Summary

We have learnt about the Queue data structure in depth. We also learnt about the important Queue operations i.e enqueue() and dequeue() along with other supported operations like peek(), isEmpty() and isFull().

Leave a Comment