Back to Blog
Java

Java ArrayDeque removeFirst and removeLast Usage

java arraydeque removefirst removelast: Use Java ArrayDeque removeFirst and removeLast to efficiently remove elements from both ends. Learn behavior, differences from...

ArrayDequeJava CollectionsremoveFirstremoveLastQueue OperationsDeque
Graphic representing ArrayDeque removal from both ends with arrows pointing inward and a Java code icon.

When you need a double-ended queue in Java, ArrayDeque is often the first choice. Its removeFirst() and removeLast() methods are the direct way to remove an element from the head or tail of the deque. Unlike pollFirst() and pollLast(), these methods throw an exception when the deque is empty. That distinction is the core of java arraydeque removefirst removelast usage and drives most of the practical decisions you will make when choosing between the remove and poll variants.

Understanding removeFirst() and removeLast()

ArrayDeque implements the Deque interface, which extends Queue. The removeFirst() method removes and returns the first element of the deque, and removeLast() removes and returns the last element. Both operate in constant time, O(1), because ArrayDeque is implemented as a resizable circular array. Unlike LinkedList, which allocates a separate node for each element, ArrayDeque stores elements in a contiguous array, so removal from either end does not involve shifting other elements.

Here is the method signature:

E removeFirst() E removeLast()

Both methods throw NoSuchElementException if the deque is empty. This is the primary difference from pollFirst() and pollLast(), which return null under the same condition. The semantics mirror the Queue interface's distinction between remove() and poll(): one fails fast, the other returns a sentinel value.

ArrayDeque<String> deque = new ArrayDeque<>(); deque.add("first"); deque.add("second"); String head = deque.removeFirst(); // returns "first" String tail = deque.removeLast(); // returns "second"

After those two calls, the deque becomes empty. The methods are useful when the code can guarantee that the deque is not empty at the point of removal, and an exception is an acceptable signal of a logic error.

When to Use removeFirst() vs pollFirst()

The choice between removeFirst() and pollFirst() comes down to how you want to handle an empty deque. Using removeFirst() makes the code fail immediately with NoSuchElementException if the deque is unexpectedly empty. That can be desirable in scenarios where an empty deque indicates a programming mistake or an invalid state that should not be silently ignored.

On the other hand, pollFirst() returns null, allowing the flow to continue and handle the absence of an element gracefully. This is common in producer-consumer patterns where the deque might be temporarily empty and you want to avoid throwing exceptions during normal operation.

The same reasoning applies to removeLast() versus pollLast(). If you are implementing a stack using ArrayDeque, you might use removeLast() to pop an element, but you should only do so when you know the stack is non-empty. Otherwise, pollLast() would be a safer choice.

MethodReturns when emptyThrows when emptyUse case
removeFirst()YesNon-empty deque, fail-fast
pollFirst()nullNoPossible empty deque, graceful handling
removeLast()YesNon-empty deque, fail-fast
pollLast()nullNoPossible empty deque, graceful handling

Do not assume that removeFirst() is always preferable. In a multithreaded environment, if another thread can concurrently remove elements, a removeFirst() call might throw an unexpected exception. Even though ArrayDeque is not thread-safe, the decision about which method to use is independent of that limitation.

Practical Example: Implementing a Sliding Window

A common use case for removeFirst() is a sliding window over a sequence of values. Suppose you need to track the maximum value in a sliding window of size k. You can use an ArrayDeque to store indices of candidate elements. When the window slides, you need to remove elements that fall out of the window from the front of the deque. If the deque is guaranteed to contain at least one element that is out of the window, removeFirst() is appropriate.

int[] nums = {1, 3, -1, -3, 5, 3, 6, 7}; int k = 3; ArrayDeque<Integer> deque = new ArrayDeque<>(); List<Integer> maxes = new ArrayList<>(); for (int i = 0; i < nums.length; i++) { // Remove indices that are out of the current window while (!deque.isEmpty() && deque.peekFirst() <= i - k) { deque.removeFirst(); } // Maintain decreasing order in the deque while (!deque.isEmpty() && nums[deque.peekLast()] <= nums[i]) { deque.removeLast(); } deque.addLast(i); if (i >= k - 1) { maxes.add(nums[deque.peekFirst()]); } } // maxes contains [3, 3, 5, 5, 6, 7]

In this example, the inner while loops call removeFirst() and removeLast() only when the deque is not empty, so they will never throw an exception. The algorithm correctly removes out-of-window indices from the front and removes smaller values from the back to maintain a monotonic deque.

Empty Deque Behavior and Error Handling

The most critical detail about removeFirst() and removeLast() is their behavior on an empty deque. Both throw NoSuchElementException, which is a runtime exception, so no checked exception handling is required. However, if you call removeFirst() on an empty deque in a production system, it can cause the current thread to fail abruptly unless you catch the exception.

ArrayDeque<String> deque = new ArrayDeque<>(); try { deque.removeFirst(); } catch (NoSuchElementException e) { System.err.println("Attempted to remove from an empty deque"); }

Catching NoSuchElementException is appropriate when the empty state is a potential but exceptional circumstance. But if the empty state is a normal part of the flow, you should use pollFirst() to avoid exception overhead. Creating and throwing an exception carries a cost, so performance-sensitive code should not use removeFirst() as a routine control-flow mechanism.

Performance and Memory Characteristics

ArrayDeque offers O(1) amortized time for removeFirst() and removeLast(). The removal operation itself does not require moving elements because the deque uses head and tail pointers into a circular array. Removing the first element simply increments the head pointer, and removing the last element decrements the tail pointer. The removed element's reference is set to null to help garbage collection, but that is an internal detail.

In contrast, LinkedList also provides O(1) removal from either end, but each node is a separate object, which increases memory usage and may lead to more cache misses. ArrayDeque avoids per-element object overhead, making it more cache-friendly. However, ArrayDeque does not allow null elements because null is used by pollFirst() and pollLast() as a sentinel for an empty deque. If your data can contain null, ArrayDeque is not a suitable container.

Memory-wise, ArrayDeque starts with a small internal array (default capacity is 16) and grows by doubling when full. When you remove elements repeatedly, the array does not shrink automatically. If you remove a large portion of the deque and keep it alive for a long time, you may retain unused capacity. That is a tradeoff: ArrayDeque favors fast operations over memory compactness.

Compatibility and Thread Safety Considerations

ArrayDeque is not thread-safe. If multiple threads access the deque concurrently without external synchronization, removeFirst() and removeLast() can produce inconsistent states. The methods themselves are not atomic. If you need thread safety, consider ConcurrentLinkedDeque or wrap the ArrayDeque with Collections.synchronizedDeque(). That choice affects how you handle empty-deque conditions as well, because concurrent removal could make the deque empty between a check and a removal call.

Another compatibility consideration is that ArrayDeque has been available since Java 6, so you can use it in most enterprise environments. In Java 21, ArrayDeque implements SequencedCollection, so it also has getFirst(), getLast(), and reverse-order methods. However, the behavior of removeFirst() and removeLast() remains unchanged.

When using removeFirst() in a loop, always guard with an emptiness check if the deque can become empty. For example, a consumer thread that processes tasks from a shared ArrayDeque should not call removeFirst() directly; it should use pollFirst() and handle the null result. Otherwise, the consumer may crash on an empty deque even if the producer is still adding tasks.

// Better consumer pattern with pollFirst Runnable task; while ((task = tasks.pollFirst()) != null) { task.run(); }

This pattern is safer and more performant than a try-catch around removeFirst(). The choice between removeFirst() and pollFirst() ultimately comes down to whether the absence of an element is an error condition or a normal case. java arraydeque removefirst removelast offers a fail-fast API, and it is your responsibility to invoke it only when you can guarantee the deque is not empty.

java arraydeque removefirst removelast: Practical Usage and | RYUSLOG DEV