Skip to content

17: Stacks & Queues

17: Stacks & Queues

  • Master the Stack (LIFO) data structure for last-in-first-out operations
  • Implement the Queue (FIFO) data structure for first-in-first-out operations
  • Build practical applications: expression evaluator, undo/redo, task scheduler
  • Understand when to use stacks vs. queues in real-world scenarios
  • Implement both structures using arrays and linked lists

Estimated Time: ~45 minutes

Before starting this chapter, you should have:

  • ✓ Understanding of arrays and their operations
  • ✓ Familiarity with linked lists from Chapter 16
  • ✓ Completion of Chapter 15 (Arrays & Dynamic Arrays)
  • ✓ Basic knowledge of recursion concepts

Stacks and queues are fundamental linear data structures with restricted access patterns. While they may seem simple, they’re incredibly powerful and used everywhere - from function call management to task scheduling, browser history to print spoolers. In this chapter, we’ll implement both structures and build practical applications that demonstrate their real-world utility.

By the end of this chapter, you will have created:

  • Complete stack implementation using arrays and linked lists
  • Queue implementations with multiple strategies (array, circular, linked list)
  • Practical applications: expression evaluator, undo/redo system, task scheduler
  • Advanced variants: double-ended queue (deque) and priority queue
  • Performance benchmarking tools to compare implementations
  • Framework integration examples for Laravel and Symfony
  • Security-hardened versions with overflow protection and input validation

Let’s visualize how a stack works:

Initial state:
Stack: [empty]
After push(10):
TOP → [10]
After push(20):
TOP → [20]
[10]
After push(30):
TOP → [30]
[20]
[10]
After push(40):
TOP → [40]
[30]
[20]
[10]
Current state:
TOP → [40]
[30]
[20]
[10]
pop() returns 40:
TOP → [30]
[20]
[10]
pop() returns 30:
TOP → [20]
[10]
Current state:
TOP → [20]
[10]
peek() returns 20 (no change to stack):
TOP → [20]
[10]

A stack is like a stack of plates: you add and remove from the top only.

Operations:

  • Push: Add element to top
  • Pop: Remove and return top element
  • Peek: View top element without removing
  • isEmpty: Check if stack is empty
stack.php
<?php
declare(strict_types=1);
class Stack
{
private array $items = [];
// Push element - O(1)
public function push(mixed $value): void
{
$this->items[] = $value;
}
// Pop element - O(1)
public function pop(): mixed
{
if ($this->isEmpty()) {
throw new UnderflowException("Stack is empty");
}
return array_pop($this->items);
}
// Peek at top - O(1)
public function peek(): mixed
{
if ($this->isEmpty()) {
throw new UnderflowException("Stack is empty");
}
return end($this->items);
}
// Check if empty - O(1)
public function isEmpty(): bool
{
return empty($this->items);
}
// Get size - O(1)
public function size(): int
{
return count($this->items);
}
// Clear stack - O(1)
public function clear(): void
{
$this->items = [];
}
// Display stack
public function display(): void
{
echo "Stack (top to bottom): ";
echo implode(' -> ', array_reverse($this->items)) . "\n";
}
}
// Usage
$stack = new Stack();
$stack->push(10);
$stack->push(20);
$stack->push(30);
$stack->display(); // 30 -> 20 -> 10
echo $stack->pop() . "\n"; // 30
echo $stack->peek() . "\n"; // 20
linked-stack.php
<?php
declare(strict_types=1);
class Node
{
public function __construct(
public mixed $data,
public ?Node $next = null
) {}
}
class LinkedStack
{
private ?Node $top = null;
private int $size = 0;
public function push(mixed $value): void
{
$newNode = new Node($value);
$newNode->next = $this->top;
$this->top = $newNode;
$this->size++;
}
public function pop(): mixed
{
if ($this->isEmpty()) {
throw new UnderflowException("Stack is empty");
}
$value = $this->top->data;
$this->top = $this->top->next;
$this->size--;
return $value;
}
public function peek(): mixed
{
if ($this->isEmpty()) {
throw new UnderflowException("Stack is empty");
}
return $this->top->data;
}
public function isEmpty(): bool
{
return $this->top === null;
}
public function size(): int
{
return $this->size;
}
}
balanced-parentheses.php
<?php
declare(strict_types=1);
function isBalanced(string $expression): bool
{
$stack = new Stack();
$pairs = [
')' => '(',
'}' => '{',
']' => '['
];
for ($i = 0; $i < strlen($expression); $i++) {
$char = $expression[$i];
// Opening bracket - push to stack
if (in_array($char, ['(', '{', '['])) {
$stack->push($char);
}
// Closing bracket - check match
elseif (isset($pairs[$char])) {
if ($stack->isEmpty() || $stack->pop() !== $pairs[$char]) {
return false;
}
}
}
return $stack->isEmpty();
}
echo isBalanced("({[]})") ? "Balanced" : "Not balanced"; // Balanced
echo isBalanced("({[}])") ? "Balanced" : "Not balanced"; // Not balanced
echo isBalanced("((())") ? "Balanced" : "Not balanced"; // Not balanced
postfix-evaluator.php
<?php
declare(strict_types=1);
function evaluatePostfix(string $expression): float
{
$stack = new Stack();
$tokens = explode(' ', $expression);
foreach ($tokens as $token) {
if (is_numeric($token)) {
$stack->push((float)$token);
} else {
$b = $stack->pop();
$a = $stack->pop();
switch ($token) {
case '+':
$stack->push($a + $b);
break;
case '-':
$stack->push($a - $b);
break;
case '*':
$stack->push($a * $b);
break;
case '/':
$stack->push($a / $b);
break;
}
}
}
return $stack->pop();
}
echo evaluatePostfix("3 4 + 2 *"); // (3+4)*2 = 14
echo evaluatePostfix("5 1 2 + 4 * + 3 -"); // 5+((1+2)*4)-3 = 14
infix-to-postfix.php
<?php
declare(strict_types=1);
function infixToPostfix(string $infix): string
{
$stack = new Stack();
$output = [];
$precedence = ['+' => 1, '-' => 1, '*' => 2, '/' => 2, '^' => 3];
$tokens = str_split(str_replace(' ', '', $infix));
foreach ($tokens as $token) {
if (is_numeric($token)) {
$output[] = $token;
} elseif ($token === '(') {
$stack->push($token);
} elseif ($token === ')') {
while (!$stack->isEmpty() && $stack->peek() !== '(') {
$output[] = $stack->pop();
}
$stack->pop(); // Remove '('
} elseif (isset($precedence[$token])) {
while (!$stack->isEmpty() &&
$stack->peek() !== '(' &&
$precedence[$stack->peek()] >= $precedence[$token]) {
$output[] = $stack->pop();
}
$stack->push($token);
}
}
while (!$stack->isEmpty()) {
$output[] = $stack->pop();
}
return implode(' ', $output);
}
echo infixToPostfix("3+4*2/(1-5)"); // 3 4 2 * 1 5 - / +
undo-redo.php
<?php
declare(strict_types=1);
class TextEditor
{
private string $text = '';
private Stack $undoStack;
private Stack $redoStack;
public function __construct()
{
$this->undoStack = new Stack();
$this->redoStack = new Stack();
}
public function type(string $newText): void
{
$this->undoStack->push($this->text);
$this->text .= $newText;
$this->redoStack->clear(); // Clear redo on new action
}
public function undo(): void
{
if (!$this->undoStack->isEmpty()) {
$this->redoStack->push($this->text);
$this->text = $this->undoStack->pop();
}
}
public function redo(): void
{
if (!$this->redoStack->isEmpty()) {
$this->undoStack->push($this->text);
$this->text = $this->redoStack->pop();
}
}
public function getText(): string
{
return $this->text;
}
}
$editor = new TextEditor();
$editor->type("Hello");
$editor->type(" World");
echo $editor->getText() . "\n"; // Hello World
$editor->undo();
echo $editor->getText() . "\n"; // Hello
$editor->redo();
echo $editor->getText() . "\n"; // Hello World
call-stack-simulation.php
<?php
declare(strict_types=1);
function factorial(int $n, Stack $callStack = null): int
{
if ($callStack === null) {
$callStack = new Stack();
}
$callStack->push("factorial($n)");
echo "Call: factorial($n)\n";
if ($n <= 1) {
echo "Return: 1\n";
$callStack->pop();
return 1;
}
$result = $n * factorial($n - 1, $callStack);
echo "Return: $result from factorial($n)\n";
$callStack->pop();
return $result;
}
factorial(5);

Let’s visualize how a queue works:

Initial state:
Queue: [empty]
After enqueue(10):
FRONT → [10] ← REAR
After enqueue(20):
FRONT → [10] [20] ← REAR
After enqueue(30):
FRONT → [10] [20] [30] ← REAR
After enqueue(40):
FRONT → [10] [20] [30] [40] ← REAR
Current state:
FRONT → [10] [20] [30] [40] ← REAR
dequeue() returns 10:
FRONT → [20] [30] [40] ← REAR
dequeue() returns 20:
FRONT → [30] [40] ← REAR
Start:
FRONT → [30] [40] ← REAR
enqueue(50):
FRONT → [30] [40] [50] ← REAR
dequeue() returns 30:
FRONT → [40] [50] ← REAR
enqueue(60):
FRONT → [40] [50] [60] ← REAR

A queue is like a line at a store: first person in line is served first.

Operations:

  • Enqueue: Add element to rear
  • Dequeue: Remove and return front element
  • Peek: View front element without removing
  • isEmpty: Check if queue is empty
queue.php
<?php
declare(strict_types=1);
class Queue
{
private array $items = [];
// Enqueue - O(1)
public function enqueue(mixed $value): void
{
$this->items[] = $value;
}
// Dequeue - O(n) due to array_shift
public function dequeue(): mixed
{
if ($this->isEmpty()) {
throw new UnderflowException("Queue is empty");
}
return array_shift($this->items);
}
// Peek front - O(1)
public function peek(): mixed
{
if ($this->isEmpty()) {
throw new UnderflowException("Queue is empty");
}
return $this->items[0];
}
public function isEmpty(): bool
{
return empty($this->items);
}
public function size(): int
{
return count($this->items);
}
public function display(): void
{
echo "Queue (front to rear): ";
echo implode(' <- ', $this->items) . "\n";
}
}
$queue = new Queue();
$queue->enqueue(10);
$queue->enqueue(20);
$queue->enqueue(30);
$queue->display(); // 10 <- 20 <- 30
echo $queue->dequeue() . "\n"; // 10
echo $queue->peek() . "\n"; // 20
circular-queue.php
<?php
declare(strict_types=1);
class CircularQueue
{
private array $items;
private int $front = 0;
private int $rear = -1;
private int $size = 0;
private int $capacity;
public function __construct(int $capacity)
{
$this->capacity = $capacity;
$this->items = array_fill(0, $capacity, null);
}
public function enqueue(mixed $value): void
{
if ($this->isFull()) {
throw new OverflowException("Queue is full");
}
$this->rear = ($this->rear + 1) % $this->capacity;
$this->items[$this->rear] = $value;
$this->size++;
}
public function dequeue(): mixed
{
if ($this->isEmpty()) {
throw new UnderflowException("Queue is empty");
}
$value = $this->items[$this->front];
$this->items[$this->front] = null;
$this->front = ($this->front + 1) % $this->capacity;
$this->size--;
return $value;
}
public function peek(): mixed
{
if ($this->isEmpty()) {
throw new UnderflowException("Queue is empty");
}
return $this->items[$this->front];
}
public function isEmpty(): bool
{
return $this->size === 0;
}
public function isFull(): bool
{
return $this->size === $this->capacity;
}
public function size(): int
{
return $this->size;
}
}
linked-queue.php
<?php
declare(strict_types=1);
class LinkedQueue
{
private ?Node $front = null;
private ?Node $rear = null;
private int $size = 0;
public function enqueue(mixed $value): void
{
$newNode = new Node($value);
if ($this->isEmpty()) {
$this->front = $this->rear = $newNode;
} else {
$this->rear->next = $newNode;
$this->rear = $newNode;
}
$this->size++;
}
public function dequeue(): mixed
{
if ($this->isEmpty()) {
throw new UnderflowException("Queue is empty");
}
$value = $this->front->data;
$this->front = $this->front->next;
if ($this->front === null) {
$this->rear = null;
}
$this->size--;
return $value;
}
public function peek(): mixed
{
if ($this->isEmpty()) {
throw new UnderflowException("Queue is empty");
}
return $this->front->data;
}
public function isEmpty(): bool
{
return $this->front === null;
}
public function size(): int
{
return $this->size;
}
}
bfs.php
<?php
declare(strict_types=1);
function bfs(array $graph, int $start): array
{
$visited = [];
$queue = new Queue();
$queue->enqueue($start);
$visited[$start] = true;
while (!$queue->isEmpty()) {
$node = $queue->dequeue();
echo "$node ";
foreach ($graph[$node] as $neighbor) {
if (!isset($visited[$neighbor])) {
$queue->enqueue($neighbor);
$visited[$neighbor] = true;
}
}
}
return array_keys($visited);
}
$graph = [
0 => [1, 2],
1 => [0, 3, 4],
2 => [0, 4],
3 => [1],
4 => [1, 2]
];
bfs($graph, 0); // 0 1 2 3 4
task-scheduler.php
<?php
declare(strict_types=1);
class Task
{
public function __construct(
public string $name,
public int $priority,
public callable $action
) {}
}
class TaskScheduler
{
private Queue $queue;
public function __construct()
{
$this->queue = new Queue();
}
public function addTask(Task $task): void
{
$this->queue->enqueue($task);
echo "Scheduled: {$task->name}\n";
}
public function processNext(): void
{
if ($this->queue->isEmpty()) {
echo "No tasks to process\n";
return;
}
$task = $this->queue->dequeue();
echo "Processing: {$task->name}\n";
($task->action)();
}
public function processAll(): void
{
while (!$this->queue->isEmpty()) {
$this->processNext();
}
}
}
$scheduler = new TaskScheduler();
$scheduler->addTask(new Task("Email", 1, fn() => print("Sending email...\n")));
$scheduler->addTask(new Task("Backup", 2, fn() => print("Running backup...\n")));
$scheduler->processAll();
print-queue.php
<?php
declare(strict_types=1);
class PrintJob
{
public function __construct(
public string $document,
public int $pages
) {}
}
class PrintQueue
{
private Queue $queue;
public function __construct()
{
$this->queue = new Queue();
}
public function addJob(PrintJob $job): void
{
$this->queue->enqueue($job);
echo "Added to print queue: {$job->document} ({$job->pages} pages)\n";
}
public function printNext(): void
{
if ($this->queue->isEmpty()) {
echo "Print queue is empty\n";
return;
}
$job = $this->queue->dequeue();
echo "Printing: {$job->document}";
for ($i = 0; $i < $job->pages; $i++) {
echo ".";
usleep(100000); // Simulate printing
}
echo " Done!\n";
}
public function getQueueSize(): int
{
return $this->queue->size();
}
}

A deque allows insertion and deletion at both ends.

deque.php
<?php
declare(strict_types=1);
class Deque
{
private array $items = [];
public function pushFront(mixed $value): void
{
array_unshift($this->items, $value);
}
public function pushBack(mixed $value): void
{
$this->items[] = $value;
}
public function popFront(): mixed
{
if ($this->isEmpty()) {
throw new UnderflowException("Deque is empty");
}
return array_shift($this->items);
}
public function popBack(): mixed
{
if ($this->isEmpty()) {
throw new UnderflowException("Deque is empty");
}
return array_pop($this->items);
}
public function peekFront(): mixed
{
if ($this->isEmpty()) {
throw new UnderflowException("Deque is empty");
}
return $this->items[0];
}
public function peekBack(): mixed
{
if ($this->isEmpty()) {
throw new UnderflowException("Deque is empty");
}
return end($this->items);
}
public function isEmpty(): bool
{
return empty($this->items);
}
public function size(): int
{
return count($this->items);
}
}
// Can be used as stack or queue!
$deque = new Deque();
$deque->pushBack(1);
$deque->pushFront(2);
$deque->pushBack(3);
// Deque: 2 <- 1 <- 3

Elements are dequeued based on priority, not insertion order.

priority-queue.php
<?php
declare(strict_types=1);
class PriorityQueue
{
private array $items = [];
public function enqueue(mixed $value, int $priority): void
{
$this->items[] = ['value' => $value, 'priority' => $priority];
// Sort by priority (higher priority first)
usort($this->items, fn($a, $b) => $b['priority'] <=> $a['priority']);
}
public function dequeue(): mixed
{
if ($this->isEmpty()) {
throw new UnderflowException("Priority queue is empty");
}
$item = array_shift($this->items);
return $item['value'];
}
public function peek(): mixed
{
if ($this->isEmpty()) {
throw new UnderflowException("Priority queue is empty");
}
return $this->items[0]['value'];
}
public function isEmpty(): bool
{
return empty($this->items);
}
}
$pq = new PriorityQueue();
$pq->enqueue("Low priority task", 1);
$pq->enqueue("High priority task", 10);
$pq->enqueue("Medium priority task", 5);
echo $pq->dequeue(); // High priority task
echo $pq->dequeue(); // Medium priority task

PHP’s Standard PHP Library provides optimized implementations of stacks and queues.

spl-stack-example.php
<?php
declare(strict_types=1);
$stack = new SplStack();
// Push elements
$stack->push(10);
$stack->push(20);
$stack->push(30);
// Access elements
echo $stack->top() . "\n"; // 30 (peek at top)
echo $stack->pop() . "\n"; // 30 (remove and return)
echo $stack->count() . "\n"; // 2
// Iterate (LIFO order)
foreach ($stack as $item) {
echo "$item "; // 20 10
}
// Check if empty
if (!$stack->isEmpty()) {
echo "Stack has elements\n";
}
spl-queue-example.php
<?php
declare(strict_types=1);
$queue = new SplQueue();
// Enqueue elements
$queue->enqueue(10);
$queue->enqueue(20);
$queue->enqueue(30);
// Dequeue
echo $queue->dequeue() . "\n"; // 10 (FIFO)
// Array access
echo $queue[0] . "\n"; // 20 (front of queue)
// Iterate (FIFO order)
foreach ($queue as $item) {
echo "$item "; // 20 30
}
spl-priority-queue-example.php
<?php
declare(strict_types=1);
$pq = new SplPriorityQueue();
// Insert with priority
$pq->insert('Low priority', 1);
$pq->insert('High priority', 10);
$pq->insert('Medium priority', 5);
// Extract in priority order
echo $pq->extract() . "\n"; // High priority
echo $pq->extract() . "\n"; // Medium priority
echo $pq->extract() . "\n"; // Low priority
// Set extraction mode
$pq->setExtractFlags(SplPriorityQueue::EXTR_BOTH);
$pq->insert('Task A', 5);
$result = $pq->extract();
print_r($result); // ['data' => 'Task A', 'priority' => 5]

SplDoublyLinkedList (Base for Stack/Queue)

Section titled “SplDoublyLinkedList (Base for Stack/Queue)”
spl-doubly-linked-list-example.php
<?php
declare(strict_types=1);
$list = new SplDoublyLinkedList();
// Can be used as stack
$list->setIteratorMode(SplDoublyLinkedList::IT_MODE_LIFO);
$list->push(1);
$list->push(2);
$list->push(3);
foreach ($list as $item) {
echo "$item "; // 3 2 1 (LIFO)
}
// Or as queue
$list->setIteratorMode(SplDoublyLinkedList::IT_MODE_FIFO);
foreach ($list as $item) {
echo "$item "; // 1 2 3 (FIFO)
}

Let’s measure real-world performance of different implementations:

benchmark-stacks-queues.php
<?php
declare(strict_types=1);
function benchmarkStackImplementations(): void
{
$operations = 10000;
// Array-based Stack
$start = microtime(true);
$stack = new Stack();
for ($i = 0; $i < $operations; $i++) {
$stack->push($i);
}
for ($i = 0; $i < $operations; $i++) {
$stack->pop();
}
$arrayTime = microtime(true) - $start;
// SPL Stack
$start = microtime(true);
$splStack = new SplStack();
for ($i = 0; $i < $operations; $i++) {
$splStack->push($i);
}
for ($i = 0; $i < $operations; $i++) {
$splStack->pop();
}
$splTime = microtime(true) - $start;
// Linked List Stack
$start = microtime(true);
$linkedStack = new LinkedStack();
for ($i = 0; $i < $operations; $i++) {
$linkedStack->push($i);
}
for ($i = 0; $i < $operations; $i++) {
$linkedStack->pop();
}
$linkedTime = microtime(true) - $start;
echo "=== Stack Performance ({$operations} push + {$operations} pop) ===\n";
echo sprintf("Array-based: %.4f seconds\n", $arrayTime);
echo sprintf("SPL Stack: %.4f seconds (%.2fx)\n", $splTime, $arrayTime / $splTime);
echo sprintf("Linked List: %.4f seconds (%.2fx)\n", $linkedTime, $arrayTime / $linkedTime);
}
function benchmarkQueueImplementations(): void
{
$operations = 10000;
// Array-based Queue (with array_shift - slow)
$start = microtime(true);
$queue = new Queue();
for ($i = 0; $i < $operations; $i++) {
$queue->enqueue($i);
}
for ($i = 0; $i < $operations; $i++) {
$queue->dequeue();
}
$arrayTime = microtime(true) - $start;
// SPL Queue
$start = microtime(true);
$splQueue = new SplQueue();
for ($i = 0; $i < $operations; $i++) {
$splQueue->enqueue($i);
}
for ($i = 0; $i < $operations; $i++) {
$splQueue->dequeue();
}
$splTime = microtime(true) - $start;
// Linked Queue
$start = microtime(true);
$linkedQueue = new LinkedQueue();
for ($i = 0; $i < $operations; $i++) {
$linkedQueue->enqueue($i);
}
for ($i = 0; $i < $operations; $i++) {
$linkedQueue->dequeue();
}
$linkedTime = microtime(true) - $start;
echo "\n=== Queue Performance ({$operations} enqueue + {$operations} dequeue) ===\n";
echo sprintf("Array-based: %.4f seconds\n", $arrayTime);
echo sprintf("SPL Queue: %.4f seconds (%.2fx faster)\n", $splTime, $arrayTime / $splTime);
echo sprintf("Linked List: %.4f seconds (%.2fx faster)\n", $linkedTime, $arrayTime / $linkedTime);
}
// Sample output:
// === Stack Performance (10000 push + 10000 pop) ===
// Array-based: 0.0089 seconds
// SPL Stack: 0.0034 seconds (2.62x)
// Linked List: 0.0156 seconds (0.57x)
//
// === Queue Performance (10000 enqueue + 10000 dequeue) ===
// Array-based: 1.2345 seconds
// SPL Queue: 0.0045 seconds (274.33x faster)
// Linked List: 0.0198 seconds (62.35x faster)
memory-analysis.php
<?php
declare(strict_types=1);
function analyzeMemoryUsage(): void
{
$elements = 1000;
// Stack memory
$before = memory_get_usage();
$stack = new Stack();
for ($i = 0; $i < $elements; $i++) {
$stack->push($i);
}
$stackMemory = memory_get_usage() - $before;
// SPL Stack memory
$before = memory_get_usage();
$splStack = new SplStack();
for ($i = 0; $i < $elements; $i++) {
$splStack->push($i);
}
$splMemory = memory_get_usage() - $before;
// Queue memory
$before = memory_get_usage();
$queue = new Queue();
for ($i = 0; $i < $elements; $i++) {
$queue->enqueue($i);
}
$queueMemory = memory_get_usage() - $before;
echo "Memory usage for {$elements} elements:\n";
echo sprintf("Stack (array): %s KB\n", number_format($stackMemory / 1024, 2));
echo sprintf("SPL Stack: %s KB\n", number_format($splMemory / 1024, 2));
echo sprintf("Queue (array): %s KB\n", number_format($queueMemory / 1024, 2));
}
// Sample output:
// Memory usage for 1000 elements:
// Stack (array): 32.45 KB
// SPL Stack: 98.23 KB
// Queue (array): 32.67 KB

Performance Insights:

  • SPL implementations are fastest (written in C)
  • Array-based queues with array_shift are very slow (O(n) dequeue)
  • Linked list queues are much faster for dequeue operations
  • For stacks, arrays are competitive since array_pop is O(1)
  • SPL uses more memory but provides better performance

Robust implementations must handle edge cases:

robust-stack.php
<?php
declare(strict_types=1);
class RobustStack
{
private array $items = [];
private int $maxSize;
public function __construct(int $maxSize = PHP_INT_MAX)
{
if ($maxSize <= 0) {
throw new InvalidArgumentException("Max size must be positive");
}
$this->maxSize = $maxSize;
}
public function push(mixed $value): void
{
// Edge case: Stack overflow
if ($this->size() >= $this->maxSize) {
throw new OverflowException(
"Stack overflow: cannot push to full stack (max: {$this->maxSize})"
);
}
// Edge case: Prevent null values if desired
if ($value === null) {
throw new InvalidArgumentException("Cannot push null value");
}
$this->items[] = $value;
}
public function pop(): mixed
{
// Edge case: Stack underflow
if ($this->isEmpty()) {
throw new UnderflowException("Stack underflow: cannot pop from empty stack");
}
return array_pop($this->items);
}
public function peek(): mixed
{
// Edge case: Peek on empty stack
if ($this->isEmpty()) {
throw new UnderflowException("Cannot peek: stack is empty");
}
return end($this->items);
}
public function isEmpty(): bool
{
return empty($this->items);
}
public function isFull(): bool
{
return $this->size() >= $this->maxSize;
}
public function size(): int
{
return count($this->items);
}
public function clear(): void
{
$this->items = [];
}
}
class RobustQueue
{
private array $items = [];
private int $maxSize;
public function __construct(int $maxSize = PHP_INT_MAX)
{
if ($maxSize <= 0) {
throw new InvalidArgumentException("Max size must be positive");
}
$this->maxSize = $maxSize;
}
public function enqueue(mixed $value): void
{
// Edge case: Queue overflow
if ($this->size() >= $this->maxSize) {
throw new OverflowException("Queue is full");
}
$this->items[] = $value;
}
public function dequeue(): mixed
{
// Edge case: Queue underflow
if ($this->isEmpty()) {
throw new UnderflowException("Queue is empty");
}
// Note: array_shift is O(n) - use LinkedQueue for better performance
return array_shift($this->items);
}
public function peek(): mixed
{
if ($this->isEmpty()) {
throw new UnderflowException("Queue is empty");
}
return $this->items[0];
}
public function isEmpty(): bool
{
return empty($this->items);
}
public function isFull(): bool
{
return $this->size() >= $this->maxSize;
}
public function size(): int
{
return count($this->items);
}
}
edge-cases-test.php
<?php
declare(strict_types=1);
// 1. Operations on empty structures
$stack = new RobustStack();
try {
$stack->pop(); // Should throw UnderflowException
} catch (UnderflowException $e) {
echo "Caught: {$e->getMessage()}\n";
}
// 2. Operations on full structures
$stack = new RobustStack(maxSize: 3);
$stack->push(1);
$stack->push(2);
$stack->push(3);
try {
$stack->push(4); // Should throw OverflowException
} catch (OverflowException $e) {
echo "Caught: {$e->getMessage()}\n";
}
// 3. Single element
$stack = new RobustStack();
$stack->push(10);
echo $stack->peek(); // Should work
$stack->pop(); // Should work
echo $stack->isEmpty() ? "Empty" : "Not empty"; // Empty
// 4. Alternating push/pop
$stack = new RobustStack();
$stack->push(1);
$stack->pop();
$stack->push(2);
$stack->pop();
echo $stack->isEmpty() ? "Empty" : "Not empty"; // Empty
// 5. Type safety
$stack = new RobustStack();
$stack->push(42); // int
$stack->push("hello"); // string
$stack->push([1, 2, 3]); // array
$stack->push(new stdClass); // object

Laravel: Queue Service with Dependency Injection

Section titled “Laravel: Queue Service with Dependency Injection”
app/Services/QueueService.php
<?php
declare(strict_types=1);
namespace App\Services;
use Illuminate\Contracts\Queue\Queue as QueueContract;
use SplQueue;
class QueueService
{
private SplQueue $queue;
public function __construct()
{
$this->queue = new SplQueue();
}
public function enqueue(mixed $item): void
{
$this->queue->enqueue($item);
\Log::info('Item enqueued', ['item' => $item]);
}
public function dequeue(): mixed
{
if ($this->queue->isEmpty()) {
throw new \RuntimeException('Queue is empty');
}
$item = $this->queue->dequeue();
\Log::info('Item dequeued', ['item' => $item]);
return $item;
}
public function isEmpty(): bool
{
return $this->queue->isEmpty();
}
public function size(): int
{
return $this->queue->count();
}
}
// Usage in Controller
use App\Services\QueueService;
class TaskController extends Controller
{
public function __construct(
private QueueService $queueService
) {}
public function enqueue(Request $request)
{
$task = $request->input('task');
$this->queueService->enqueue($task);
return response()->json(['message' => 'Task queued']);
}
public function process()
{
if ($this->queueService->isEmpty()) {
return response()->json(['message' => 'No tasks to process']);
}
$task = $this->queueService->dequeue();
// Process task
ProcessTaskJob::dispatch($task);
return response()->json(['message' => 'Task processing started']);
}
}
src/Service/UndoService.php
<?php
declare(strict_types=1);
namespace App\Service;
use SplStack;
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
#[Autoconfigure(shared: false)] // New instance per request
class UndoService
{
private SplStack $undoStack;
private SplStack $redoStack;
public function __construct()
{
$this->undoStack = new SplStack();
$this->redoStack = new SplStack();
}
public function executeCommand(CommandInterface $command): void
{
$command->execute();
$this->undoStack->push($command);
// Clear redo stack on new action
$this->redoStack = new SplStack();
}
public function undo(): bool
{
if ($this->undoStack->isEmpty()) {
return false;
}
$command = $this->undoStack->pop();
$command->undo();
$this->redoStack->push($command);
return true;
}
public function redo(): bool
{
if ($this->redoStack->isEmpty()) {
return false;
}
$command = $this->redoStack->pop();
$command->execute();
$this->undoStack->push($command);
return true;
}
public function canUndo(): bool
{
return !$this->undoStack->isEmpty();
}
public function canRedo(): bool
{
return !$this->redoStack->isEmpty();
}
}
// Command Interface
interface CommandInterface
{
public function execute(): void;
public function undo(): void;
}
// Example Command
class UpdateTextCommand implements CommandInterface
{
public function __construct(
private Document $document,
private string $newText,
private ?string $oldText = null
) {
$this->oldText = $document->getText();
}
public function execute(): void
{
$this->document->setText($this->newText);
}
public function undo(): void
{
$this->document->setText($this->oldText);
}
}
app/Services/RateLimiter.php
<?php
declare(strict_types=1);
namespace App\Services;
use Illuminate\Support\Facades\Cache;
use SplQueue;
class RateLimiter
{
private int $maxRequests;
private int $timeWindow; // in seconds
public function __construct(int $maxRequests = 100, int $timeWindow = 60)
{
$this->maxRequests = $maxRequests;
$this->timeWindow = $timeWindow;
}
public function allow(string $key): bool
{
$cacheKey = "rate_limit:{$key}";
$queue = Cache::get($cacheKey);
if (!$queue) {
$queue = new SplQueue();
}
$now = time();
// Remove old timestamps outside the time window
while (!$queue->isEmpty() && $queue->bottom() < $now - $this->timeWindow) {
$queue->dequeue();
}
// Check if limit exceeded
if ($queue->count() >= $this->maxRequests) {
return false;
}
// Add current timestamp
$queue->enqueue($now);
// Store back in cache
Cache::put($cacheKey, $queue, $this->timeWindow);
return true;
}
public function remaining(string $key): int
{
$cacheKey = "rate_limit:{$key}";
$queue = Cache::get($cacheKey, new SplQueue());
$now = time();
// Remove old timestamps
while (!$queue->isEmpty() && $queue->bottom() < $now - $this->timeWindow) {
$queue->dequeue();
}
return max(0, $this->maxRequests - $queue->count());
}
}
secure-stack.php
<?php
declare(strict_types=1);
class SecureStack
{
private array $items = [];
private int $maxSize = 1000; // Reasonable limit
public function push(mixed $value): void
{
// Prevent unbounded growth
if (count($this->items) >= $this->maxSize) {
throw new OverflowException("Stack size limit exceeded");
}
// Validate input size (prevent memory exhaustion)
if (is_string($value) && strlen($value) > 1000000) {
throw new InvalidArgumentException("Value too large");
}
$this->items[] = $value;
}
}
secure-queue.php
<?php
declare(strict_types=1);
class SecureQueue
{
private SplQueue $queue;
public function enqueue(mixed $value): void
{
// Sanitize strings
if (is_string($value)) {
$value = htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
}
// Prevent serialized object injection
if (is_string($value) && $this->isSerialized($value)) {
throw new InvalidArgumentException("Serialized data not allowed");
}
$this->queue->enqueue($value);
}
private function isSerialized(string $data): bool
{
return @unserialize($data) !== false || $data === 'b:0;';
}
}
throttled-stack.php
<?php
declare(strict_types=1);
class ThrottledStack
{
private Stack $stack;
private int $operations = 0;
private int $maxOperationsPerSecond = 1000;
private float $lastReset;
public function __construct()
{
$this->stack = new Stack();
$this->lastReset = microtime(true);
}
private function checkThrottle(): void
{
$now = microtime(true);
if ($now - $this->lastReset >= 1.0) {
$this->operations = 0;
$this->lastReset = $now;
}
if ($this->operations >= $this->maxOperationsPerSecond) {
throw new RuntimeException("Rate limit exceeded");
}
$this->operations++;
}
public function push(mixed $value): void
{
$this->checkThrottle();
$this->stack->push($value);
}
public function pop(): mixed
{
$this->checkThrottle();
return $this->stack->pop();
}
}

Pitfall 1: Using array_shift for Queue (O(n) Performance)

Section titled “Pitfall 1: Using array_shift for Queue (O(n) Performance)”
pitfalls-example.php
<?php
declare(strict_types=1);
// BAD: Very slow for large queues
class SlowQueue
{
private array $items = [];
public function dequeue(): mixed
{
return array_shift($this->items); // O(n) - reindexes entire array!
}
}
// GOOD: Use SplQueue or linked list
class FastQueue
{
private SplQueue $queue;
public function __construct()
{
$this->queue = new SplQueue();
}
public function dequeue(): mixed
{
return $this->queue->dequeue(); // O(1)
}
}
// OR: Use circular queue with fixed size
class FastCircularQueue
{
private array $items;
private int $front = 0;
private int $rear = -1;
private int $size = 0;
private int $capacity;
public function dequeue(): mixed
{
// ... (O(1) with modulo arithmetic)
}
}

Pitfall 2: Not Checking Empty Before Pop/Dequeue

Section titled “Pitfall 2: Not Checking Empty Before Pop/Dequeue”
empty-check-example.php
<?php
declare(strict_types=1);
// BAD: Can cause errors
$stack = new Stack();
$value = $stack->pop(); // Fatal error or unexpected behavior
// GOOD: Always check
$stack = new Stack();
if (!$stack->isEmpty()) {
$value = $stack->pop();
} else {
// Handle empty case
}
// BETTER: Use exceptions
try {
$value = $stack->pop();
} catch (UnderflowException $e) {
// Handle gracefully
error_log("Attempted to pop from empty stack");
}

Pitfall 3: Modifying Queue During Iteration

Section titled “Pitfall 3: Modifying Queue During Iteration”
iteration-pitfall.php
<?php
declare(strict_types=1);
// BAD: Unexpected behavior
$queue = new SplQueue();
$queue->enqueue(1);
$queue->enqueue(2);
$queue->enqueue(3);
foreach ($queue as $item) {
$queue->dequeue(); // Don't modify while iterating!
}
// GOOD: Use while loop
while (!$queue->isEmpty()) {
$item = $queue->dequeue();
// Process item
}

Pitfall 4: Not Clearing Redo Stack on New Action

Section titled “Pitfall 4: Not Clearing Redo Stack on New Action”
undo-redo-pitfall.php
<?php
declare(strict_types=1);
// BAD: Redo stack has invalid states
class BadUndoRedo
{
private Stack $undoStack;
private Stack $redoStack;
public function execute($command): void
{
$command->execute();
$this->undoStack->push($command);
// FORGOT: $this->redoStack->clear();
}
}
// GOOD: Clear redo on new action
class GoodUndoRedo
{
private Stack $undoStack;
private Stack $redoStack;
public function execute($command): void
{
$command->execute();
$this->undoStack->push($command);
$this->redoStack->clear(); // Clear redo history!
}
}

Pitfall 5: Memory Leaks with Large Objects

Section titled “Pitfall 5: Memory Leaks with Large Objects”
memory-leak-example.php
<?php
declare(strict_types=1);
// BAD: Keeps references to large objects
class LeakyStack
{
private array $items = [];
public function push($value): void
{
$this->items[] = $value; // Large objects never released
}
}
// GOOD: Implement proper cleanup
class CleanStack
{
private array $items = [];
private int $maxSize = 100;
public function push($value): void
{
// Limit size to prevent unbounded growth
if (count($this->items) >= $this->maxSize) {
array_shift($this->items); // Remove oldest
}
$this->items[] = $value;
}
public function clear(): void
{
// Explicitly clear to help garbage collection
$this->items = [];
}
public function __destruct()
{
$this->clear();
}
}
FeatureStackQueueDeque
InsertTop onlyRear onlyBoth ends
RemoveTop onlyFront onlyBoth ends
OrderLIFOFIFOFlexible
Use CaseUndo, recursionScheduling, BFSSliding window

Advanced Stack Techniques: Monotonic Stack

Section titled “Advanced Stack Techniques: Monotonic Stack”

A monotonic stack maintains elements in either strictly increasing or strictly decreasing order. This powerful technique solves “next greater/smaller element” problems efficiently.

The stack stores indices (or values) and maintains a monotonic property:

  • Increasing monotonic stack: Elements increase from bottom to top
  • Decreasing monotonic stack: Elements decrease from bottom to top

When processing a new element, we pop elements that violate the monotonic property.

Find the next greater element for each element in an array:

next-greater-element.php
<?php
declare(strict_types=1);
function nextGreaterElements(array $nums): array
{
$n = count($nums);
$result = array_fill(0, $n, -1);
$stack = []; // Stores indices
for ($i = 0; $i < $n; $i++) {
// While stack not empty and current is greater than stack top
while (!empty($stack) && $nums[$i] > $nums[end($stack)]) {
$idx = array_pop($stack);
$result[$idx] = $nums[$i];
}
$stack[] = $i;
}
return $result;
}
$nums = [4, 2, 1, 5, 3];
print_r(nextGreaterElements($nums));
// [5, 5, 5, -1, -1]
// Explanation: 4→5, 2→5, 1→5, 5→none, 3→none

Time Complexity: O(n) - each element pushed and popped at most once
Space Complexity: O(n)

Find the next smaller element for each element:

next-smaller-element.php
<?php
declare(strict_types=1);
function nextSmallerElements(array $nums): array
{
$n = count($nums);
$result = array_fill(0, $n, -1);
$stack = []; // Stores indices
for ($i = 0; $i < $n; $i++) {
// While stack not empty and current is smaller than stack top
while (!empty($stack) && $nums[$i] < $nums[end($stack)]) {
$idx = array_pop($stack);
$result[$idx] = $nums[$i];
}
$stack[] = $i;
}
return $result;
}
$nums = [4, 2, 1, 5, 3];
print_r(nextSmallerElements($nums));
// [2, 1, -1, 3, -1]

Find the largest rectangle area in a histogram:

largest-rectangle-histogram.php
<?php
declare(strict_types=1);
function largestRectangleArea(array $heights): int
{
$stack = [];
$maxArea = 0;
$n = count($heights);
for ($i = 0; $i <= $n; $i++) {
$height = ($i === $n) ? 0 : $heights[$i];
while (!empty($stack) && $height < $heights[end($stack)]) {
$h = $heights[array_pop($stack)];
$width = empty($stack) ? $i : $i - end($stack) - 1;
$maxArea = max($maxArea, $h * $width);
}
$stack[] = $i;
}
return $maxArea;
}
$heights = [2, 1, 5, 6, 2, 3];
echo largestRectangleArea($heights); // 10
// Explanation: Rectangle of height 5 and width 2 (indices 2-3)

Why It Works: We maintain an increasing stack. When we find a bar shorter than the top, we calculate the area of rectangles ending at the popped bar.

Calculate the span of stock prices (days until a higher price):

stock-span.php
<?php
declare(strict_types=1);
function stockSpan(array $prices): array
{
$n = count($prices);
$span = array_fill(0, $n, 1);
$stack = []; // Stores indices
for ($i = 0; $i < $n; $i++) {
// Pop indices with prices <= current price
while (!empty($stack) && $prices[end($stack)] <= $prices[$i]) {
array_pop($stack);
}
// Span is distance from last higher price
$span[$i] = empty($stack) ? $i + 1 : $i - end($stack);
$stack[] = $i;
}
return $span;
}
$prices = [100, 80, 60, 70, 60, 75, 85];
print_r(stockSpan($prices));
// [1, 1, 1, 2, 1, 4, 6]

Find how many days until a warmer temperature:

daily-temperatures.php
<?php
declare(strict_types=1);
function dailyTemperatures(array $temperatures): array
{
$n = count($temperatures);
$result = array_fill(0, $n, 0);
$stack = []; // Stores indices
for ($i = 0; $i < $n; $i++) {
// While current temperature is warmer than stack top
while (!empty($stack) && $temperatures[$i] > $temperatures[end($stack)]) {
$idx = array_pop($stack);
$result[$idx] = $i - $idx;
}
$stack[] = $i;
}
return $result;
}
$temps = [73, 74, 75, 71, 69, 72, 76, 73];
print_r(dailyTemperatures($temps));
// [1, 1, 4, 2, 1, 1, 0, 0]

A monotonic deque maintains elements in sorted order and efficiently finds min/max in a sliding window. This is covered in detail in Chapter 36: Stream Processing Algorithms, but here’s a brief introduction:

monotonic-deque-intro.php
<?php
declare(strict_types=1);
class MonotonicDeque
{
private SplDoublyLinkedList $deque;
public function __construct()
{
$this->deque = new SplDoublyLinkedList();
}
// Maintain decreasing order for max
public function pushForMax(int $value): void
{
while (!$this->deque->isEmpty() && $this->deque->top() < $value) {
$this->deque->pop();
}
$this->deque->push($value);
}
// Maintain increasing order for min
public function pushForMin(int $value): void
{
while (!$this->deque->isEmpty() && $this->deque->top() > $value) {
$this->deque->pop();
}
$this->deque->push($value);
}
public function getMax(): ?int
{
return $this->deque->isEmpty() ? null : $this->deque->bottom();
}
public function getMin(): ?int
{
return $this->deque->isEmpty() ? null : $this->deque->bottom();
}
public function remove(int $value): void
{
if (!$this->deque->isEmpty() && $this->deque->bottom() === $value) {
$this->deque->shift();
}
}
}

Key Insight: By maintaining monotonic order, the front always contains the min/max, giving O(1) access. See Chapter 36 for complete sliding window implementations.

Implement a stack that supports push, pop, and getMin in O(1):

exercise-min-stack.php
<?php
declare(strict_types=1);
class MinStack
{
// Your implementation here
public function push(int $value): void {}
public function pop(): int {}
public function top(): int {}
public function getMin(): int {} // O(1)!
}
$stack = new MinStack();
$stack->push(3);
$stack->push(5);
$stack->push(2);
echo $stack->getMin(); // 2

Implement a queue using only two stacks:

exercise-queue-with-stacks.php
<?php
declare(strict_types=1);
class QueueWithStacks
{
private Stack $stack1; // For enqueue
private Stack $stack2; // For dequeue
public function __construct()
{
$this->stack1 = new Stack();
$this->stack2 = new Stack();
}
public function enqueue(mixed $value): void
{
$this->stack1->push($value);
}
public function dequeue(): mixed
{
if ($this->stack2->isEmpty()) {
// Move all elements from stack1 to stack2
while (!$this->stack1->isEmpty()) {
$this->stack2->push($this->stack1->pop());
}
}
if ($this->stack2->isEmpty()) {
throw new UnderflowException("Queue is empty");
}
return $this->stack2->pop();
}
public function peek(): mixed
{
if ($this->stack2->isEmpty()) {
while (!$this->stack1->isEmpty()) {
$this->stack2->push($this->stack1->pop());
}
}
if ($this->stack2->isEmpty()) {
throw new UnderflowException("Queue is empty");
}
return $this->stack2->peek();
}
public function isEmpty(): bool
{
return $this->stack1->isEmpty() && $this->stack2->isEmpty();
}
}
// Usage
$queue = new QueueWithStacks();
$queue->enqueue(1);
$queue->enqueue(2);
$queue->enqueue(3);
echo $queue->dequeue() . "\n"; // 1 (FIFO)
echo $queue->peek() . "\n"; // 2

How It Works:

  • Enqueue: Push to stack1 (O(1))
  • Dequeue: If stack2 is empty, transfer all elements from stack1 to stack2 (reverses order), then pop from stack2
  • Amortized Complexity: O(1) per operation (each element moved at most once)

Find maximum in each window of size k:

exercise-sliding-window.php
<?php
declare(strict_types=1);
function slidingWindowMax(array $nums, int $k): array
{
$n = count($nums);
$result = [];
$deque = new SplDoublyLinkedList(); // Stores indices
for ($i = 0; $i < $n; $i++) {
// Remove indices outside current window
while (!$deque->isEmpty() && $deque->bottom() <= $i - $k) {
$deque->shift();
}
// Remove indices with values smaller than current
while (!$deque->isEmpty() && $nums[$deque->top()] <= $nums[$i]) {
$deque->pop();
}
$deque->push($i);
// Add max to result when window is complete
if ($i >= $k - 1) {
$result[] = $nums[$deque->bottom()];
}
}
return $result;
}
print_r(slidingWindowMax([1,3,-1,-3,5,3,6,7], 3));
// [3, 3, 5, 5, 6, 7]

How It Works: Maintain a decreasing deque. The front always contains the index of the maximum in the current window. Remove indices outside the window and those with smaller values.

Implement a stack using only two queues:

exercise-stack-with-queues.php
<?php
declare(strict_types=1);
class StackWithQueues
{
private Queue $queue1;
private Queue $queue2;
public function __construct()
{
$this->queue1 = new Queue();
$this->queue2 = new Queue();
}
public function push(mixed $value): void
{
// Always push to non-empty queue (or queue1 if both empty)
if (!$this->queue1->isEmpty()) {
$this->queue1->enqueue($value);
} else {
$this->queue2->enqueue($value);
}
}
public function pop(): mixed
{
if ($this->isEmpty()) {
throw new UnderflowException("Stack is empty");
}
// Find non-empty queue
$fromQueue = $this->queue1->isEmpty() ? $this->queue2 : $this->queue1;
$toQueue = $this->queue1->isEmpty() ? $this->queue1 : $this->queue2;
// Move all but last element to other queue
while ($fromQueue->size() > 1) {
$toQueue->enqueue($fromQueue->dequeue());
}
// Last element is the top of stack
return $fromQueue->dequeue();
}
public function peek(): mixed
{
if ($this->isEmpty()) {
throw new UnderflowException("Stack is empty");
}
$fromQueue = $this->queue1->isEmpty() ? $this->queue2 : $this->queue1;
$toQueue = $this->queue1->isEmpty() ? $this->queue1 : $this->queue2;
$top = null;
while (!$fromQueue->isEmpty()) {
$top = $fromQueue->dequeue();
$toQueue->enqueue($top);
}
// Swap queues
$temp = $this->queue1;
$this->queue1 = $this->queue2;
$this->queue2 = $temp;
return $top;
}
public function isEmpty(): bool
{
return $this->queue1->isEmpty() && $this->queue2->isEmpty();
}
}
// Usage
$stack = new StackWithQueues();
$stack->push(1);
$stack->push(2);
$stack->push(3);
echo $stack->pop() . "\n"; // 3 (LIFO)
echo $stack->peek() . "\n"; // 2

How It Works:

  • Push: Add to the non-empty queue (or queue1 if both empty)
  • Pop: Move all elements except the last from one queue to the other, then dequeue the last element
  • Complexity: O(n) for pop/peek, O(1) for push

Congratulations! You’ve completed this chapter on stacks and queues. Here’s what you’ve accomplished:

  • ✓ Implemented stack (LIFO) data structure using arrays and linked lists
  • ✓ Built queue (FIFO) data structure with multiple implementation strategies
  • ✓ Created practical applications: expression evaluator, undo/redo system, task scheduler
  • ✓ Understood performance trade-offs between array and linked list implementations
  • ✓ Explored advanced variants: circular queues, deques, and priority queues
  • ✓ Learned PHP SPL implementations (SplStack, SplQueue, SplPriorityQueue)
  • ✓ Analyzed real-world performance benchmarks and memory usage
  • ✓ Applied stacks and queues to solve classic problems (balanced parentheses, BFS, rate limiting)
  • ✓ Learned monotonic stack technique for next greater/smaller element problems
  • ✓ Solved advanced problems: largest rectangle, stock span, daily temperatures
  • ✓ Understood security considerations and common pitfalls

Stacks and queues are fundamental building blocks for many algorithms and data structures. The concepts you’ve learned here will be essential for understanding tree traversals, graph algorithms, and many other advanced topics in the chapters ahead.

  • Stacks are LIFO - perfect for undo, recursion, expression evaluation
  • Queues are FIFO - ideal for scheduling, BFS, buffering
  • Array implementation is simple but dequeue can be O(n)
  • Linked list implementation makes all operations O(1)
  • Circular queue is space-efficient
  • Deque is versatile - can act as stack or queue
  • Priority queue processes by priority, not order
  • SPL implementations are optimized C code - use them in production
  • Choose implementation based on your use case: arrays for simplicity, linked lists for performance
  • Monotonic stack solves next greater/smaller element problems efficiently
  • Monotonic deque enables O(1) min/max queries in sliding windows

In the next chapter, we’ll explore Trees & Binary Search Trees, learning hierarchical data structures and their operations.

All code examples from this chapter are available in the GitHub repository:

View Chapter 17 Code Samples

Clone the repository to run examples:

Terminal window
git clone https://github.com/dalehurley/codewithphp.git
cd codewithphp/code/php-algorithms/chapter-17
php 01-*.php

Continue to Chapter 18: Trees & Binary Search Trees.