TypeScript generic stack and queue
Write containers that work for any element type and keep it checked.
class Stack<T> {
private items: T[] = [];
push(item: T): void {
this.items.push(item);
}
pop(): T | undefined {
return this.items.pop();
}
peek(): T | undefined {
return this.items[this.items.length - 1];
}
get size(): number {
return this.items.length;
}
}
class Queue<T> {
private items: T[] = [];
private head = 0;
enqueue(item: T): void {
this.items.push(item);
}
dequeue(): T | undefined {
if (this.head >= this.items.length) {
return undefined;
}
return this.items[this.head++];
}
get size(): number {
return this.items.length - this.head;
}
}
const history = new Stack<string>();
history.push("home");
history.push("search");
history.push("results");
console.log(history.pop(), history.peek(), history.size);
const jobs = new Queue<number>();
[10, 20, 30].forEach((job) => jobs.enqueue(job));
console.log(jobs.dequeue(), jobs.dequeue(), jobs.size);
console.log(jobs.dequeue(), jobs.dequeue());results search 2 10 20 1 30 undefined
Explanation
A generic class takes a type parameter, so Stack<number> and Stack<string> share one implementation but cannot be mixed up. The compiler knows that pop returns a T | undefined, so you must handle the empty case before using the value. The type parameter exists only at compile time.
A stack is last-in, first-out and an array supports it with push and pop in O(1). A queue is first-in, first-out. Removing from the front of an array with shift is O(n), so this queue keeps a head index and reads from it, giving O(1) dequeue. Both classes expose a size and never leak the internal array.
Explore the dry run
class Stack<T> { private items: T[] = []; push(item: T): void { this.items.push(item); } pop(): T | undefined { return this.items.pop(); } peek(): T | undefined { return this.items[this.items.length - 1]; } get size(): number { return this.items.length; }} class Queue<T> { private items: T[] = []; private head = 0; enqueue(item: T): void { this.items.push(item); } dequeue(): T | undefined { if (this.head >= this.items.length) { return undefined; } return this.items[this.head++]; } get size(): number { return this.items.length - this.head; }} const history = new Stack<string>();history.push("home");history.push("search");history.push("results");console.log(history.pop(), history.peek(), history.size); const jobs = new Queue<number>();[10, 20, 30].forEach((job) => jobs.enqueue(job));console.log(jobs.dequeue(), jobs.dequeue(), jobs.size);console.log(jobs.dequeue(), jobs.dequeue());Follow the example
Start to inspect the source, values, and output. These prepared steps explain the published sample; grouped loop steps describe multiple iterations.
View the complete dry-run table
| Step / line | What happens | State after step | Output so far |
|---|---|---|---|
| T is filled in by the user: Stack<string> holds strings only. | — | — | |
| The return type forces callers to consider the empty stack. | — | — | |
| Dequeue reads at the head and moves it forward, in O(1). | — | — | |
| The last page visited is removed first; search is now on top. | — | results search 2 | |
| The oldest jobs come out first. | — | results search 2 10 20 1 | |
| The third call returns 30 and the fourth finds the queue empty. | — | results search 2 10 20 1 30 undefined |
Cost and common mistakes
push, pop, peek, enqueue, and dequeue are all O(1).
- The queue's array keeps consumed items. A real implementation compacts it occasionally or uses a linked list.
- T | undefined makes an empty result visible, but a Stack<undefined> would be ambiguous. Avoid storing undefined.
- Generics are erased at run time. You cannot test x instanceof T.
Try it yourself
What does history.push(42) do?
Reveal answer and reasoning
It is a compile-time error, because history is a Stack<string> and 42 is a number.