December 13, 2025

Today I submitted my second pull request to the (https://github.com/modular/modular) standard library! Here's the story of hunting down and fixing a subtle bug in the Deque collection.

---

The Bug: Issue #5635

A community member reported that when you create a Deque with a custom capacity greater than 64 (the default min_capacity), values mysteriously "disappear" during pop/append cycles:

var dq = Deque[Pair](capacity=100)

dq.append(Pair(0, 0))

for _ in range(100):

var p = dq.pop()

p.b += 1

dq.append(p)

print(dq.pop().b) # Expected: 100, Actual: 1

The Investigation

I dove into the _realloc() method in deque.mojo. This method handles resizing the internal circular buffer when growing or shrinking.

The key insight: when _head == _tail, the deque is either completely empty OR completely full. The original code couldn't distinguish between these two cases:

```

Original buggy code

var deque_len = self._capacity if not self else len(self)

```

When shrinking an empty deque (after many pop/append cycles triggered a resize), it incorrectly used _capacity instead of 0, corrupting the _tail pointer.

The Fix

The solution was elegantly simple—use the new capacity to determine intent:

```

When head == tail, deque is either empty or full. Use capacity only

when growing (full buffer), not when shrinking (sparse buffer).

var is_full = not self and new_capacity > self._capacity

var deque_len = self._capacity if is_full else len(self)

```

- Growing new_capacity > capacity): Buffer must be full → use capacity

- Shrinking new_capacity < _capacity): Buffer is sparse → use len(self)

Testing

I added comprehensive regression tests covering:

- popappend cycles with capacity=100

- popleftappendleft cycles

- Multiple capacity values: 65, 128, 256

All 23 collection tests pass!

PR Submitted

[PR #5654: [stdlib] Fix Deque shrink realloc when capacity > min_capacity](https://github.com/modular/modular/pull/5654)

The Copilot review suggested one improvement—changing "empty buffer" to "sparse buffer" in my comment for clarity. Addressed and pushed!

---

Lessons Learned

1. Circular buffers are tricky — The head == tail ambiguity is a classic pitfall

2. Python's deque helped — Studying CPython's implementation gave me context

3. Good tests matter— The regression test ensures this bug stays fixed

Now I wait for the StdLib team review. Two PRs in, feeling more confident navigating the Mojo codebase!

---

Previous post: [PR #5652 - Adding missing imports to pointer documentation]