Stacks & Queues

LIFO and FIFO data structures with various implementations

intermediate
stackqueuedequevec
🎮 Interactive Playground

What are Stacks and Queues?

Stack is a LIFO (Last-In-First-Out) data structure where elements are added and removed from the same end (top). Think of a stack of plates. Queue is a FIFO (First-In-First-Out) data structure where elements are added at one end (back) and removed from the other (front). Think of a line at a store. The Rust perspective:
  • Vec serves as an excellent stack with push() and pop()
  • VecDeque provides efficient double-ended queue operations
  • Type safety through custom wrapper types
  • Zero-cost abstractions for stack/queue interfaces
// Vec as stack - efficient!
let mut stack = Vec::new();
stack.push(1);
stack.push(2);
let top = stack.pop();  // Some(2)

// VecDeque as queue
use std::collections::VecDeque;
let mut queue = VecDeque::new();
queue.push_back(1);
queue.push_back(2);
let front = queue.pop_front();  // Some(1)

When to Use Stacks and Queues

Stack use cases:
  1. Function call management (call stack)
  2. Expression evaluation and parsing
  3. Undo/redo functionality
  4. Backtracking algorithms (DFS)
  5. Bracket matching and validation
  6. Syntax tree traversal
Queue use cases:
  1. Task scheduling and job queues
  2. Message passing systems
  3. Breadth-first search (BFS)
  4. Rate limiting and buffering
  5. Print spoolers
  6. Event handling

Real-World Example 1: Type-Safe Stack with Generics

A complete stack implementation with additional features.

use std::fmt;

/// A generic stack implementation
#[derive(Clone)]
pub struct Stack<T> {
    data: Vec<T>,
    capacity: Option<usize>,
}

impl<T> Stack<T> {
    /// Create a new empty stack
    pub fn new() -> Self {
        Self {
            data: Vec::new(),
            capacity: None,
        }
    }

    /// Create a stack with fixed capacity
    pub fn with_capacity(capacity: usize) -> Self {
        Self {
            data: Vec::with_capacity(capacity),
            capacity: Some(capacity),
        }
    }

    /// Push an element onto the stack
    pub fn push(&mut self, item: T) -> Result<(), StackError> {
        if let Some(cap) = self.capacity {
            if self.data.len() >= cap {
                return Err(StackError::Overflow);
            }
        }
        self.data.push(item);
        Ok(())
    }

    /// Pop an element from the stack
    pub fn pop(&mut self) -> Option<T> {
        self.data.pop()
    }

    /// Peek at the top element without removing
    pub fn peek(&self) -> Option<&T> {
        self.data.last()
    }

    /// Peek at the top element mutably
    pub fn peek_mut(&mut self) -> Option<&mut T> {
        self.data.last_mut()
    }

    /// Check if stack is empty
    pub fn is_empty(&self) -> bool {
        self.data.is_empty()
    }

    /// Get the number of elements
    pub fn len(&self) -> usize {
        self.data.len()
    }

    /// Check if stack is full (only for bounded stacks)
    pub fn is_full(&self) -> bool {
        self.capacity.map_or(false, |cap| self.data.len() >= cap)
    }

    /// Clear all elements
    pub fn clear(&mut self) {
        self.data.clear();
    }

    /// Get remaining capacity
    pub fn remaining_capacity(&self) -> Option<usize> {
        self.capacity.map(|cap| cap - self.data.len())
    }

    /// Iterate over elements from top to bottom
    pub fn iter(&self) -> impl Iterator<Item = &T> {
        self.data.iter().rev()
    }

    /// Drain all elements from top to bottom
    pub fn drain(&mut self) -> impl Iterator<Item = T> + '_ {
        self.data.drain(..).rev()
    }
}

impl<T> Default for Stack<T> {
    fn default() -> Self {
        Self::new()
    }
}

impl<T: fmt::Debug> fmt::Debug for Stack<T> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "Stack[")?;
        for (i, item) in self.data.iter().rev().enumerate() {
            if i > 0 {
                write!(f, ", ")?;
            }
            write!(f, "{:?}", item)?;
        }
        write!(f, "]")
    }
}

impl<T> FromIterator<T> for Stack<T> {
    fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
        Self {
            data: iter.into_iter().collect(),
            capacity: None,
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum StackError {
    Overflow,
    Underflow,
}

impl fmt::Display for StackError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            StackError::Overflow => write!(f, "Stack overflow"),
            StackError::Underflow => write!(f, "Stack underflow"),
        }
    }
}

impl std::error::Error for StackError {}

// Expression evaluation using stack

#[derive(Debug, Clone)]
pub enum Token {
    Number(f64),
    Operator(char),
    LeftParen,
    RightParen,
}

/// Evaluate postfix (Reverse Polish Notation) expression
pub fn eval_postfix(tokens: &[Token]) -> Result<f64, String> {
    let mut stack = Stack::new();

    for token in tokens {
        match token {
            Token::Number(n) => {
                stack.push(*n).map_err(|_| "Stack overflow")?;
            }
            Token::Operator(op) => {
                let b = stack.pop().ok_or("Missing operand")?;
                let a = stack.pop().ok_or("Missing operand")?;

                let result = match op {
                    '+' => a + b,
                    '-' => a - b,
                    '*' => a * b,
                    '/' => {
                        if b == 0.0 {
                            return Err("Division by zero".to_string());
                        }
                        a / b
                    }
                    '^' => a.powf(b),
                    _ => return Err(format!("Unknown operator: {}", op)),
                };

                stack.push(result).map_err(|_| "Stack overflow")?;
            }
            _ => return Err("Unexpected token in postfix expression".to_string()),
        }
    }

    if stack.len() != 1 {
        return Err("Invalid expression".to_string());
    }

    stack.pop().ok_or("Empty result".to_string())
}

/// Convert infix to postfix (Shunting Yard algorithm)
pub fn infix_to_postfix(tokens: &[Token]) -> Result<Vec<Token>, String> {
    let mut output = Vec::new();
    let mut operators = Stack::new();

    let precedence = |op: char| -> i32 {
        match op {
            '+' | '-' => 1,
            '*' | '/' => 2,
            '^' => 3,
            _ => 0,
        }
    };

    let is_left_assoc = |op: char| -> bool {
        op != '^'
    };

    for token in tokens {
        match token {
            Token::Number(_) => output.push(token.clone()),
            Token::Operator(op) => {
                while let Some(Token::Operator(top)) = operators.peek() {
                    if (is_left_assoc(*op) && precedence(*op) <= precedence(*top))
                        || (!is_left_assoc(*op) && precedence(*op) < precedence(*top))
                    {
                        output.push(operators.pop().unwrap());
                    } else {
                        break;
                    }
                }
                operators.push(token.clone()).map_err(|_| "Stack overflow")?;
            }
            Token::LeftParen => {
                operators.push(token.clone()).map_err(|_| "Stack overflow")?;
            }
            Token::RightParen => {
                while let Some(top) = operators.peek() {
                    if matches!(top, Token::LeftParen) {
                        break;
                    }
                    output.push(operators.pop().unwrap());
                }
                if operators.pop().is_none() {
                    return Err("Mismatched parentheses".to_string());
                }
            }
        }
    }

    while let Some(token) = operators.pop() {
        if matches!(token, Token::LeftParen | Token::RightParen) {
            return Err("Mismatched parentheses".to_string());
        }
        output.push(token);
    }

    Ok(output)
}

fn main() {
    // Basic stack operations
    let mut stack: Stack<i32> = Stack::new();
    stack.push(1).unwrap();
    stack.push(2).unwrap();
    stack.push(3).unwrap();

    println!("Stack: {:?}", stack);
    println!("Top: {:?}", stack.peek());
    println!("Pop: {:?}", stack.pop());
    println!("After pop: {:?}", stack);

    // Bounded stack
    let mut bounded: Stack<i32> = Stack::with_capacity(2);
    bounded.push(1).unwrap();
    bounded.push(2).unwrap();
    println!("\nBounded stack full: {}", bounded.is_full());
    println!("Push to full: {:?}", bounded.push(3));

    // Expression evaluation: (3 + 4) * 2 = 14
    let infix = vec![
        Token::LeftParen,
        Token::Number(3.0),
        Token::Operator('+'),
        Token::Number(4.0),
        Token::RightParen,
        Token::Operator('*'),
        Token::Number(2.0),
    ];

    println!("\nInfix expression: (3 + 4) * 2");
    let postfix = infix_to_postfix(&infix).unwrap();
    println!("Postfix: {:?}", postfix);
    let result = eval_postfix(&postfix).unwrap();
    println!("Result: {}", result);
}

Real-World Example 2: Queue Implementations

Multiple queue types for different use cases.

use std::collections::VecDeque;
use std::fmt;

/// Standard FIFO queue
#[derive(Clone)]
pub struct Queue<T> {
    data: VecDeque<T>,
    capacity: Option<usize>,
}

impl<T> Queue<T> {
    pub fn new() -> Self {
        Self {
            data: VecDeque::new(),
            capacity: None,
        }
    }

    pub fn with_capacity(capacity: usize) -> Self {
        Self {
            data: VecDeque::with_capacity(capacity),
            capacity: Some(capacity),
        }
    }

    /// Add element to back of queue
    pub fn enqueue(&mut self, item: T) -> Result<(), QueueError> {
        if let Some(cap) = self.capacity {
            if self.data.len() >= cap {
                return Err(QueueError::Full);
            }
        }
        self.data.push_back(item);
        Ok(())
    }

    /// Remove element from front of queue
    pub fn dequeue(&mut self) -> Option<T> {
        self.data.pop_front()
    }

    /// Peek at front element
    pub fn peek(&self) -> Option<&T> {
        self.data.front()
    }

    /// Peek at back element
    pub fn peek_back(&self) -> Option<&T> {
        self.data.back()
    }

    pub fn is_empty(&self) -> bool {
        self.data.is_empty()
    }

    pub fn len(&self) -> usize {
        self.data.len()
    }

    pub fn is_full(&self) -> bool {
        self.capacity.map_or(false, |cap| self.data.len() >= cap)
    }

    pub fn clear(&mut self) {
        self.data.clear();
    }

    pub fn iter(&self) -> impl Iterator<Item = &T> {
        self.data.iter()
    }
}

impl<T> Default for Queue<T> {
    fn default() -> Self {
        Self::new()
    }
}

impl<T: fmt::Debug> fmt::Debug for Queue<T> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "Queue{:?}", self.data)
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum QueueError {
    Full,
    Empty,
}

/// Circular buffer queue (ring buffer)
pub struct CircularQueue<T> {
    data: Vec<Option<T>>,
    head: usize,
    tail: usize,
    len: usize,
    capacity: usize,
}

impl<T> CircularQueue<T> {
    pub fn new(capacity: usize) -> Self {
        let mut data = Vec::with_capacity(capacity);
        for _ in 0..capacity {
            data.push(None);
        }
        Self {
            data,
            head: 0,
            tail: 0,
            len: 0,
            capacity,
        }
    }

    pub fn enqueue(&mut self, item: T) -> Result<(), QueueError> {
        if self.is_full() {
            return Err(QueueError::Full);
        }

        self.data[self.tail] = Some(item);
        self.tail = (self.tail + 1) % self.capacity;
        self.len += 1;
        Ok(())
    }

    pub fn dequeue(&mut self) -> Option<T> {
        if self.is_empty() {
            return None;
        }

        let item = self.data[self.head].take();
        self.head = (self.head + 1) % self.capacity;
        self.len -= 1;
        item
    }

    pub fn peek(&self) -> Option<&T> {
        if self.is_empty() {
            None
        } else {
            self.data[self.head].as_ref()
        }
    }

    pub fn is_empty(&self) -> bool {
        self.len == 0
    }

    pub fn is_full(&self) -> bool {
        self.len == self.capacity
    }

    pub fn len(&self) -> usize {
        self.len
    }

    pub fn capacity(&self) -> usize {
        self.capacity
    }
}

/// Priority queue (min-heap by default)
pub struct PriorityQueue<T: Ord> {
    data: Vec<T>,
    comparator: fn(&T, &T) -> std::cmp::Ordering,
}

impl<T: Ord> PriorityQueue<T> {
    /// Create a min-heap (smallest element has highest priority)
    pub fn new_min_heap() -> Self {
        Self {
            data: Vec::new(),
            comparator: |a, b| a.cmp(b),
        }
    }

    /// Create a max-heap (largest element has highest priority)
    pub fn new_max_heap() -> Self {
        Self {
            data: Vec::new(),
            comparator: |a, b| b.cmp(a),
        }
    }

    /// Push element maintaining heap property
    pub fn push(&mut self, item: T) {
        self.data.push(item);
        self.sift_up(self.data.len() - 1);
    }

    /// Pop highest priority element
    pub fn pop(&mut self) -> Option<T> {
        if self.data.is_empty() {
            return None;
        }

        let last = self.data.len() - 1;
        self.data.swap(0, last);
        let item = self.data.pop();

        if !self.data.is_empty() {
            self.sift_down(0);
        }

        item
    }

    /// Peek at highest priority element
    pub fn peek(&self) -> Option<&T> {
        self.data.first()
    }

    pub fn is_empty(&self) -> bool {
        self.data.is_empty()
    }

    pub fn len(&self) -> usize {
        self.data.len()
    }

    fn sift_up(&mut self, mut idx: usize) {
        while idx > 0 {
            let parent = (idx - 1) / 2;
            if (self.comparator)(&self.data[idx], &self.data[parent])
                == std::cmp::Ordering::Less
            {
                self.data.swap(idx, parent);
                idx = parent;
            } else {
                break;
            }
        }
    }

    fn sift_down(&mut self, mut idx: usize) {
        loop {
            let left = 2 * idx + 1;
            let right = 2 * idx + 2;
            let mut smallest = idx;

            if left < self.data.len()
                && (self.comparator)(&self.data[left], &self.data[smallest])
                    == std::cmp::Ordering::Less
            {
                smallest = left;
            }

            if right < self.data.len()
                && (self.comparator)(&self.data[right], &self.data[smallest])
                    == std::cmp::Ordering::Less
            {
                smallest = right;
            }

            if smallest != idx {
                self.data.swap(idx, smallest);
                idx = smallest;
            } else {
                break;
            }
        }
    }
}

impl<T: Ord> Default for PriorityQueue<T> {
    fn default() -> Self {
        Self::new_min_heap()
    }
}

/// Double-ended queue (Deque)
pub struct Deque<T> {
    data: VecDeque<T>,
}

impl<T> Deque<T> {
    pub fn new() -> Self {
        Self {
            data: VecDeque::new(),
        }
    }

    pub fn push_front(&mut self, item: T) {
        self.data.push_front(item);
    }

    pub fn push_back(&mut self, item: T) {
        self.data.push_back(item);
    }

    pub fn pop_front(&mut self) -> Option<T> {
        self.data.pop_front()
    }

    pub fn pop_back(&mut self) -> Option<T> {
        self.data.pop_back()
    }

    pub fn peek_front(&self) -> Option<&T> {
        self.data.front()
    }

    pub fn peek_back(&self) -> Option<&T> {
        self.data.back()
    }

    pub fn is_empty(&self) -> bool {
        self.data.is_empty()
    }

    pub fn len(&self) -> usize {
        self.data.len()
    }
}

impl<T> Default for Deque<T> {
    fn default() -> Self {
        Self::new()
    }
}

fn main() {
    // Standard queue
    println!("=== Standard Queue ===");
    let mut queue: Queue<&str> = Queue::new();
    queue.enqueue("first").unwrap();
    queue.enqueue("second").unwrap();
    queue.enqueue("third").unwrap();

    println!("Queue: {:?}", queue);
    println!("Dequeue: {:?}", queue.dequeue());
    println!("After dequeue: {:?}", queue);

    // Circular buffer
    println!("\n=== Circular Buffer ===");
    let mut circular = CircularQueue::new(3);
    circular.enqueue(1).unwrap();
    circular.enqueue(2).unwrap();
    circular.enqueue(3).unwrap();
    println!("Full: {}, Push result: {:?}", circular.is_full(), circular.enqueue(4));

    circular.dequeue();
    circular.enqueue(4).unwrap();  // Wraps around
    println!("After wrap-around, front: {:?}", circular.peek());

    // Priority queue
    println!("\n=== Priority Queue (Min-Heap) ===");
    let mut pq = PriorityQueue::new_min_heap();
    pq.push(5);
    pq.push(2);
    pq.push(8);
    pq.push(1);
    pq.push(3);

    print!("Priority order: ");
    while let Some(item) = pq.pop() {
        print!("{} ", item);
    }
    println!();

    // Max-heap
    println!("\n=== Priority Queue (Max-Heap) ===");
    let mut max_pq = PriorityQueue::new_max_heap();
    max_pq.push(5);
    max_pq.push(2);
    max_pq.push(8);
    max_pq.push(1);

    print!("Priority order: ");
    while let Some(item) = max_pq.pop() {
        print!("{} ", item);
    }
    println!();

    // Deque
    println!("\n=== Deque ===");
    let mut deque = Deque::new();
    deque.push_back(2);
    deque.push_front(1);
    deque.push_back(3);

    println!("Front: {:?}, Back: {:?}", deque.peek_front(), deque.peek_back());
    println!("Pop front: {:?}", deque.pop_front());
    println!("Pop back: {:?}", deque.pop_back());
}

Real-World Example 3: Task Scheduler with Queue

A practical task scheduler using queues.

use std::collections::VecDeque;
use std::time::{Duration, Instant};

#[derive(Debug, Clone)]
pub struct Task {
    pub id: u64,
    pub name: String,
    pub priority: Priority,
    pub created_at: Instant,
    pub estimated_duration: Duration,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Priority {
    Low = 0,
    Normal = 1,
    High = 2,
    Critical = 3,
}

impl Task {
    pub fn new(id: u64, name: &str, priority: Priority, estimated_duration: Duration) -> Self {
        Self {
            id,
            name: name.to_string(),
            priority,
            created_at: Instant::now(),
            estimated_duration,
        }
    }
}

/// Multi-level feedback queue scheduler
pub struct TaskScheduler {
    /// Queue for each priority level
    queues: [VecDeque<Task>; 4],
    /// Currently executing task
    current: Option<Task>,
    /// Completed tasks
    completed: Vec<(Task, Duration)>,
    /// Total tasks processed
    total_processed: u64,
}

impl TaskScheduler {
    pub fn new() -> Self {
        Self {
            queues: Default::default(),
            current: None,
            completed: Vec::new(),
            total_processed: 0,
        }
    }

    /// Submit a new task
    pub fn submit(&mut self, task: Task) {
        let queue_idx = task.priority as usize;
        self.queues[queue_idx].push_back(task);
    }

    /// Get next task based on priority
    pub fn next_task(&mut self) -> Option<Task> {
        // Check queues from highest to lowest priority
        for queue in self.queues.iter_mut().rev() {
            if let Some(task) = queue.pop_front() {
                return Some(task);
            }
        }
        None
    }

    /// Start executing next task
    pub fn start_next(&mut self) -> Option<&Task> {
        if self.current.is_some() {
            return self.current.as_ref();
        }

        self.current = self.next_task();
        self.current.as_ref()
    }

    /// Complete current task
    pub fn complete_current(&mut self, actual_duration: Duration) -> Option<Task> {
        if let Some(task) = self.current.take() {
            self.completed.push((task.clone(), actual_duration));
            self.total_processed += 1;
            Some(task)
        } else {
            None
        }
    }

    /// Get queue lengths by priority
    pub fn queue_lengths(&self) -> [usize; 4] {
        [
            self.queues[0].len(),
            self.queues[1].len(),
            self.queues[2].len(),
            self.queues[3].len(),
        ]
    }

    /// Total pending tasks
    pub fn pending_count(&self) -> usize {
        self.queues.iter().map(|q| q.len()).sum()
    }

    /// Get statistics
    pub fn stats(&self) -> SchedulerStats {
        let total_wait_time: Duration = self
            .completed
            .iter()
            .map(|(task, _)| task.created_at.elapsed())
            .sum();

        let avg_wait_time = if self.completed.is_empty() {
            Duration::ZERO
        } else {
            total_wait_time / self.completed.len() as u32
        };

        SchedulerStats {
            total_processed: self.total_processed,
            pending: self.pending_count(),
            completed: self.completed.len(),
            avg_wait_time,
        }
    }
}

impl Default for TaskScheduler {
    fn default() -> Self {
        Self::new()
    }
}

#[derive(Debug)]
pub struct SchedulerStats {
    pub total_processed: u64,
    pub pending: usize,
    pub completed: usize,
    pub avg_wait_time: Duration,
}

/// Work-stealing queue for parallel processing
pub struct WorkStealingQueue<T> {
    local: VecDeque<T>,
    id: usize,
}

impl<T> WorkStealingQueue<T> {
    pub fn new(id: usize) -> Self {
        Self {
            local: VecDeque::new(),
            id,
        }
    }

    /// Push to local queue
    pub fn push(&mut self, item: T) {
        self.local.push_back(item);
    }

    /// Pop from local queue (LIFO for locality)
    pub fn pop(&mut self) -> Option<T> {
        self.local.pop_back()
    }

    /// Steal from another queue (FIFO)
    pub fn steal_from(&mut self, other: &mut WorkStealingQueue<T>) -> Option<T> {
        other.local.pop_front()
    }

    /// Steal half of another queue's work
    pub fn steal_half_from(&mut self, other: &mut WorkStealingQueue<T>) -> usize {
        let steal_count = other.local.len() / 2;
        for _ in 0..steal_count {
            if let Some(item) = other.local.pop_front() {
                self.local.push_back(item);
            }
        }
        steal_count
    }

    pub fn len(&self) -> usize {
        self.local.len()
    }

    pub fn is_empty(&self) -> bool {
        self.local.is_empty()
    }

    pub fn id(&self) -> usize {
        self.id
    }
}

fn main() {
    // Task scheduler demo
    println!("=== Task Scheduler Demo ===\n");

    let mut scheduler = TaskScheduler::new();

    // Submit tasks with different priorities
    scheduler.submit(Task::new(1, "Background sync", Priority::Low, Duration::from_secs(10)));
    scheduler.submit(Task::new(2, "User request", Priority::Normal, Duration::from_secs(2)));
    scheduler.submit(Task::new(3, "Database backup", Priority::Normal, Duration::from_secs(30)));
    scheduler.submit(Task::new(4, "Emergency alert", Priority::Critical, Duration::from_secs(1)));
    scheduler.submit(Task::new(5, "Cache refresh", Priority::High, Duration::from_secs(5)));

    println!("Queue lengths [Low, Normal, High, Critical]: {:?}", scheduler.queue_lengths());

    // Process tasks
    println!("\nProcessing tasks in priority order:");
    while let Some(task) = scheduler.start_next() {
        println!("  Executing: {} (Priority: {:?})", task.name, task.priority);
        // Simulate execution
        let actual = task.estimated_duration;
        scheduler.complete_current(actual);
    }

    let stats = scheduler.stats();
    println!("\nScheduler Stats:");
    println!("  Total processed: {}", stats.total_processed);
    println!("  Completed: {}", stats.completed);

    // Work-stealing demo
    println!("\n=== Work-Stealing Queue Demo ===\n");

    let mut worker1 = WorkStealingQueue::new(1);
    let mut worker2 = WorkStealingQueue::new(2);

    // Worker 1 has lots of work
    for i in 0..10 {
        worker1.push(format!("Task {}", i));
    }

    println!("Before stealing:");
    println!("  Worker 1 queue length: {}", worker1.len());
    println!("  Worker 2 queue length: {}", worker2.len());

    // Worker 2 steals half
    let stolen = worker2.steal_half_from(&mut worker1);
    println!("\nWorker 2 stole {} tasks", stolen);

    println!("\nAfter stealing:");
    println!("  Worker 1 queue length: {}", worker1.len());
    println!("  Worker 2 queue length: {}", worker2.len());

    // Process stolen work
    println!("\nWorker 2 processing stolen work:");
    while let Some(task) = worker2.pop() {
        println!("  {}", task);
    }
}

Comparison: Stack and Queue Implementations

| Implementation | Push | Pop | Peek | Memory | Use Case |

|---------------|------|-----|------|--------|----------|

| Vec (stack) | O(1)* | O(1) | O(1) | Contiguous | General stack |

| VecDeque (queue) | O(1)* | O(1) | O(1) | Ring buffer | General queue |

| Linked list | O(1) | O(1) | O(1) | Scattered | Stable pointers |

| Circular buffer | O(1) | O(1) | O(1) | Fixed | Bounded queues |

| Binary heap | O(log n) | O(log n) | O(1) | Array | Priority queue |

*amortized

⚠️ Anti-patterns

// DON'T: Use Vec as queue (O(n) pop front)
let mut bad_queue = Vec::new();
bad_queue.push(1);
bad_queue.remove(0);  // O(n)! Use VecDeque instead

// DON'T: Implement stack with linked list when Vec works
struct OvercomplicatedStack<T> {
    head: Option<Box<Node<T>>>,  // Unnecessary complexity
}

// DO: Just use Vec
let mut stack = Vec::new();
stack.push(1);
stack.pop();

// DON'T: Unbounded queues in production
fn process_requests(queue: &mut Queue<Request>) {
    // Memory can grow forever!
}

// DO: Use bounded queues or apply backpressure
fn process_requests(queue: &mut Queue<Request>) -> Result<(), QueueError> {
    if queue.len() > MAX_QUEUE_SIZE {
        return Err(QueueError::Full);  // Apply backpressure
    }
    // ...
}

Best Practices

  1. Use Vec for stacks - Simple and cache-friendly
  2. Use VecDeque for queues - O(1) operations on both ends
  3. Bound your queues - Prevent memory exhaustion
  4. Consider priority queues - When order matters beyond FIFO
  5. Use work-stealing - For parallel processing
  6. Profile before optimizing - The obvious choice is often correct

Performance Characteristics

| Operation | Vec Stack | VecDeque Queue | Priority Queue |

|-----------|-----------|----------------|----------------|

| Push | O(1) | O(1) | O(log n) |

| Pop | O(1) | O(1) | O(log n) |

| Peek | O(1) | O(1) | O(1) |

| Search | O(n) | O(n) | O(n) |

| Memory | Dense | Dense | Dense |

Exercises

Beginner

  1. Implement bracket matching using a stack
  2. Create a simple print queue simulator

Intermediate

  1. Build a call stack tracer for function calls
  2. Implement a round-robin scheduler with multiple queues

Advanced

  1. Create a lock-free concurrent queue
  2. Implement a time-based priority queue with deadlines

Further Reading

Real-World Usage

  • tokio: Task scheduling with work-stealing
  • crossbeam: Lock-free concurrent queues
  • rayon: Parallel job queues
  • actix: Message queues for actors
  • reqwest: Request queue management

🎮 Try it Yourself

🎮

Stacks & Queues - Playground

Run this code in the official Rust Playground