Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

After this page, you should be able to:

Builds on: Queues restrict additions/removals to opposite ends; here both ends are exposed.

Code languages: Python defines the explanations, figures and self-checks. Use the language guide for native conventions and dependencies. Complete runnable topic examples: Python · JavaScript · Julia.

Julia uses one-based positions and named functions such as enqueue! rather than Python-style instance methods. Endpoint adapters use the supplied linked DequeBuffer, so they do not hide linear array-front shifts. Empty operations reject the request; null/nothing remain valid payloads.

A deque exposes both ends

A deque (double-ended queue) allows insertion and removal at both ends. Distinguish the container name deque from the queue operation dequeue.

OperationReturned resultState change or empty policyLibrary mapping
add_first(x)NoneAdd at frontappendleft(x)
add_last(x)NoneAdd at backappend(x)
peek_first()Front itemObserve; empty raises IndexErrord[0]
peek_last()Back itemObserve; empty raises IndexErrord[-1]
remove_first()Front itemRemove; empty raises IndexErrorpopleft()
remove_last()Back itemRemove; empty raises IndexErrorpop()
size() / len(d)Number of itemsObservelen(d)
is_empty()BooleanObservelen(d) == 0

The interface below is unbounded: it does not automatically discard an item. Repeated values and None are valid. Empty observations/removals raise before changing state. “First” and “last” identify ends, not minimum and maximum keys.

Representation, invariant and trace

The wrapped library deque stores exactly the logical records, from first to last. All methods preserve that order except for the one endpoint addition/removal requested. The next listing uses library names to show the underlying operations directly:

Python
JavaScript
Julia
from collections import deque

items = deque()
items.append("B")
items.appendleft("A")
items.append("C")
assert items.pop() == "C"
items.appendleft("X")
assert items.popleft() == "X"
assert list(items) == ["A", "B"]

Python implementation with the ADT names

Python
JavaScript
Julia
from collections import deque

class Deque:
    def __init__(self):
        self._items = deque()

    def __len__(self):
        return len(self._items)

    def size(self):
        return len(self)

    def is_empty(self):
        return len(self) == 0

    def add_first(self, item):
        self._items.appendleft(item)

    def add_last(self, item):
        self._items.append(item)

    def peek_first(self):
        if self.is_empty():
            raise IndexError("first of empty deque")
        return self._items[0]

    def peek_last(self):
        if self.is_empty():
            raise IndexError("last of empty deque")
        return self._items[-1]

    def remove_first(self):
        if self.is_empty():
            raise IndexError("remove first from empty deque")
        return self._items.popleft()

    def remove_last(self):
        if self.is_empty():
            raise IndexError("remove last from empty deque")
        return self._items.pop()

Implementation choices and correctness

The methods delegate to the corresponding endpoint, so each successful removal returns and removes exactly that endpoint record. The wrapper keeps the abstract names consistent without recreating the library’s internals. A circular buffer or a doubly linked chain is an alternative representation. A singly linked chain with only head/tail references still needs a predecessor search to remove its tail.

Time and space costs

All operations in the table use O(1) time in the ordinary endpoint-operation model and O(1) wrapper workspace. The underlying library describes endpoint updates as approximately constant time. Storage is O(n+1) for n live items; payload objects are not copied by endpoint operations. These are not guarantees of hard real-time allocation.

Python’s deque supports approximately constant-time additions and removals at either end. Indexed access near the middle is linear, so a deque is not a replacement for an array when arbitrary indexed access dominates. Empty endpoint removal or inspection raises IndexError.

A deque can implement stack behaviour by using only one end, or queue behaviour by adding at one end and removing at the other. A restricted stack or queue interface can still communicate the intended policy more clearly.

When to use it: keep the latest readings

To retain the latest four readings, add each new reading at the back and discard the oldest at the front when the window exceeds four items.

Python
JavaScript
Julia
window = deque([12, 15, 14, 18])
window.append(20)
if len(window) > 4:
    window.popleft()
assert list(window) == [15, 14, 18, 20]
A rolling window discards the oldest reading 12 and adds 20, retaining 15, 14, 18, 20.

The invariant after each update is: the window contains the most recent at most four readings, in arrival order. This policy intentionally discards old data. A bounded deque(maxlen=4) can perform that eviction automatically on append, while a bounded queue may instead reject an insertion when full. These are different contracts.

Compare the front and back

A palindrome reads the same in both directions. A deque can hold its characters: repeatedly remove and compare both ends until fewer than two remain. RACECAR gives matching pairs R/R, A/A and C/C, with E left in the middle.

Python
JavaScript
Julia
def is_palindrome(sequence):
    remaining = deque(sequence)
    while len(remaining) > 1:
        if remaining.popleft() != remaining.pop():
            return False
    return True

This checks exact element equality: it does not remove spaces or normalise case. Building and scanning the deque takes O(n)O(n) time and O(n)O(n) additional space for nn input elements. Two indices into an existing sequence can avoid that extra copy; the deque version illustrates the access policy.

Check your understanding

Predict the operation result and the resulting state before opening the explanation.

1. Add B at the back, A at the front and C at the back; remove last. What is returned and what remains?

Show explanation

C is returned; A,B remain from first to last.

2. Does a full rolling window have to raise OverflowError on a new reading?

Show explanation

No. The rolling-window contract intentionally evicts an old reading. A bounded queue that promises rejection has a different policy.

Reference: Python deque documentation. Next: Arrays and positional operations.