Queue and Deque in Java: Interfaces and Core Methods (A Beginner’s Guide)

  • Last Updated: August 11, 2026
  • By: javahandson
  • Series
img

Queue and Deque in Java: Interfaces and Core Methods (A Beginner’s Guide)

Learn Queue and Deque in Java the easy way. Understand FIFO, core methods like offer, poll, and peek, two-ended Deque access, and when to use ArrayDeque.

1. Introduction

The Queue and Deque in Java are two collection types built around one idea: the order in which items get served. You already know a List keeps items in a row. You also know a Set drops duplicates. Now comes a fresh shape of collection.

The Queue and Deque in Java care about one thing above all: order of service. They decide who gets handled first, and who waits.

Think of a line at a ticket counter. The person who came first gets served first. Nobody jumps ahead. That simple rule sits at the heart of a queue.

A Deque takes that idea and stretches it. With a Deque, you can add or remove from both ends. So it works as a queue, a stack, or both at once.

In this guide, we start with the Queue interface. After that, we open up the Deque interface and its handy two-ended methods.

Here is what we will cover:

  • What a Queue is, and the FIFO rule behind it
  • The core Queue methods, and why they come in pairs
  • What a Deque is, and how it handles both ends
  • The full set of Deque methods, laid out clearly
  • How to use a Deque as a stack the right way
  • Common mistakes, plus a batch of interview questions

No prior queue knowledge is needed. If you can use an ArrayList, you are ready for this.

Queue and Deque in Java

2. What Is a Queue?

A Queue is a collection built around waiting in line. Items enter at one end and leave from the other. The first item in is the first item out.

We call this rule FIFO, short for First In, First Out. It matches how a real queue behaves every single day.

2.1 The FIFO Rule in Plain Words

Picture a queue at a coffee shop. You join at the back. You wait your turn. When you reach the front, you get served and leave.

A Java Queue works the same way. New items join the tail. Served items leave from the head. Order stays fair and predictable.

So where does a queue help in real code? Quite a few places, actually:

  • A print spooler that handles documents in the order they arrive
  • A task queue where jobs run one after another
  • A message buffer that holds events until a worker reads them
  • A breadth-first search that visits nodes level by level

In each case, order matters. You want the oldest item handled first. A queue gives you that for free.

2.2 Watching FIFO Order Play Out

Let us make the rule concrete with a tiny example. Three people join a line, one after another.

Queue<String> line = new LinkedList<>();
line.offer("Amy");   // joins the back
line.offer("Ben");   // joins behind Amy
line.offer("Cara");  // joins behind Ben
 
// served in the same order they arrived
System.out.println(line.poll()); // Amy
System.out.println(line.poll()); // Ben
System.out.println(line.poll()); // Cara

Amy came first, so Amy leaves first. Nobody skips the line. That predictable order is the whole promise of a queue.

2.3 Queue Is an Interface, Not a Class

Here is a point that trips up many beginners. You cannot write new Queue(). The Queue is an interface, so it only describes behaviour.

A concrete class does the actual work. Java hands you a few common choices:

  • LinkedList — a classic Queue, and it also works as a List.
  • ArrayDeque — a fast, array-backed choice, and the usual default today.
  • PriorityQueue — orders items by priority, not by arrival time.

Here is the usual way to declare one:

Queue<String> line = new LinkedList<>();
line.add("Amy");
line.add("Ben");
line.add("Cara");
 
System.out.println(line.poll()); // Amy leaves first
System.out.println(line);        // [Ben, Cara]

Notice the left side says Queue, not LinkedList. This is a good habit. You code to the interface, so swapping the class later stays painless.

2.4 When a Queue Is Not the Right Fit

A queue is great for order, but it is not a search tool. It does not let you jump to the middle or look up a value fast.

So skip a plain queue when you need any of these:

  • Random access by index — reach for a List instead.
  • Fast lookups by value — a HashSet or HashMap fits better.
  • Sorted output — a PriorityQueue or TreeSet does that job.

Pick the tool that matches your access pattern. A queue earns its place when you care about who goes first, not who sits where.

3. The Core Queue Methods

The Queue interface gives you a small, focused set of methods. The neat part is how they come in pairs. One pair throws on failure, the other returns a quiet signal.

3.1 Two Styles: Throwing vs Returning

Each core action has two versions. Both do the same job in normal cases. They differ only when something goes wrong, like an empty queue.

  • Throwing version — raises an exception on failure.
  • Returning version — hands back a special value like null or false instead.

Here is the full pairing, side by side:

Action Throws on Failure Returns Special Value
Insert add(e) offer(e)
Remove remove() poll()
Peek element() peek()

Read that table row by row. Insert has add and offer. Remove has remove and poll. Peek has element and peek. Learn the pairs, and the whole interface clicks.

3.2 Inserting: add() and offer()

Both methods put an item at the tail of the queue. In everyday use, they behave the same.

The gap shows up in a bounded queue, one with a size limit. When such a queue is full, add() throws an exception. But offer() simply returns false.

Queue<Integer> q = new LinkedList<>();
q.add(10);      // returns true
q.offer(20);    // returns true
System.out.println(q); // [10, 20]

For an unbounded queue like a plain LinkedList, either one is fine. Pick offer() when you expect limits and want to avoid exceptions.

3.3 Removing: remove() and poll()

Both methods take the item off the head of the queue. They also return that item to you.

The difference lands on an empty queue. Calling remove() throws a NoSuchElementException. Calling poll() just returns null.

Queue<String> q = new LinkedList<>();
q.offer("first");
q.offer("second");
 
String head = q.poll();  // "first"
System.out.println(head);
System.out.println(q);   // [second]
Interview Insight
Q: Why prefer poll() over remove() in a loop?
A: Because poll() returns null on an empty queue instead of throwing. That makes it clean to loop with while ((item = q.poll()) != null). You drain the queue without wrapping every call in a try-catch.

3.4 Peeking: element() and peek()

Sometimes you want to see the head without removing it. That is what peeking does.

Both methods return the head item and leave it in place. On an empty queue, element() throws, while peek() returns null.

Queue<String> q = new LinkedList<>();
q.offer("job-1");
 
String next = q.peek(); // "job-1", still in the queue
System.out.println(next);
System.out.println(q);  // [job-1]

Peeking is handy when you want to check the next item before you commit to processing it. You look first, then decide.

3.5 A Small Queue in Action

Let us put the pairs together in one flow. We build a queue, peek at the head, then drain it one item at a time.

Queue<String> q = new LinkedList<>();
q.offer("a");
q.offer("b");
q.offer("c");
 
System.out.println(q.peek()); // "a" — head, not removed
 
while (!q.isEmpty()) {
    System.out.println(q.poll()); // a, then b, then c
}
System.out.println(q.poll()); // null — queue is empty now

Watch the last line closely. Once the queue is empty, poll() hands back null. No exception, no crash. That quiet behaviour makes loops easy to write.

4. How Fast Are Queue Operations?

Speed matters when queues grow large. The good news is that the main operations are cheap for the common classes.

For both ArrayDeque and LinkedList, the core actions run in constant time. That means the cost stays the same whether you hold ten items or ten million.

  • offer / add at the tail — O(1), a quick drop into place.
  • poll / remove from the head — O(1), a quick lift off the front.
  • peek / element at the head — O(1), just a look.

An ArrayDeque may resize its backing array once in a while. That single resize is slower. But spread across many adds, the average stays constant. We call that amortized O(1).

Interview Insight
Q: Is ArrayDeque or LinkedList faster for a queue?
A: ArrayDeque usually wins. It stores items in one contiguous array, so the CPU cache loves it. A LinkedList scatters nodes across memory and stores extra pointers per node. Both are O(1) for queue operations, but ArrayDeque tends to run faster and use less memory in practice.

5. What Is a Deque?

A Deque is a double-ended queue. The name is short for Double-Ended Queue, and folks pronounce it “deck”.

A plain queue lets you add at one end and remove from the other. A Deque removes that limit. You can add or remove at both the head and the tail.

5.1 Two Ends, Full Freedom

Picture a deck of cards held in your hand. You can slide a card off the top. You can also slip one under the bottom. A Deque gives you that same freedom.

Because of this, a Deque can play two roles at once:

  • As a queue — add at the tail, remove from the head (FIFO).
  • As a stack — add and remove at the same end (LIFO).

So one interface covers two classic data structures. That flexibility makes Deque a favourite in modern Java code.

5.2 Deque Extends Queue

The Deque interface builds right on top of Queue. In code terms, Deque extends Queue. So every Queue method still works on a Deque.

On top of those, Deque adds its own two-ended methods. You get First and Last versions of each action, which we will see next.

The most common class here is ArrayDeque. It is fast, array-backed, and great as both a queue and a stack:

Deque<String> dq = new ArrayDeque<>();
dq.offerFirst("B");
dq.offerFirst("A"); // A goes to the front
dq.offerLast("C");  // C goes to the back
 
System.out.println(dq); // [A, B, C]

6. The Core Deque Methods

A Deque doubles the method count, but the pattern stays simple. Each action gets a First flavour and a Last flavour. And once again, each flavour has a throwing and a returning version.

6.1 The Full Method Map

Do not try to memorise this table. Just see the pattern. First works on the head, Last works on the tail.

Action Head — Throws Head — Returns Tail — Throws Tail — Returns
Insert addFirst(e) offerFirst(e) addLast(e) offerLast(e)
Remove removeFirst() pollFirst() removeLast() pollLast()
Peek getFirst() peekFirst() getLast() peekLast()

Read it as a grid. Pick your end, then pick your failure style. That is the whole idea behind the Deque method names.

6.2 Working Both Ends

Let us watch a Deque in action from both sides. We add and remove at the head and the tail.

Deque<Integer> dq = new ArrayDeque<>();
dq.offerLast(1);   // [1]
dq.offerLast(2);   // [1, 2]
dq.offerFirst(0);  // [0, 1, 2]
 
System.out.println(dq.pollFirst()); // 0
System.out.println(dq.pollLast());  // 2
System.out.println(dq);             // [1]

See how each call names its end? offerFirst touches the head. pollLast touches the tail. The names read almost like plain English.

6.3 Where a Deque Really Helps

Two-ended access is not just a party trick. Several real problems map onto it cleanly.

  • Undo and redo — push actions on one end, pop them off to reverse.
  • A browser history — go back and forward from either side.
  • A sliding window — add on one end, drop stale items from the other.
  • A work-stealing pool — a thread takes from its own end, others steal from the far end.

In each case, you need to touch both ends. A plain queue cannot do that. A Deque handles it in one clean interface.

7. Using a Deque as a Stack

You may have heard of the old Stack class in Java. It works, but the team now advises against it. A Deque is the modern replacement.

7.1 Why Not the Old Stack Class?

The legacy Stack extends Vector, so it carries synchronisation you rarely need. That extra locking slows things down for no gain.

The Java docs themselves point you to ArrayDeque for stack behaviour. It is faster and cleaner. So reach for a Deque instead.

7.2 The push, pop, and peek Trio

For stack use, Deque gives you three friendly method names. They all act on the head of the deque.

  • push(e) — add an item to the top (same as addFirst).
  • pop() — remove and return the top item (same as removeFirst).
  • peek() — look at the top item without removing it.

Here is a small stack in action. Notice the LIFO order: last in, first out.

Deque<String> stack = new ArrayDeque<>();
stack.push("page-1");
stack.push("page-2");
stack.push("page-3");
 
System.out.println(stack.pop()); // page-3 (last in, first out)
System.out.println(stack.pop()); // page-2
System.out.println(stack.peek()); // page-1, still there
Interview Insight
Q: Queue vs stack — how does a Deque switch between them?
A: It is all about which end you touch. Use offer and poll for FIFO queue behaviour. Use push and pop for LIFO stack behaviour. Same Deque, two mental models, no new class needed.

7.3 A Real Stack Example: Matching Brackets

Stacks show up in real problems all the time. A classic one is checking whether brackets in a string are balanced.

The idea is simple. Push every opening bracket. On a closing bracket, pop and check the pair. If anything mismatches, the string is not balanced.

boolean isBalanced(String s) {
    Deque<Character> stack = new ArrayDeque<>();
    for (char c : s.toCharArray()) {
        if (c == '(' || c == '[' || c == '{') {
            stack.push(c);            // opening bracket
        } else if (c == ')' || c == ']' || c == '}') {
            if (stack.isEmpty()) return false;
            char open = stack.pop();  // must match
            if (!matches(open, c)) return false;
        }
    }
    return stack.isEmpty();           // nothing left over
}

See how push and pop carry the whole logic? Each opening bracket waits on the stack. Each closing bracket checks the most recent one. That is LIFO doing real work.

8. Queue, Stack, and Deque at a Glance

Three words get thrown around a lot: queue, stack, and deque. They sound similar, so let us pin down the difference.

8.1 The One-Line Difference

It comes down to which end you use.

  • Queue — add at one end, remove from the other. First in, first out.
  • Stack — add and remove at the same end. Last in, first out.
  • Deque — do both. Add or remove at either end, whenever you like.

So a Deque is the general case. A queue and a stack are just two ways of using it. Once that clicks, the whole family feels simple.

8.2 A Quick Behaviour Table

Here is the same idea in a grid. Notice how the Deque row covers both others.

Type Order Add End Remove End
Queue FIFO Tail Head
Stack LIFO Head Head
Deque Both Either Either

8.3 How Iteration Reads Them

One small point about looping. When you iterate a Deque, it walks from head to tail by default.

For stack use, that can feel backwards. The head holds your most recent push, so iteration shows newest first. Keep that in mind when you print a Deque you are using as a stack.

9. Choosing the Right Implementation

You now know the interfaces. So which concrete class should you pick? It depends on what you need.

9.1 ArrayDeque: The Everyday Choice

For most queue and stack needs, ArrayDeque is the go-to. It backs itself with a resizable array, so access stays fast.

A quick heads-up, though. An ArrayDeque does not allow null elements. Add a null, and it throws right away. That is by design.

9.2 LinkedList: When You Also Need a List

A LinkedList implements both Deque and List. So you can treat it as a queue and index into it too.

That said, ArrayDeque usually wins on raw speed for pure queue work. Pick LinkedList only when you truly need List features as well.

9.3 PriorityQueue: When Order by Priority Matters

Sometimes arrival order is not what you want. You want the smallest or highest-priority item first. That is where PriorityQueue shines.

It does not follow strict FIFO. Instead, it serves items by their natural order or a custom comparator. We cover it in depth in its own article.

Here is a quick comparison to keep things straight:

Class Best For Watch Out For
ArrayDeque Fast queue or stack No null elements
LinkedList Queue plus List access Slower for pure queue use
PriorityQueue Priority-based order Not FIFO; no null

9.4 What About Thread Safety?

The classes above are not thread-safe. If two threads touch the same queue at once, you can hit trouble. So what do you use then?

Java has a whole family built for concurrent work. You will find them in the java.util.concurrent package.

  • ConcurrentLinkedQueue — a lock-free, unbounded FIFO queue.
  • LinkedBlockingQueue — a bounded queue that can block until space is free.
  • ArrayBlockingQueue — a fixed-size queue backed by an array.
  • ConcurrentLinkedDeque — a thread-safe, two-ended deque.

These shine in producer-consumer setups, where one thread adds jobs and another pulls them. We keep the deep dive for the concurrency series. For now, just know they exist.

10. Common Mistakes and Pitfalls

A handful of traps catch beginners over and over. Let us name them so you can steer clear.

10.1 Mixing Up the Method Pairs

Many folks call add() when they wanted offer(). The mix-up bites only on a full bounded queue, where add() throws. Know which style you need before you type it.

10.1.1 A Quick Rule of Thumb

If an empty or full case is normal in your flow, use the returning versions. So prefer offer, poll, and peek. They fail quietly with null or false.

10.2 Adding null to an ArrayDeque

As noted earlier, an ArrayDeque rejects null. New users try it and get a NullPointerException. If you must store nulls, a LinkedList allows them.

10.3 Assuming a Queue Sorts Items

A plain Queue does not sort anything. It keeps arrival order, plain and simple. If you expect sorted output, you want a PriorityQueue instead.

10.4 Reaching for the Legacy Stack

Old tutorials still show the Stack class. Skip it in new code. An ArrayDeque used with push and pop does the same job faster.

10.5 Confusing peek() with poll()

These two look similar, but they behave differently. peek() only looks at the head. poll() looks and removes.

Beginners sometimes call peek() in a loop and wonder why it never ends. The head never leaves, so the loop spins forever. Use poll() when you mean to drain the queue.

11. A Practical Walkthrough

Let us tie it together with a small, real task. Say you are building a simple task scheduler.

11.1 Queueing the Jobs

Jobs arrive over time, and you handle them in order. New jobs join the tail. You pull the next one from the head.

Queue<String> jobs = new ArrayDeque<>();
jobs.offer("send-email");
jobs.offer("build-report");
jobs.offer("backup-db");
 
while (!jobs.isEmpty()) {
    String job = jobs.poll();
    System.out.println("Running: " + job);
}
// Runs in arrival order: send-email, build-report, backup-db

Each poll() grabs the oldest job. The loop drains the queue in fair FIFO order. Clean and easy to reason about.

11.2 Adding an Urgent Job

Now a job needs to jump the line. With a Deque, you push it to the front instead of the back.

Deque<String> jobs = new ArrayDeque<>();
jobs.offerLast("build-report");
jobs.offerLast("backup-db");
 
jobs.offerFirst("urgent-patch"); // jumps to the head
System.out.println(jobs.pollFirst()); // urgent-patch

The urgent job lands at the head. Your next poll picks it up first. This is exactly why the two-ended Deque is so useful.

11.3 Checking Before You Process

Sometimes you want to peek before you commit. Say you only run a job if it is not a duplicate of the last one.

Deque<String> jobs = new ArrayDeque<>();
jobs.offer("build-report");
jobs.offer("build-report"); // a repeat slipped in
 
String last = null;
while (!jobs.isEmpty()) {
    String next = jobs.peek(); // look, do not remove yet
    if (next.equals(last)) {
        jobs.poll();           // skip the duplicate
        continue;
    }
    last = jobs.poll();
    System.out.println("Running: " + last);
}

The peek() lets you look ahead safely. You decide, then you poll. This look-then-act pattern comes up often in real queue code.

12. Interview Questions on Queue and Deque

These come up often in Java interviews. Short, honest answers work best. The full question set, formatted for the blog, is provided separately.

We have gathered the common ones into a dedicated FAQ block. Look for it below the article on the site.

Q: What is the difference between a Queue and a Deque in Java?

A: A Queue lets you add at one end and remove from the other, so it follows FIFO order. A Deque is a double-ended queue, so you can add or remove at both the head and the tail. That means a Deque can act as a queue or a stack, while a plain Queue cannot.

Q: What does FIFO mean in a queue?

A: FIFO stands for First In, First Out. The item added first is the item removed first, just like people waiting in a line. New items join the tail, and served items leave from the head.

Q: Why do Queue methods come in pairs like add/offer and remove/poll?

A: Each action has a throwing version and a returning version. The throwing version (add, remove, element) raises an exception on failure. The returning version (offer, poll, peek) hands back a quiet signal like false or null. Use the returning versions when an empty or full queue is a normal case.

Q: Should I use ArrayDeque or LinkedList for a queue?

A: ArrayDeque is usually the better choice. It stores items in one contiguous array, so it is cache-friendly and uses less memory. Pick LinkedList only when you also need List features such as index access. Both give O(1) queue operations.

Q: Why should I use a Deque instead of the old Stack class?

A: The legacy Stack extends Vector, so it carries synchronisation you rarely need, and that slows it down. The Java docs recommend ArrayDeque for stack behaviour. Use push, pop, and peek on a Deque for a faster, cleaner LIFO stack.

Q: Can an ArrayDeque hold null elements?

A: No. ArrayDeque rejects null and throws a NullPointerException if you try to add one. This is by design, because null is used as a “queue is empty” signal by poll and peek. If you must store nulls, use a LinkedList instead.

Q: How do you use a Deque as a stack in Java?

A: Use push to add to the head, pop to remove from the head, and peek to look at the head. All three act on the same end, which gives you LIFO order. For example, push(“a”), push(“b”), then pop() returns “b” first.

Q: Does a Queue sort its elements?

A: No. A plain Queue keeps arrival order and does not sort anything. If you need items served by priority or sorted order, use a PriorityQueue, which orders items by their natural order or a custom comparator.

Q: What is the time complexity of Queue operations?

A: For ArrayDeque and LinkedList, offer, poll, and peek all run in O(1) time. ArrayDeque may resize its backing array occasionally, which is slower, but averaged over many adds the cost stays constant, known as amortized O(1).

Q: Which Java classes implement the Queue and Deque interfaces?

A: LinkedList implements both Queue and Deque. ArrayDeque implements Deque and is the common default for queues and stacks. PriorityQueue implements Queue but orders items by priority rather than arrival, and it does not implement Deque.

13. Conclusion

Let us wrap up what we covered. A Queue models a waiting line, and it follows the FIFO rule.

Its core methods come in pairs. So add and offer insert, remove and poll take off the head, and element and peek look without removing.

A Deque widens the idea to both ends. You add or remove at the head or tail, which lets one interface act as both a queue and a stack.

For most work, reach for ArrayDeque. Pick LinkedList when you also need List access. And choose PriorityQueue when priority beats arrival order.

One last tip to carry with you. When an empty or full case is normal, lean on offer, poll, and peek. They fail quietly instead of throwing, which keeps your loops clean.

Master these two interfaces, and a big chunk of everyday Java gets easier. Scheduling, buffering, undo stacks, and graph traversal all lean on queues and deques under the hood.

14. Further Reading

Leave a Comment