Ver en Español
Data Structures in Go: Queues
Apr 16, 2023
Updated: Jun 25, 2026

Data Structures in Go: Queues

Queues are one of the most common and fundamental data structures. A queue is a linear data structure in which elements are added and removed in a specific order. It behaves like a real line, where elements are added at the "back" and removed from the "front". This means the first element added to the queue will be the first one removed. We call this behavior FIFO, which stands for first in, first out. Thanks to its simplicity and that ordering, a queue helps us solve many programming problems. For example, we can use a queue to manage a list of jobs in a computing system, so that the jobs that arrive first are processed first.

A bit of history about queues

Rather than having a single inventor, the queue is a classic abstract data type that took shape alongside computing itself. Its FIFO behavior appears naturally in queueing theory, a field of applied mathematics that the Danish engineer A. K. Erlang advanced in the early 20th century while modeling telephone call traffic. Later, with the arrival of operating systems and process scheduling, the queue became an everyday tool for ordering waiting tasks. Today it is one of the fundamental data structures, and we use it to solve a wide variety of problems.

Some characteristics

  • Queues are linear data structures in which elements are added and removed in a specific order.
  • Elements are added at the "back" of the queue and removed from the "front".
  • The insertion operation is known as "enqueue" (Enqueue) and removing an element is known as "dequeue" (Dequeue).
  • Queues process elements in FIFO order (first in, first out). This distinguishes them from stacks, which work in LIFO order (last in, first out).
  • Queues let elements be processed according to the order in which they were added.
  • Queues can be implemented statically (using arrays) or dynamically (using linked lists).
  • Queues are widely used for scheduling concurrent processes.
  • They also serve to implement a work queue system, in which jobs are added to the queue and processed in order.
  • They are considered a fundamental data structure because of their simplicity and the way elements are added and removed in order.

Understanding queues through analogies

At a supermarket, customers join the back of the line and are served from the front. This is a perfect analogy for a queue, because elements enter at the back and leave from the front.

At a bank it's the same: customers line up at the back and are served from the front, which lets them be served in the right order.

At a theme park ride, visitors line up at the back and move forward from the front. It's another good way to picture how a queue behaves.

Queues in Go

Let's go to Go! Let's write a queue. We'll start with the classic version using []interface{}, and further down we'll modernize it with generics. So the example runs from start to finish, we include the package main header and the import "fmt" we'll need.

package main

import "fmt"

// We declare a Cola (Queue) struct
type Cola struct {
    elementos []interface{}
}

// We add an Enqueue method to add elements to the back of the queue
func (c *Cola) Enqueue(elemento interface{}) {
    c.elementos = append(c.elementos, elemento)
}

// We add a Dequeue method to remove elements from the front of the queue.
// We also return a bool indicating whether the operation was valid (the queue wasn't empty).
func (c *Cola) Dequeue() (interface{}, bool) {
    if len(c.elementos) == 0 {
        return nil, false
    }
    elemento := c.elementos[0]
    c.elementos = c.elementos[1:]
    return elemento, true
}

// We add a Peek method to look at the element at the front of the queue,
// without removing it. The bool indicates whether there was an element to look at.
func (c *Cola) Peek() (interface{}, bool) {
    if len(c.elementos) == 0 {
        return nil, false
    }
    return c.elementos[0], true
}

// We add a Len method to get the number of elements in the queue
func (c *Cola) Len() int {
    return len(c.elementos)
}

// We add a Clear method to remove all elements from the queue
func (c *Cola) Clear() {
    c.elementos = []interface{}{}
}

Notice the important detail: Dequeue and Peek first check len(c.elementos) == 0. If we didn't, accessing c.elementos[0] on an empty queue would make the program panic with an index out of range error. Returning a (value, ok) pair is a very idiomatic pattern in Go and makes it clear to the caller whether the result is valid.

Some extra methods

IsEmpty tells us whether the queue is empty:

// We add an IsEmpty method to check whether the queue is empty
func (c *Cola) IsEmpty() bool {
    return len(c.elementos) == 0
}

ToString builds a readable representation of the queue for us:

// We add a ToString method to print the queue
func (c *Cola) ToString() string {
    resultado := "Cola: ["
    for _, elemento := range c.elementos {
        resultado += fmt.Sprintf("%v ", elemento)
    }
    resultado += "]"
    return resultado
}

Modernizing the queue with generics

The previous version works perfectly, but it uses []interface{}, which forces us to do type assertions every time we pull out an element. Since Go 1.18 we have generics, so we can write a type safe queue. By the way, remember that any is simply an alias for interface{}, so you can use whichever you prefer.

package main

import "fmt"

// Generic queue: T is the type of the elements it will store
type Cola[T any] struct {
    elementos []T
}

func (c *Cola[T]) Enqueue(elemento T) {
    c.elementos = append(c.elementos, elemento)
}

func (c *Cola[T]) Dequeue() (T, bool) {
    var cero T
    if len(c.elementos) == 0 {
        return cero, false
    }
    elemento := c.elementos[0]
    c.elementos = c.elementos[1:]
    return elemento, true
}

func (c *Cola[T]) Peek() (T, bool) {
    var cero T
    if len(c.elementos) == 0 {
        return cero, false
    }
    return c.elementos[0], true
}

func (c *Cola[T]) Len() int {
    return len(c.elementos)
}

func (c *Cola[T]) IsEmpty() bool {
    return len(c.elementos) == 0
}

The advantage is clear: if you declare var cola Cola[string], the compiler guarantees that only string values go in and out, and Dequeue returns a string directly, with no type assertions. When the queue is empty, we return the zero value of the type (with var cero T) together with false.

Using the structure

Let's see an example with a queue of three people, Sebas, Jill and Anastasia, and let's use each of the methods we created to show how it works. We'll use the classic version so we can also illustrate handling the ok value.

package main

import "fmt"

func main() {
    // Declare a queue
    var cola Cola

    // Add elements to the queue
    cola.Enqueue("Sebas")
    cola.Enqueue("Jill")
    cola.Enqueue("Anastasia")

    // Print the queue
    fmt.Println(cola.ToString()) // Cola: [Sebas Jill Anastasia ]

    // Get information about the queue
    // First element of the queue
    if elemento, ok := cola.Peek(); ok {
        fmt.Println(elemento) // Sebas
    }
    // Number of elements in the queue
    fmt.Println(cola.Len()) // 3

    // Remove elements from the queue
    // First element
    if elemento, ok := cola.Dequeue(); ok {
        fmt.Println(elemento) // Sebas
    }
    // New number of elements
    fmt.Println(cola.Len()) // 2
    // New queue
    fmt.Println(cola.ToString()) // Cola: [Jill Anastasia ]

    // Check whether the queue is empty
    fmt.Println(cola.IsEmpty()) // false

    // Empty the queue
    cola.Clear()
    // Check whether the queue is empty
    fmt.Println(cola.IsEmpty()) // true
}

Conclusions

Queues are one of the most common and fundamental data structures, and we use them in a wide variety of programming problems. They behave like a real line, where elements enter at the "back" and leave from the "front", following FIFO order: the first element in is the first one out. This sets them apart from stacks, which follow LIFO order. Queues are also one of the most used structures for scheduling concurrent processes. Being so simple and easy to use, they are ideal for solving problems where elements must be processed in order.

Suggested exercises

Below are a couple of exercises so you can practice the Cola (Queue) structure in Go. When you finish, send your solution as a Pull request to this repository:

Go Para Principiantes

  1. Create a Go program that simulates a supermarket queue. The program should let users enqueue themselves, view the element at the front of the queue and dequeue themselves. It should also report how many users are in the queue.
  2. Create a Go program that implements a work queue system to process files. The system should let you enqueue files for processing, view the file at the front of the queue and dequeue files once they've been processed. It should also report how many files are in the queue.

3-point summary

  1. A queue is a linear data structure that processes elements in FIFO order: the first in is the first out, unlike a stack, which is LIFO.
  2. The basic operations are Enqueue (add to the back) and Dequeue (remove from the front); it's a good idea to guard Dequeue and Peek so they don't fail on an empty queue, returning a (value, ok) pair.
  3. In modern Go it's best to implement it with generics (type Cola[T any]) to get type safety and avoid type assertions.

That's all. I hope this post is useful to you and that you can apply it to a project you have in mind. Leave me a comment if it helped, if you want to add an opinion, or if you have any questions, and remember, if you liked it, you can also share it using the social links below. Good luck!

Sebastian Gomez

Sebastian Gomez

Creador de contenido principalmente acerca de tecnología.

Leave a Reply

0 Comments

Advertisements

Related Posts

Categorias