krahets/hello-algo · error · IndexError

двусторонняя очередь пуста

Error message

двусторонняя очередь пуста

What it means

Raised by LinkedListDeque.pop(is_front) in chapter_stack_and_queue/linkedlist_deque.py:66 — IndexError("двусторонняя очередь пуста"). This is the internal removal routine called by pop_first()/pop_last(); it refuses to unlink from an empty deque because self._front/self._rear would be None. Note the public API is pop_first()/pop_last(); pop(is_front) is the shared private helper.

Source

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

        else:
            # Добавить node в хвост списка
            self._rear.next = node
            node.prev = self._rear
            self._rear = node  # Обновить хвостовой узел
        self._size += 1  # Обновить длину очереди

    def push_first(self, num: int):
        """Добавление в голову очереди"""
        self.push(num, True)

    def push_last(self, num: int):
        """Добавление в хвост очереди"""
        self.push(num, False)

    def pop(self, is_front: bool) -> int:
        """Операция извлечения из очереди"""
        if self.is_empty():
            raise IndexError("двусторонняя очередь пуста")
        # Операция извлечения из головы очереди
        if is_front:
            val: int = self._front.val  # Временно сохранить значение головного узла
            # Удалить головной узел
            fnext: ListNode | None = self._front.next
            if fnext is not None:
                fnext.prev = None
                self._front.next = None
            self._front = fnext  # Обновить головной узел
        # Операция извлечения из хвоста очереди
        else:
            val: int = self._rear.val  # Временно сохранить значение хвостового узла
            # Удалить хвостовой узел
            rprev: ListNode | None = self._rear.prev
            if rprev is not None:
                rprev.next = None
                self._rear.prev = None
            self._rear = rprev  # Обновить хвостовой узел

View on GitHub (pinned to 69932aed18)

Solutions

  1. Guard the public call: if not dq.is_empty(): dq.pop_first().
  2. Use is_empty() as the termination condition for two-ended draining.
  3. Wrap pop_first/pop_last in try/except IndexError for best-effort removal.
  4. Track logical element count so pops never exceed pushes.

Example fix

// before
x = dq.pop_first()
// after
x = dq.pop_first() if not dq.is_empty() else None
Defensive patterns

Strategy: validation

Validate before calling

def safe_pop_first(dq):
    return dq.pop_first() if not dq.is_empty() else None

Type guard

def deque_non_empty(dq) -> bool:
    return not dq.is_empty()

Try / catch

try:
    x = dq.pop_first()  # delegates to private pop(is_front=True)
except IndexError:
    x = None

Prevention

When it happens

Trigger: Calling pop_first() or pop_last() on an empty deque (both delegate here). More pops than pushes from either end. Asymmetric use (push only on one end, pop on the other) until drained.

Common situations: Palindrome/two-pointer deque algorithms that overshoot the middle. Consumer outrunning producer on a shared deque. Cleanup that pops remaining elements twice.

Related errors


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