krahets/hello-algo · error · IndexError

Double-ended queue is empty

Error message

Double-ended queue is empty

What it means

LinkedListDeque.pop (the internal dequeue used by pop_first/pop_last) raises IndexError('Double-ended queue is empty') when is_empty() is true, before attempting to dereference self._front.val / self._rear.val. Because the linked-list head/tail pointers are None when empty, the guard prevents an AttributeError on .val and gives a clear message. Both pop_first and pop_last route through this method.

Source

Thrown at en/codes/python/chapter_stack_and_queue/linkedlist_deque.py:66

        else:
            # Add node to the tail of the linked list
            self._rear.next = node
            node.prev = self._rear
            self._rear = node  # Update tail node
        self._size += 1  # Update queue length

    def push_first(self, num: int):
        """Front of the queue enqueue"""
        self.push(num, True)

    def push_last(self, num: int):
        """Rear of the queue enqueue"""
        self.push(num, False)

    def pop(self, is_front: bool) -> int:
        """Dequeue operation"""
        if self.is_empty():
            raise IndexError("Double-ended queue is empty")
        # Front of the queue dequeue operation
        if is_front:
            val: int = self._front.val  # Temporarily store head node value
            # Delete head node
            fnext: ListNode | None = self._front.next
            if fnext is not None:
                fnext.prev = None
                self._front.next = None
            self._front = fnext  # Update head node
        # Rear of the queue dequeue operation
        else:
            val: int = self._rear.val  # Temporarily store tail node value
            # Delete tail node
            rprev: ListNode | None = self._rear.prev
            if rprev is not None:
                rprev.next = None
                self._rear.prev = None
            self._rear = rprev  # Update tail node

View on GitHub (pinned to 69932aed18)

Solutions

  1. Guard with the predicate: `if not deque.is_empty(): deque.pop_first()`.
  2. Bound drains: `while not deque.is_empty(): ...`.
  3. Use deque.size() to drive counted loops.
  4. Wrap pop in a helper returning Optional when empty states are expected.

Example fix

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

Strategy: validation

Validate before calling

if not deque.is_empty():
    val = deque.pop_first()

Type guard

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

Try / catch

try:
    val = deque.pop_first()
except IndexError:
    val = None

Prevention

When it happens

Trigger: Calling pop_first()/pop_last() on an empty deque; calling pop after the last node was detached; unguarded drain loops alternating front/rear pops; deque used as a stack/queue drained past empty.

Common situations: BFS/DFS with a linked deque; sliding-window deque emptied by pops; undo/redo on a linked structure; algorithms that pop from whichever end is cheaper without checking size.

Related errors


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