Standard Library[lib]
std::deque in C++: what it is and when to use it
- Section
- Standard Library [lib]
- Standard
- C++17
- Compilers
- MSVC 19.44, Clang 19.1 (MSVC STL), GCC 16.2, Clang 23.1 (libc++)
- Published
- Length
- 7 min read
In this answer7 clauses
Abstract
std::deque, from the header <deque>, is a double-ended queue: push_front, push_back, pop_front and pop_back take constant time, and [] and at() reach any element by index. Its elements are not in one contiguous block, so a push at either end never moves the elements already there, but it has no data(), capacity() or reserve(). Use a vector by default, a deque when you add or remove at the front, and a std::queue when first in, first out is all you need.

std::deque, the double-ended queue from the header <deque>, is a sequence container that adds and removes elements at both the front and the back in constant time, and reaches any element by index like a vector. Its elements are not in one contiguous block, so a push at either end never moves the elements already there.
The smallest program: a line of people at a desk, where Ana joins at the front and Dee at the back.
#include <deque>
#include <iostream>
#include <string>
int main() {
std::deque<std::string> line{"Ben", "Cy"};
line.push_front("Ana"); // Ana Ben Cy
line.push_back("Dee"); // Ana Ben Cy Dee
std::cout << "front: " << line.front() << '\n'; // Ana
std::cout << "back: " << line.back() << '\n'; // Dee
std::cout << "[1]: " << line[1] << '\n'; // Ben
std::cout << "size: " << line.size() << '\n'; // 4
line.pop_front(); // Ana is served
for (const std::string& name : line)
std::cout << name << '\n'; // Ben, Cy, Dee
}— end example]
It printed:
front: Ana
back: Dee
[1]: Ben
size: 4
Ben
Cy
Dee— end example]
The samples are C++17, built with MSVC 19.44, Clang 19.1 (MSVC STL), GCC 16.2 and Clang 23.1 (libc++), and all four printed the same unless a sentence says otherwise. Nothing on this page needs a newer standard; Since When lists what each one added.
How do you use a std::deque?
Add with push_front() and push_back(), remove with pop_front() and pop_back(), and read with front(), back(), [] or at(); size() counts the elements and empty() says whether there are none. The traps are in indexing and in an empty deque:
at(i)checks, and throwsstd::out_of_rangewheniis not less thansize().[]is not required to check: an index past the end is undefined behavior, which means the standard sets no requirement on what happens; built without optimization, GCC 16.2’s library stopped the program withAssertion '__n < this->size()' failed., while Clang with libc++ read past the end and went on.front(),back()and a pop on an empty deque are undefined behavior too, so testempty()first.
std::cout << line[2] << '\n'; // Cy: [] need not check
std::cout << line.at(3) << '\n'; // Dee: at() checks
try {
std::cout << line.at(10) << '\n'; // only 4 people
} catch (const std::out_of_range&) {
std::cout << "no one at position 10\n";
}
while (!line.empty()) { // test before every pop
std::cout << "serving " << line.front() << '\n';
line.pop_front();
}
std::cout << "size now: " << line.size() << '\n'; // 0— end example]
On a line of Ana, Ben, Cy and Dee, all four printed Cy, Dee and no one at position 10, served the four in order and ended on size now: 0.
How is a deque stored?
In separate blocks. Typical implementations keep the elements in a series of fixed-size arrays, each allocated on its own, plus a small index of those blocks, instead of one contiguous array.

A push at either end fills a free slot in the first or last block, or adds a block, and the standard promises the result: pointers and references to the elements already there stay valid. A pointer to Ben, taken before 2,000 pushes:
std::cout << std::boolalpha;
std::deque<std::string> line{"Ben", "Cy"};
const std::string* first = &line[0]; // points at Ben
for (int i = 0; i < 1000; ++i)
line.push_back("Guest"); // 1,000 at the back
for (int i = 0; i < 1000; ++i)
line.push_front("Guest"); // 1,000 at the front
// Ben is now at index 1000
std::cout << (*first == "Ben") << '\n'; // true
std::cout << (&line[1000] == first) << '\n'; // true: not moved— end example]
On all three standard libraries the pointer still read Ben, at the same address as line[1000]. A std::vector filled to its capacity and given one more push_back moved its first element to a new block on all three.
The cost of the blocks: a deque has no data(), capacity() or reserve(), so you cannot hand it to a function that takes a pointer and a length.
std::string* first = line.data(); // error: a deque has no data()— end example]
Clang with libc++ said:
error: no member named 'data' in 'std::deque<std::string>'— end example]
reserve() and capacity() were rejected the same way by all four compilers.
std::deque vs std::vector: which should you use?
Use a std::vector by default, and a deque when you add or remove elements at the front.
| Operation | std::deque |
std::vector |
|---|---|---|
| Add or remove at the front | constant time | linear: every element shifts |
| Add at the back | constant time | constant, with a reallocation now and then |
Read by index with [] |
constant, one extra step | constant |
| Elements in one contiguous block | no | yes |
push_back moves existing elements |
never | when it grows |
Adding at the front is what decides. Putting 1,000 elements at the front, the deque moved none of the elements already inside, on all three libraries; a vector, adding each with insert() at begin(), moved elements already inside 499,500 times, because every insert at the front shifts all of them. For how a vector grows and what its growth invalidates, see std::vector size, capacity and reallocation.
Deque vs queue: what is the difference?
std::deque is a container you can use from both ends, index and loop over. std::queue is a container adaptor, a thin wrapper around a container that lets you push only at the back and pop only at the front, and by default that container is a std::deque. std::stack uses one too:
static_assert(std::is_same_v<std::queue<int>::container_type,
std::deque<int>>);
static_assert(std::is_same_v<std::stack<int>::container_type,
std::deque<int>>);
// ...
std::queue<int> desk;
std::deque<int> line;
for (int ticket : {1, 2, 3}) {
desk.push(ticket); // queue: in at the back
line.push_back(ticket); // deque: the same, by name
}
desk.pop(); // out at the front
line.pop_front();— end example]
Both static_asserts passed on all three libraries, and the two containers ended the same: 2 at the front, 3 at the back.
Use a C++ queue when first in, first out is all you need: the type says so and allows nothing else. Use the deque when you also need the other end, an index or a loop over the elements. A C++ stack is the same choice for last in, first out.
What do insert and erase cost, and what do they invalidate?
In the middle, linear time, as in a vector: elements shift to open or close the gap. Here print writes the names on one line:
line.insert(line.begin() + 2, "Bo"); // before Cy
print(line); // Ana Ben Bo Cy Dee
line.erase(line.begin() + 1); // Ben leaves
print(line); // Ana Bo Cy Dee— end example]
All four printed those two lines. Changes can also invalidate iterators, the position markers begin() hands out, and pointers and references to elements; reading through one afterwards is undefined behavior. The rules from the standard ([deque]):
| Change | Iterators | Pointers and references |
|---|---|---|
push_front, push_back |
all invalid | all still valid |
insert in the middle |
all invalid | all invalid |
pop_front, pop_back |
the removed element’s, plus end() when the last element goes |
the removed element’s |
erase in the middle |
all invalid | all invalid |
To hold on to an element across pushes, keep a pointer or a reference, not an iterator. An index stays right across push_back but not push_front, which shifts every position: Ben went from index 0 to index 1000 above.
When should you use a deque?
When items arrive at one end and leave from the other while you still read all of them: a sliding window that keeps the last few, or a work list fed at both ends. The last three temperature readings and their average:
std::cout << std::fixed << std::setprecision(1);
std::deque<int> window; // the last 3 readings
for (int reading : {18, 21, 19, 24, 22}) {
window.push_back(reading);
if (window.size() > 3)
window.pop_front(); // drop the oldest
double sum = 0;
for (int r : window) {
std::cout << r << ' ';
sum += r;
}
std::cout << "-> " << sum / window.size() << '\n';
}— end example]
Once the fourth reading came in, the oldest left: all four printed 18 21 19 -> 19.3, then 21 19 24 -> 21.3. A std::queue would not do here, because the average loops over the elements.
Skip the deque when you need the elements in one block of memory, or when you mostly append and index: that is a vector’s work.
Which container: deque, vector, queue or list?
| If you | Use |
|---|---|
| add and remove at both ends, and index | std::deque |
| append and index, or need one block of memory | std::vector |
| only need first in, first out | std::queue |
| insert in the middle through iterators that must stay valid | std::list |
The other containers and the rest of the library are in Standard library.
Questions
What does deque stand for?
Double-ended queue: a sequence you can add to and remove from at both ends. In C++ it is std::deque, and unlike a std::queue it also lets you read any element by index and loop over all of them.
Which header is std::deque in?
Include <deque>. std::queue, from <queue>, and std::stack, from <stack>, keep their elements in a std::deque by default, but code that names std::deque itself should include <deque>.
Does std::deque have reserve or capacity?
No. A deque has no reserve() and no capacity(), and calls to either did not compile on any of the four compilers tested. It has size(), resize() and shrink_to_fit(), which is a request to free unused memory that the library may ignore.
Is the memory of a std::deque contiguous?
No. The elements are not stored in one block; typical implementations use a series of separately allocated fixed-size arrays. That is why a deque has no data() member, and why it cannot be passed to a function that takes a pointer and a length.
Is std::deque slower than std::vector?
It depends on the operation. In the usual implementation a deque takes one more step to read by index (find the block, then the element), and its elements are not in one block of memory. At the front it runs the other way: each insert there shifts every element of a vector and moves none in a deque. If speed decides it, measure your own case.
Should I use a deque or a list?
Both add and remove at both ends in constant time. A deque reaches any element by index and a std::list does not. A list keeps its iterators valid when you insert elsewhere, while any insert into a deque invalidates all of its iterators. Pick the deque unless you insert or erase in the middle and need iterators that stay valid; then use the list.
Sources
- std::dequecppreference.com
- std::deque::atcppreference.com
- std::deque::operator[]cppreference.com
- std::deque::frontcppreference.com
- std::deque::pop_frontcppreference.com
- [deque] Class template dequeC++ standard working draft
- std::queuecppreference.com
- std::stackcppreference.com