Skip to main content

Overview

A Deque (double-ended queue) is a data structure that allows insertion and deletion at both ends. The UCX DSA package provides two deque implementations:
  • Deque: Fixed-capacity static deque
  • DynamicDeque: Automatically resizing deque
Both implementations use circular indexing internally for efficient space utilization.

Deque (Static)

A static deque with a fixed capacity that supports operations at both the front and back.

Creating a Deque

from dsa.deque import Deque

# Create deque with default capacity of 10
deque = Deque()
print(deque.capacity())  # 10
print(len(deque))        # 0

Key Methods

Operations at the Front

Add an element to the front of the deque:
deque = Deque()
deque.append_left(1)
deque.append_left(2)
deque.append_left(3)
print(deque.to_list())  # [3, 2, 1]

# push_front is a synonym for append_left
deque.push_front(4)
print(deque.to_list())  # [4, 3, 2, 1]

# Raises Exception if deque is full
full_deque = Deque(capacity=2)
full_deque.append_left(1)
full_deque.append_left(2)
try:
    full_deque.append_left(3)
except Exception as e:
    print(e)  # Deque Full
Time Complexity: O(1)

Operations at the Back

Add an element to the back of the deque:
deque = Deque()
deque.append_right(1)
deque.append_right(2)
deque.append_right(3)
print(deque.to_list())  # [1, 2, 3]

# push_back is a synonym for append_right
deque.push_back(4)
print(deque.to_list())  # [1, 2, 3, 4]

# Raises Exception if deque is full
full_deque = Deque(capacity=2)
full_deque.append_right(1)
full_deque.append_right(2)
try:
    full_deque.append_right(3)
except Exception as e:
    print(e)  # Deque Full
Time Complexity: O(1)

Utility Methods

deque = Deque.from_list([1, 2, 3, 4, 5])

# Check if empty
print(deque.is_empty())  # False

# Get number of elements
print(len(deque))  # 5

# Get capacity
print(deque.capacity())  # 10

# Convert to Python list
print(deque.to_list())  # [1, 2, 3, 4, 5]

# String representation
print(deque)  # [1, 2, 3, 4, 5] Count: 5 Capacity: 10

Circular Indexing

The Deque uses circular indexing internally for efficient space utilization:
deque = Deque(capacity=5)

# Add from both ends
deque.append_right(3)
deque.append_left(2)
deque.append_left(1)
deque.append_right(4)
deque.append_right(5)

print(deque.to_list())  # [1, 2, 3, 4, 5]

# Remove from both ends
deque.pop_left()   # 1
deque.pop_right()  # 5

# Add more elements (reuses space)
deque.append_left(0)
deque.append_right(6)

print(deque.to_list())  # [0, 2, 3, 4, 6]

DynamicDeque

A dynamic deque that automatically adjusts its capacity. It doubles when full and halves when utilization drops below 25%.

Creating a DynamicDeque

from dsa.deque import DynamicDeque

# Create with default capacity
deque = DynamicDeque()

# Create from list
deque = DynamicDeque.from_list([1, 2, 3])

Automatic Resizing

The DynamicDeque grows and shrinks automatically:
deque = DynamicDeque(capacity=2)
print(f"Initial capacity: {deque.capacity()}")  # 2

# Add elements - capacity doubles when full
deque.append_right(1)
deque.append_right(2)
print(f"After 2 elements: {deque.capacity()}")  # 2

deque.append_right(3)  # Triggers growth
print(f"After 3 elements: {deque.capacity()}")  # 4

deque.append_left(0)  # Add to front
deque.append_right(4)  # Triggers growth again
print(f"After 5 elements: {deque.capacity()}")  # 8

# Shrinks when count <= 1/4 of capacity
for i in range(4):
    deque.pop_left()
print(f"After removals: {deque.capacity()}")  # 4 (shrunk from 8)
Growth Strategy:
  • Grows: capacity × 2 when count >= capacity
  • Shrinks: capacity ÷ 2 when count <= capacity ÷ 4
  • Minimum capacity: 10
  • Elements are reorganized during resize to maintain order

Key Methods

DynamicDeque supports all the same methods as Deque:
deque = DynamicDeque()

# Append to both ends without capacity concerns
for i in range(50):
    deque.append_right(i)
for i in range(50):
    deque.append_left(-i)

print(len(deque))  # 100

# Pop from both ends
while not deque.is_empty():
    if len(deque) % 2 == 0:
        deque.pop_left()
    else:
        deque.pop_right()
Time Complexity:
  • append_left(), append_right(): O(1) amortized
  • pop_left(), pop_right(): O(1) amortized
  • peek_left(), peek_right(): O(1)

Use Cases

Check if a sequence is a palindrome:
from dsa.deque import Deque

def is_palindrome(text):
    """Check if text is a palindrome using deque."""
    # Remove spaces and convert to lowercase
    text = text.replace(" ", "").lower()
    deque = Deque.from_list(list(text))
    
    while len(deque) > 1:
        if deque.pop_left() != deque.pop_right():
            return False
    
    return True

print(is_palindrome("racecar"))      # True
print(is_palindrome("hello"))        # False
print(is_palindrome("A man a plan a canal Panama"))  # True

Method Aliases

Deque provides convenient aliases for operations:
OperationPrimary MethodAlias
Add to frontappend_left()push_front()
Add to backappend_right()push_back()
Remove from frontpop_left()pop_front()
Remove from backpop_right()pop_back()
View frontpeek_left()front()
View backpeek_right()back()
deque = Deque()

# These are equivalent
deque.append_left(1)
deque.push_front(1)

# These are equivalent
deque.pop_right()
deque.pop_back()

# These are equivalent
deque.peek_left()
deque.front()

Comparison: Deque vs DynamicDeque

FeatureDequeDynamicDeque
CapacityFixedDynamic
MemoryPredictableVariable
PerformanceConstant O(1)Amortized O(1)
Use CaseKnown max sizeUnknown size
Exception on FullYesNo (auto-grows)
ShrinkingNoYes (below 25%)
When to use Deque: Use when you know the maximum size in advance and want predictable memory usage.When to use DynamicDeque: Use when size varies significantly and you need automatic memory management.

Equality Comparison

Both deque types support equality comparison based on contents:
from dsa.deque import Deque, DynamicDeque

deque1 = Deque.from_list([1, 2, 3])
deque2 = DynamicDeque.from_list([1, 2, 3])
deque3 = Deque.from_list([1, 2, 4])

print(deque1 == deque2)  # True (same contents)
print(deque1 == deque3)  # False (different contents)

Best Practices

Check Before Pop

Always check if the deque is empty before popping:
if not deque.is_empty():
    value = deque.pop_left()

Use Peek for Inspection

Use peek_left() or peek_right() to inspect without removing:
if not deque.is_empty():
    front = deque.peek_left()
    back = deque.peek_right()

Choose Operations Wisely

Use appropriate end for your use case:
  • Stack behavior: use one end
  • Queue behavior: use both ends
  • Deque behavior: leverage both ends

Handle Exceptions

Wrap operations in try-except for robust code:
try:
    deque.append_right(value)
except Exception:
    # Handle full deque

Deque vs Stack vs Queue

Use Deque when:
  • You need access to both ends
  • You want to implement both stack and queue operations
  • You need flexibility in adding/removing elements
Example: Palindrome checking, sliding window algorithms

Build docs developers (and LLMs) love