krahets/hello-algo · error · IndexError

Double-ended queue is empty

Error message

Double-ended queue is empty

What it means

ArrayDeque.peek_first raises IndexError('Double-ended queue is empty') when the deque holds zero elements, blocking the read of self._nums[self._front] which would return stale/garbage data from the circular buffer. peek_first is also the backbone of pop_first, so the same failure surfaces through a pop_first call on an empty deque. The guard enforces that accessing the front is only valid when at least one element exists.

Source

Thrown at en/codes/python/chapter_stack_and_queue/array_deque.py:76

    def pop_first(self) -> int:
        """Front of the queue dequeue"""
        num = self.peek_first()
        # Front pointer moves one position backward
        self._front = self.index(self._front + 1)
        self._size -= 1
        return num

    def pop_last(self) -> int:
        """Rear of the queue dequeue"""
        num = self.peek_last()
        self._size -= 1
        return num

    def peek_first(self) -> int:
        """Access front of the queue element"""
        if self.is_empty():
            raise IndexError("Double-ended queue is empty")
        return self._nums[self._front]

    def peek_last(self) -> int:
        """Access rear of the queue element"""
        if self.is_empty():
            raise IndexError("Double-ended queue is empty")
        # Calculate tail element index
        last = self.index(self._front + self._size - 1)
        return self._nums[last]

    def to_array(self) -> list[int]:
        """Return array for printing"""
        # Only convert list elements within the valid length range
        res = []
        for i in range(self._size):
            res.append(self._nums[self.index(self._front + i)])
        return res

View on GitHub (pinned to 69932aed18)

Solutions

  1. Check the predicate first: `if not deque.is_empty(): head = deque.peek_first()`.
  2. Bound drain loops: `while not deque.is_empty(): x = deque.pop_first()`.
  3. Use deque.size() to drive counted loops and stop at zero.
  4. Where flow control by exception is intended, catch IndexError at the dequeue site only.

Example fix

// before
front = deque.peek_first()  # raises if empty
// after
front = deque.peek_first() if not deque.is_empty() else None
Defensive patterns

Strategy: validation

Validate before calling

if not deque.is_empty():
    front = deque.peek_first()

Type guard

def deque_nonempty(d) -> bool:
    return not d.is_empty()

Try / catch

try:
    front = deque.peek_first()
except IndexError:
    front = None

Prevention

When it happens

Trigger: Calling peek_first() or pop_first() on a freshly constructed ArrayDeque with no pushes; calling after all elements were popped; interleaving pushes/pops where a pop on the last element is followed by another pop; loops that pop_front until empty without checking is_empty().

Common situations: Sliding-window and BFS-style algorithms that drain a deque; producer/consumer pacing where the consumer outruns the producer; off-by-one in counted dequeue loops; reusing a deque capacity object across rounds without tracking size.

Related errors


AI-assisted analysis of krahets/hello-algo@69932aed18 (2026-08-13). Data as JSON: /api/errors/75a617a474f03579. Report an issue: GitHub.