Queues
September 8, 2026 · 6 min read · Angelos Ioakeimidis
I wrote about stacks last time, and queues are the natural next stop - same idea of “you can only touch one end of the list”, just the opposite rule about which end. Once stacks clicked for me, queues took about five minutes, because most of what I already knew just flips.
A stack flips the rule to LIFO - last in, first out - and shows up in undo buttons and the call stack. Read Stack →
Here’s my take on what a queue is, the two moves you need, and a few places you’ve probably used one without noticing.
What a queue even is
A queue is a list where new items go in at the back, and items come out from the front. You can’t cut the line and you can’t pull someone out of the middle.
The technical term is FIFO - First In, First Out. Whatever got added first is the first thing that leaves.
Think of an actual queue at a shop. You join at the back. Whoever’s been standing there longest gets served first. Nobody’s letting you skip to the front just because you showed up in a hurry.
The two operations you actually need
Same shape as a stack, just different names and different ends:
- enqueue - add something to the back
- dequeue - remove whatever’s at the front and hand it back to you
Some implementations add a peek too, so you can check what’s next without removing it yet.
Here’s a queue in plain JavaScript. I’m using an object with a front and back pointer instead of a plain array, because shifting an array with .shift() is O(n) - it has to renumber every remaining item. This version keeps both operations O(1):
class Queue {
#items = {}
#front = 0
#back = 0
enqueue(item) {
this.#items[this.#back] = item
this.#back++
}
dequeue() {
if (this.isEmpty()) return undefined
const item = this.#items[this.#front]
delete this.#item[this.#front]
this.#front++
return item
}
peek() {
return this.#items[this.#front]
}
isEmpty() {
return this.#front === this.#back
}
get size() {
return this.#back - this.#front
}
}The #front and #back counters just keep track of which index is next up and which index is next free, so I never have to reshuffle the whole object every time something leaves.
Where you’ve already used one: task runners
This is the example that made queues click for me. Say you’re building something that has to process jobs one at a time - resizing uploaded images, sending emails, whatever. You don’t want two jobs stepping on each other, so you queue them up and work through them in order.
const jobQueue = new Queue()
let isProcessing = false
function addJob(job) {
jobQueue.enqueue(job)
processNext()
}
async function processNext() {
if (isProcessing || jobQueue.isEmpty()) return
isProcessing = true
const job = jobQueue.dequeue()
await job()
isProcessing = false
processNext()
}Every job that comes in gets enqueued, and processNext just keeps working through the front of the line until it’s empty. Whoever submitted first gets handled first - nobody’s job jumps the queue because it happened to be smaller or arrived from a faster connection.
Your printer is doing the same thing
This one’s almost too literal, but it’s the clearest real-world FIFO there is. Send three documents to a shared office printer, and they come out in the order you sent them, not the order you’d prefer. Document one goes in first, prints first, and leaves the queue. Document two moves up to the front and does the same.
If you’ve ever watched a print job get “stuck” and hold up everything behind it, that’s the FIFO rule at work too - nothing behind it can jump ahead, it just has to wait its turn.
The one that’s running right now, without you asking for it
JavaScript’s own event loop leans on a queue too. When a setTimeout fires, or a promise resolves, or an event handler is ready to run, it doesn’t execute immediately - it gets placed into a queue (the task queue, or for promises specifically, the microtask queue), and the event loop works through that queue in order once the current code finishes running.
console.log('first')
setTimeout(() => console.log('third'), 0)
Promise.resolve().then(() => console.log('second'))
console.log('fourth, but logs before the timeout')That logs first, then fourth, but logs before the timeout, then second, then third - even though the setTimeout was written before the promise. Microtasks (promises) get their own queue that drains before the regular task queue gets touched, but within each queue, it’s still FIFO: whatever got queued first runs first.
Here’s a little demonstration to visualise it:
I’ve got a whole separate post on this if you want the full breakdown of tasks vs microtasks.
Where else queues show up: breadth-first search
If you’ve ever written a breadth-first search over a tree or graph, you’ve used a queue whether you called it that or not. BFS visits nodes level by level - everything one step away, then everything two steps away, and so on.
function bfs(startNode) {
const visited = new Set([startNode])
const queue = new Queue()
queue.enqueue(startNode)
while (!queue.isEmpty()) {
const node = queue.dequeue()
console.log(node.value)
for (const neighbor of node.neighbors) {
if (!visited.has(neighbor)) {
visited.add(neighbor)
queue.enqueue(neighbor)
}
}
}
}Every neighbor gets enqueued as it’s discovered, so the nodes closest to the start always get dequeued and processed before anything further away. Swap that queue for a stack and you’d get depth-first search instead - same code, completely different traversal order, just because of which end you’re pulling from.
Stack vs Queue, quickly
Same comparison from the stack post, just from the other side. A stack is LIFO, last in first out - reach for it when you want to undo the most recent thing or unwind something in reverse order. A queue is FIFO, first in first out - reach for it when order of arrival actually matters, like a task runner, a print queue, or level-by-level traversal.
Missed the stack post?
The call stack is a stack in disguise - LIFO order, pushing a frame on every call and popping one on every return.
A queue only allows two moves: enqueue (add to the back) and dequeue (remove from the front) - FIFO, first in first out.
Job runners and task processors use a queue so work gets handled in the order it arrived.
Print queues are a real-world FIFO: the first document sent is the first one printed.
Breadth-first search uses a queue to visit nodes level by level, closest first.
Stack vs queue: a stack reverses order (undo, recursion), a queue preserves arrival order (task runners, BFS).
Liked this article? Share it with a friend on Twitter or support me to take on more ambitious projects to write about. Have a question, feedback or simply wish to contact me privately? Shoot me a DM and I'll do my best to get back to you.
Have a wonderful day.

Angelos Ioakeimidis
Data Science & AI Student. Writing about software engineering, design, and AI.