<style> .reveal-up, .reveal-left, .reveal-right, .reveal-stagger > * { opacity: 1 !important; transform: none !important; } </style>
  1. Learn
  2. DSA
  3. Linear data structures
All lessons in this course

Lesson 11 of 50 14 minBeginner

Queues, circular buffers and deques

First in, first out: why a List makes a slow queue, how a circular buffer gives O(1) at both ends, using Queue<T>, and when you need a deque.

A queue is the fair version of a stack: items leave in the order they arrived. You enqueue at the back and dequeue from the front: FIFO, first in, first out. Print jobs, customer support tickets, messages between services, and the steps of breadth-first search all use queues.

OperationMeaningCost
Enqueue(x)Add x at the backO(1) amortised
Dequeue()Remove and return the front itemO(1)
Peek()Look at the front itemO(1)
CountHow many itemsO(1)

The slow way: a List

The obvious idea is list.Add(x) to enqueue and list.RemoveAt(0) to dequeue. But removing index 0 shifts every other item one slot left, so each dequeue is O(n), and processing n items costs O(n²).

The fast way: a circular buffer

Instead of moving the items, move the front marker. Keep two indexes: head (where the next item will be taken) and tail (where the next item will be written). Both only move forward, and when they reach the end of the array they wrap around to index 0 using % capacity. The array behaves like a ring.

Interactive

A queue in a circular array

Enqueue writes at tail, Dequeue reads at head. Both move forward and wrap from slot 7 back to slot 0. Nothing is ever shifted.

01234567head = 0tail = 0count = 0
The same array, laid flat
T·[0]
·[1]
·[2]
·[3]
·[4]
·[5]
·[6]
·[7]
Queue order (front → back)
empty
Empty queue: head and tail both start at 0.
RingQueue.cs
public class RingQueue<T>{    private T[] _items;    private int _head;      // index of the front item    private int _tail;      // index where the next item goes    private int _count;     public RingQueue(int capacity = 4) => _items = new T[capacity];    public int Count => _count;     public void Enqueue(T item)    {        if (_count == _items.Length) Grow();        _items[_tail] = item;        _tail = (_tail + 1) % _items.Length;     // wrap around        _count++;    }     public T Dequeue()    {        if (_count == 0) throw new InvalidOperationException("Queue is empty");        T item = _items[_head];        _items[_head] = default!;        _head = (_head + 1) % _items.Length;     // wrap around        _count--;        return item;    }     private void Grow()    {        var bigger = new T[_items.Length * 2];        for (int i = 0; i < _count; i++)         // copy in queue order, front first            bigger[i] = _items[(_head + i) % _items.Length];        _items = bigger;        _head = 0;        _tail = _count;    }}

Queue<T> in .NET

.NET’s Queue<T> is a growable circular buffer, just like the one above.

Jobs.cs
var jobs = new Queue<string>();jobs.Enqueue("Export IFC");jobs.Enqueue("Generate drawings");jobs.Enqueue("Send report"); Console.WriteLine($"Next up: {jobs.Peek()}"); while (jobs.TryDequeue(out var job))    Console.WriteLine($"Running {job} ({jobs.Count} left)");
Output
Next up: Export IFC
Running Export IFC (2 left)
Running Generate drawings (1 left)
Running Send report (0 left)

Deque: a double-ended queue

A deque (say “deck”) allows adding and removing at both ends in O(1). It can act as a stack, a queue, or both at once, and it is the key structure of the “sliding window maximum” technique you will meet later. A circular buffer supports it naturally: moving head backwards is (head - 1 + capacity) % capacity.

.NET has no class called Deque<T>. Use LinkedList<T> (AddFirst, AddLast, RemoveFirst, RemoveLast are all O(1)), or write a circular-buffer deque when performance matters.

Other queues you will meet

TypeOrderWhere
Queue<T>First in, first outSingle-threaded work lists, BFS
PriorityQueue<TElement, TPriority>Smallest priority firstSchedulers, Dijkstra (see Heaps lesson)
ConcurrentQueue<T>FIFO, thread-safeSeveral threads adding and taking work
Channel<T>FIFO, async producer/consumerBackground processing pipelines
Quick check

Enqueue 1, 2, 3, then Dequeue twice, then Enqueue 4. What is the front item?

Quick check

A circular buffer has capacity 8 and tail = 7. After one Enqueue, what is tail?

Practice

Write a RecentCounter class. Ping(int t) records a request at time t milliseconds (times only increase) and returns how many requests happened in the last 3000 ms, including this one (the range [t − 3000, t]). Pings at 1, 100, 3001, 3002 should return 1, 2, 3, 3.

Show a hint

Keep the times in a Queue<int>. On each ping, enqueue t, then dequeue from the front while the oldest time is less than t − 3000.

Show the solution
Solution.cs
var counter = new RecentCounter();foreach (int t in new[] { 1, 100, 3001, 3002 })    Console.Write(counter.Ping(t) + " "); public class RecentCounter{    private readonly Queue<int> _times = new();     public int Ping(int t)    {        _times.Enqueue(t);        while (_times.Peek() < t - 3000)            _times.Dequeue();           // too old, never needed again        return _times.Count;    }}
Output
1 2 3 3 

Remember

  • A queue is FIFO: enqueue at the back, dequeue from the front, both O(1).
  • A List with RemoveAt(0) is an O(n) dequeue; a circular buffer moves indexes instead of items.
  • Head and tail wrap around with % capacity; when growing, copy items in queue order.
  • A deque allows O(1) at both ends; in .NET use LinkedList<T> or a circular buffer.
0 of 50 lessons completed