Deque, List, Forward List — Double-Ended Queue, Linked Lists Performance
In this tutorial, you will learn about Deque, List, Forward List. We cover key concepts, practical examples, and best practices to help you master this topic.
C++ deque is a double-ended queue with O(1) front and back operations and random access, while list and forward_list are linked lists offering O(1) insertion at arbitrary positions when an Iterator is available.
What You'll Learn
You will use std::deque when you need fast front and back operations with random access, use std::list for bidirectional traversal with stable iterators, use std::forward_list for memory-efficient singly-linked lists, understand iterator invalidation rules for each container, and choose the right linked structure based on your access and insertion patterns.
Why It Matters
Choosing the right sequence container significantly impacts performance. While std::vector is the default, deque is superior for queues and buffers, list excels when you need stable iterators through insertions, and forward_list is the most memory-efficient linked structure. Misunderstanding these containers leads to poor performance or incorrect code.
Learning Path
graph LR
A["30: Vector"] --> B["31: Deque, List, Forward List"]
B --> C["32: Set & Multiset"]
C --> D["33: Map & Multimap"]
style A fill:#4a90d9,stroke:#2c5f8a,color:#fff
style B fill:#4a90d9,stroke:#2c5f8a,color:#fff
style C fill:#4a90d9,stroke:#2c5f8a,color:#fff
style D fill:#4a90d9,stroke:#2c5f8a,color:#fff
std::deque
Double-ended queue: dynamic array of fixed-size blocks.
#include <iostream>
#include <deque>
int main() {
std::deque<int> dq;
// O(1) at both ends
dq.push_back(10);
dq.push_front(20);
dq.push_back(30);
dq.push_front(40);
std::cout << "Front: " << dq.front() << ", Back: " << dq.back() << "\n";
// Random access (O(1))
std::cout << "dq[2] = " << dq[2] << "\n";
// Iterate
for (int x : dq) std::cout << x << " ";
std::cout << "\n";
// Pop from ends
dq.pop_front();
dq.pop_back();
// Size and capacity-like behavior
std::cout << "Size: " << dq.size() << "\n";
// Insert at arbitrary position (O(n))
dq.insert(dq.begin() + 1, 99);
}
Deque Characteristics
| Operation | Complexity |
|---|---|
| Random access | O(1) |
| Push/pop front | O(1) (amortized) |
| Push/pop back | O(1) (amortized) |
| Insert in middle | O(n) |
| Iterator invalidation (front/back push) | None (most implementations) |
Deque is implemented as a sequence of fixed-size blocks, not a single contiguous array. This means it can grow at both ends without moving existing elements.
std::list
Doubly-Linked List.
#include <iostream>
#include <list>
#include <algorithm>
int main() {
std::list<int> lst = {5, 2, 8, 1, 9};
// O(1) insertion at any position (with iterator)
auto it = std::find(lst.begin(), lst.end(), 8);
if (it != lst.end()) {
lst.insert(it, 99); // insert before 8
}
// Splice: transfer elements from another list (O(1))
std::list<int> other = {100, 200, 300};
lst.splice(lst.end(), other); // transfers all elements
std::cout << "other size: " << other.size() << "\n"; // 0
// Merge two sorted lists (O(n))
std::list<int> a = {1, 3, 5};
std::list<int> b = {2, 4, 6};
a.merge(b); // a becomes {1, 2, 3, 4, 5, 6}
for (int x : a) std::cout << x << " ";
std::cout << "\n";
// Sort (O(n log n))
lst.sort();
// Reverse
lst.reverse();
// Unique: remove consecutive duplicates
lst.unique();
// No random access
// lst[3] // Error
}
List Characteristics
| Operation | Complexity |
|---|---|
| Random access | O(n) (not supported) |
| Insert at position | O(1) (with iterator) |
| Erase at position | O(1) (with iterator) |
| Splice | O(1) |
| Sort | O(n log n) |
| Iterator invalidation (insert/erase) | Only the affected element |
std::forward_list
Singly-linked list (C++11).
#include <iostream>
#include <forward_list>
int main() {
std::forward_list<int> fl = {3, 1, 4, 1, 5};
// No size() method (would be O(n))
// No back() access
// Only forward iteration
// Insert after a position (not before)
auto pos = fl.before_begin();
for (auto it = fl.begin(); it != fl.end(); ++it) {
if (*it == 4) break;
++pos;
}
fl.insert_after(pos, 99);
// push_front only (no push_back)
fl.push_front(0);
// Splice after
std::forward_list<int> other = {10, 20};
fl.splice_after(fl.before_begin(), other); // insert at front
for (int x : fl) std::cout << x << " ";
std::cout << "\n";
}
forward_list Characteristics
| Operation | Complexity |
|---|---|
| Forward traversal | O(1) per step |
| Insert after | O(1) |
| Erase after | O(1) |
| push_front | O(1) |
| Memory overhead | 1 pointer per element (vs 2 for list) |
Iterator Invalidation Comparison
| Operation | vector | deque | list | forward_list |
|---|---|---|---|---|
| Read-only | No | No | No | No |
| Push/pop back | Maybe | Front: no, Back: no | No | N/A |
| Push/pop front | N/A | Front: no, Back: no | No | No |
| Insert/erase in middle | Yes | Yes | Only erased | Only erased |
| Reallocation | All | Never (no reallocation) | N/A | N/A |
Performance Comparison
#include <iostream>
#include <vector>
#include <deque>
#include <list>
#include <chrono>
template <typename Container>
double test(size_t count) {
auto start = std::chrono::high_resolution_clock::now();
Container c;
for (size_t i = 0; i < count; ++i) {
c.push_back(static_cast<int>(i));
}
auto end = std::chrono::high_resolution_clock::now();
return std::chrono::duration<double, std::milli>(end - start).count();
}
int main() {
constexpr size_t N = 1000000;
std::cout << "vector: " << test<std::vector<int>>(N) << " ms\n";
std::cout << "deque: " << test<std::deque<int>>(N) << " ms\n";
std::cout << "list: " << test<std::list<int>>(N) << " ms\n";
std::cout << "forward_list: " << test<std::forward_list<int>>(N) << " ms\n";
}
Common Mistakes
Mistake 1: Using list When Cache Locality Matters
Linked lists have poor cache locality. Each element may be in a completely different memory page. For most workloads, vector or deque is faster even if you need occasional insertions.
Mistake 2: Calling size() on forward_list
std::forward_list does not have a size() method because it would be O(n). Track the size yourself if needed.
Mistake 3: Assuming deque is Contiguous in Memory
Although deque supports operator[], elements are NOT contiguous. You cannot pass deque.data() to a C function expecting an array.
Mistake 4: Using list::sort When You Could Sort Once
list::sort is stable O(n log n), but if you can sort while inserting, use std::set or std::priority_queue instead.
Mistake 5: Erasing During Iteration
for (auto it = lst.begin(); it != lst.end(); ++it) {
if (*it % 2 == 0) lst.erase(it); // it is invalidated!
}
Use it = lst.erase(it); instead, but do not increment when erasing.
Mistake 6: Using forward_list When list Would Be Simpler
forward_list only iterates forward. If you need bidirectional iteration, use list.
Practice Questions
- When would you choose
dequeovervector? - What is the advantage of
listovervectorfor insertions in the middle? - Why does
forward_listnot have asize()method? - What is the splice operation and why is it O(1)?
- Write code that removes every other element from a
listusing iterator manipulation.
Challenge
Implement a circular buffer using deque with a maximum capacity. When the buffer is full and a new element is pushed, the oldest element is automatically removed. Compare its performance with a vector-based circular buffer.
FAQ
Mini Project
Build a task scheduler using deque:
#include <iostream>
#include <deque>
#include <string>
#include <functional>
class TaskScheduler {
private:
std::deque<std::function<void()>> tasks_;
public:
void addFront(std::function<void()> task) {
tasks_.push_front(std::move(task));
}
void addBack(std::function<void()> task) {
tasks_.push_back(std::move(task));
}
void runAll() {
while (!tasks_.empty()) {
auto task = std::move(tasks_.front());
tasks_.pop_front();
task();
}
}
size_t pending() const { return tasks_.size(); }
};
int main() {
TaskScheduler scheduler;
scheduler.addBack([]() { std::cout << "Task 1 (back)\n"; });
scheduler.addBack([]() { std::cout << "Task 2 (back)\n"; });
scheduler.addFront([]() { std::cout << "Urgent task (front)\n"; });
std::cout << "Pending: " << scheduler.pending() << "\n";
scheduler.runAll();
}
What's Next
Linked lists and deques cover sequence containers. The next lesson covers set and multiset: ordered and unordered associative containers for unique and duplicate elements.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro