logo hsb.horse
← Back to snippets index

Snippets

TypeScript yocto-queue Implementation

TypeScript reimplementation of lightweight queue yocto-queue. Provides basic operations: enqueue, dequeue, iterator.

Published: Updated:

TypeScript reimplementation of lightweight queue yocto-queue.

When a queue starts as a plain array, it often ends up with repeated shift() calls. That is acceptable in a tiny script, but it gets awkward inside concurrency limiters and task schedulers. yocto-queue is the minimal shape for that case, and it is easy to copy into a project when I want the behavior without another dependency.

Original implementation

/**
* @see {@link https://github.com/sindresorhus/yocto-queue/blob/main/index.js}
*/
interface QNode<T> {
value: T;
next: QNode<T> | undefined;
}
interface Queue<T> {
enqueue(value: T): void;
dequeue(): T | undefined;
peek(): T | undefined;
clear(): void;
readonly size: number;
[Symbol.iterator](): Generator<T, void, unknown>;
drain(): Generator<T | undefined, void, unknown>;
}
function createNode<T>(value: T): QNode<T> {
return { value, next: undefined };
}
function createQueue<T>(): Queue<T> {
let head: QNode<T> | undefined;
let tail: QNode<T> | undefined;
let size: number;
const clear = (): void => {
head = tail = undefined;
size = 0;
}
clear();
const enqueue = (value: T): void => {
const node = createNode(value);
if (head) {
tail && (tail.next = node);
tail = node;
} else {
head = node;
tail = node;
}
size++;
}
const dequeue = (): T | undefined => {
const current = head;
if (!current) return;
head = head?.next;
size--;
return current.value;
}
const peek = (): T | undefined => {
return head?.value;
}
function* iterator(): Generator<T, void, unknown> {
let current = head;
while (current) {
yield current.value;
current = current.next;
}
}
function* drain(): Generator<T | undefined, void, unknown> {
while (head) {
yield dequeue();
}
}
return {
enqueue,
dequeue,
peek,
clear,
get size() {
return size;
},
[Symbol.iterator]: iterator,
drain
}
}

When to Use It

This fits Promise concurrency control, breadth-first traversal, and worker dispatch queues where items are appended at the tail and consumed from the head. The same behavior is possible with arrays, but a dedicated queue makes the caller intent easier to read.

Why This Shape Works

Because the implementation keeps separate head and tail pointers, enqueue() and dequeue() do not depend on array reindexing. The size getter also makes pending work visible from the outside, while iterator and drain() separate read-only traversal from destructive consumption.

Notes

This is intentionally a minimal FIFO queue. It does not try to handle priorities or random access. If all I need is a small waiting line, this is enough. If I also need cancellation, priorities, or deduplication, a different structure is a better fit.