A queue in golang is normally a FIFO structure: enqueue at the rear, dequeue (and optionally peek) at the front. You can implement a golang queue with a slice, with container/list for pointer-based nodes, or with a buffered channel when goroutines coordinate work—each is a valid golang queue implementation with different trade-offs. This page compares those approaches for queues in golang, fixes common typos (peek versus peak), and notes when a golang fifo queue backed only by a slice retains memory. For typed helpers, see generics in Go; for channel semantics, see channels.
Checked with Go 1.24 on 64-bit Linux (Fedora and Ubuntu family).
golang queue implementation patterns
golang fifo queue with a slice
Enqueue with append; dequeue by reslicing with q = q[1:] after you read q[0]. Always check len(q) before accessing the front—otherwise you panic on an empty queue.
package main
import "fmt"
func main() {
queue := make([]int, 0)
queue = append(queue, 1, 5, 8)
fmt.Println("enqueue:", queue)
if len(queue) == 0 {
return
}
fmt.Println("peek:", queue[0])
queue = queue[1:]
fmt.Println("dequeue:", queue)
if len(queue) == 0 {
fmt.Println("empty")
} else {
fmt.Println("not empty, len=", len(queue))
}
}You should see enqueue: [1 5 8], peek: 1, then dequeue: [5 8], and a non-empty message.
The underlying array can keep capacity for removed slots; for very long-lived queues with many dequeues, occasionally copy surviving elements into a fresh slice (q = append([]T(nil), q...)) or switch to container/list if profiling shows retained memory matters.
Type-safe queue golang with generics
Generics let one pair of helpers serve many element types ([T any] is enough for a basic FIFO):
package main
import "fmt"
func enqueue[T any](q []T, v T) []T { return append(q, v) }
func dequeue[T any](q []T) ([]T, T, bool) {
if len(q) == 0 {
var zero T
return q, zero, false
}
return q[1:], q[0], true
}
func peek[T any](q []T) (T, bool) {
if len(q) == 0 {
var zero T
return zero, false
}
return q[0], true
}
func main() {
q := []string{}
q = enqueue(q, "first")
q = enqueue(q, "second")
q = enqueue(q, "third")
fmt.Println("queue:", q)
if v, ok := peek(q); ok {
fmt.Println("peek:", v)
}
if nq, front, ok := dequeue(q); ok {
fmt.Println("popped:", front, "rest:", nq)
}
}You should see the three strings enqueued, peek: first, then popped: first with the remaining slice.
queue golang with container/list
list.List stores values in doubly-linked elements, so removing the front does not retain the whole underlying array like a slice can.
package main
import (
"container/list"
"fmt"
)
func main() {
q := list.New()
q.PushBack(36)
q.PushBack(49)
q.PushBack(42)
if f := q.Front(); f != nil {
q.Remove(f)
}
if f := q.Front(); f != nil {
fmt.Println("peek:", f.Value)
}
fmt.Println("isEmpty:", q.Len() == 0)
}After one dequeue from [36,49,42], the front value should print as 49.
go queue with a buffered channel
A buffered chan is a bounded FIFO between goroutines: sends enqueue, receives dequeue. len on the channel reports queued elements (not capacity). There is still no built-in peek without receiving.
package main
import "fmt"
func main() {
q := make(chan int, 200)
q <- 5
q <- 4
q <- 6
fmt.Println("dequeue:", <-q)
fmt.Println("dequeue:", <-q)
if len(q) == 0 {
fmt.Println("queue empty")
} else {
fmt.Println("remaining:", len(q))
}
}You should see 5 and 4 printed, then remaining: 1 because one value remains.
For concurrent producers and consumers, prefer sending and receiving from goroutines instead of assuming len is a synchronization primitive—see channels.
Mutex-wrapped slice for a concurrent queue in go
When multiple goroutines share one slice queue, guard append and reslicing with a sync.Mutex or use a channel instead. A minimal pattern is: lock, append or slice, unlock; never expose the inner slice outside the mutex.
Summary
Queue golang code usually means a FIFO: golang fifo queue with slices is the quickest path; golang queues that live a long time and dequeue heavily may need memory awareness or container/list. A go queue built from a buffered channel fits producer–consumer pipelines but cannot peek the head without extra structure. Golang queue implementation choices come down to threading model, need for peek, and whether you want generics for reusable helpers. Search noise like go-queue often points at third-party broker libraries—in-process work rarely needs a special module name if the patterns above fit.

