Collection Adapters and Specialized Types

pop_front() and pop_back()

The .pop_front() and .pop_back() methods (along with .pop_front_if() and .pop_back_if()) remove and return the first or last element of a VecDeque<T> in guaranteed $O(1)$ time, returning None if the deque is empty.

Signatures and Companion Inspection Methods

impl<T> VecDeque<T> {
    pub fn pop_front(&mut self) -> Option<T>;
    pub fn pop_back(&mut self) -> Option<T>;

    pub fn front(&self) -> Option<&T>;
    pub fn front_mut(&mut self) -> Option<&mut T>;
    pub fn back(&self) -> Option<&T>;
    pub fn back_mut(&mut self) -> Option<&mut T>;
}
Operation Front (Index 0) Back (Index len - 1) Ring Buffer Update
Read without removing q.front() q.back() None
Mutate without removing q.front_mut() q.back_mut() None
Remove and return (Option<T>) q.pop_front() q.pop_back() Advances head = (head + 1) % cap (front) or decrements len (back)
Conditional remove q.pop_front_if(pred) q.pop_back_if(pred) Pops only if pred(&mut elem) returns true

Breadth-First Search (BFS) Using push_back() and pop_front()

A classic FIFO queue for Breadth-First Search pushes newly discovered neighbors to the back (push_back) and dequeues the next node to visit from the front (pop_front):

use std::collections::VecDeque;

fn bfs_shortest_levels(adj: &[Vec<usize>], start: usize) -> Vec<Option<u32>> {
    let mut dist = vec![None; adj.len()];
    let mut queue = VecDeque::new();

    dist[start] = Some(0);
    queue.push_back(start);

    while let Some(u) = queue.pop_front() {
        let next_d = dist[u].unwrap() + 1;
        for &v in &adj[u] {
            if dist[v].is_none() {
                dist[v] = Some(next_d);
                queue.push_back(v);
            }
        }
    }
    dist
}

fn main() {
    // 0 -> 1 -> 3, 0 -> 2 -> 3
    let graph = vec![vec![1, 2], vec![3], vec![3], vec![]];
    let distances = bfs_shortest_levels(&graph, 0);
    println!("Shortest hop distances from node 0: {distances:?}");
}
Shortest hop distances from node 0: [Some(0), Some(1), Some(1), Some(2)]

Two-Ended Consumption (Palindrome / Work-Stealing Deque Pattern)

Because both pop_front() and pop_back() are $O(1)$, algorithms can consume elements from both ends toward the middle—such as checking whether a sequence is a palindrome or allowing worker threads to pop local tasks from one end while thieves steal from the other:

use std::collections::VecDeque;

fn is_palindrome<T: Eq>(items: impl IntoIterator<Item = T>) -> bool {
    let mut deque: VecDeque<T> = items.into_iter().collect();

    // When 0 or 1 element remains, `pop_front()` and `pop_back()` stop:
    while deque.len() > 1 {
        if deque.pop_front() != deque.pop_back() {
            return false;
        }
    }
    true
}

fn main() {
    println!("[1, 2, 3, 2, 1]: {}", is_palindrome([1, 2, 3, 2, 1]));
    println!("[1, 2, 3, 4]:    {}", is_palindrome([1, 2, 3, 4]));
}
[1, 2, 3, 2, 1]: true
[1, 2, 3, 4]:    false