Learn Java

Lesson 38Phase 4: Collections and algorithms

Stacks, queues and deques

Last in first out, first in first out, and both ends at once, with ArrayDeque and PriorityQueue and the problems each one solves.

By Updated 45 min

  • stack
  • queue
  • deque
  • ArrayDeque
  • push
  • pop

Some programs care most about which item comes out next. Press undo in an editor and your most recent change is the first to go. Send three documents to a printer and the one you sent first prints first. In a hospital emergency room, the most urgent patient is seen next, even one who arrived a minute ago. Java has a collection for each of these rules: a stack, a queue and a priority queue, plus the deque, which can work as a stack or as a queue. This lesson puts each one to work on the problems it suits.

By the end of this lesson
  • Use an ArrayDeque as a stack with push, pop and peek, and explain last in, first out (LIFO).
  • Use it as a queue with offer, poll and peek, and say when add, remove and element throw an exception instead.
  • Work at both ends of a Deque with addFirst, addLast, pollFirst and pollLast.
  • Make a PriorityQueue hand out the smallest, the largest or the shortest job first, and explain why printing one does not show sorted order.
  • Use these collections for undo, matching brackets, reversing, a palindrome check, a print queue and a top three.
  • Say roughly what each operation costs, and why ArrayDeque replaced the old Stack class.
From before
The Collections Framework introduced the Queue interface on the café line: offer adds at the back, peek looks at the front and poll takes the front element out, first in, first out. It also named Deque, said “deck”. Designing classes well told the story of Stack, the old class that extends Vector. From Comparable, Comparator and sorting you know Comparator.comparing and reversed(), which a priority queue uses to decide its order. The print jobs on this page are records, and wrapper classes explain why a collection of characters is a Deque<Character>.

A stack: last in, first out#

Picture a pile of plates in a kitchen cupboard. You put a clean plate on top of the pile, and when you need one you take the top plate. The plate you put down last is the first one you pick up, and the bottom plate stays where it is until every plate above it has gone.

A stack is a collection that follows the same rule. You add at the top and take from the top, and the elements underneath wait their turn. The rule is called last in, first out, or LIFO. The three stack operations have short names:

  • push(x) puts x on top of the stack.
  • pop() takes the top element off and returns it.
  • peek() returns the top element without taking it off.

In Java you make a stack with the Deque interface and its ArrayDeque class, both in java.util. An undo history is the classic stack: every action is pushed as you make it, and undo pops the newest one.

Undo.java
1import java.util.ArrayDeque;
2import java.util.Deque;
3
4public class Main {
5 public static void main(String[] args) {
6 Deque<String> history = new ArrayDeque<>();
7 history.push("type Hello");
8 history.push("make it bold");
9 history.push("type world");
10 System.out.println("History: " + history);
11 System.out.println("Last action: " + history.peek());
12
13 undo(history);
14 undo(history);
15 System.out.println("History: " + history);
16 undo(history);
17 undo(history);
18 }
19
20 static void undo(Deque<String> history) {
21 if (history.isEmpty()) {
22 System.out.println("Nothing to undo");
23 } else {
24 System.out.println("Undo: " + history.pop());
25 }
26 }
27}
Prints
›History: [type world, make it bold, type Hello]
›Last action: type world
›Undo: type world
›Undo: make it bold
›History: [type Hello]
›Undo: type Hello
›Nothing to undo

The first line prints History: [type world, make it bold, type Hello]. A deque prints from its front to its back, and a stack made from a deque keeps its top at the front, so the newest action comes first. peek() reports Last action: type world and leaves it in place. The next two calls undo type world and then make it bold, which leaves History: [type Hello]. The third call undoes type Hello, and the fourth finds the history empty and prints Nothing to undo.

Step through it and watch the memory view under the code. It draws the deque with its front on the left, so each push adds a cell on the left and each pop takes one from there.

Popping an empty stack stops the program
On an empty ArrayDeque, pop() throws a NoSuchElementException, while peek() returns null. That is why undo asks isEmpty() before it pops.
Key idea

A stack hands back the newest element first. On a Deque, push adds to the top, pop takes the top element off, and peek looks at it without taking it.

Predict the outputPile.java
1import java.util.ArrayDeque;
2import java.util.Deque;
3
4public class Main {
5 public static void main(String[] args) {
6 Deque<Integer> stack = new ArrayDeque<>();
7 stack.push(1);
8 stack.push(2);
9 stack.push(3);
10 System.out.println(stack.pop());
11 stack.push(4);
12 System.out.println(stack.pop() + " " + stack.peek());
13 System.out.println(stack);
14 }
15}

Pushes, pops and a peek on one stack. What does the program print?

It prints 3, then 4 2, then [2, 1]. The first pop takes 3, the newest. Then 4 is pushed and popped straight off again, which leaves 2 on top for peek(). The last line lists what remains, top first.

Why not the Stack class?#

Java also has a class called Stack, and many people reach for it first. It dates from Java 1.0, before the Collections Framework existed, and its own documentation now recommends Deque and its classes instead. You saw the main reason in designing classes well: Stack extends Vector, an old list class, so every Stack is also a list. This program puts the same three plates on both kinds of stack:

Plates.java
1import java.util.ArrayDeque;
2import java.util.Deque;
3import java.util.Stack;
4
5public class Main {
6 public static void main(String[] args) {
7 Stack<String> old = new Stack<>();
8 Deque<String> plates = new ArrayDeque<>();
9 for (String plate : new String[] {"red", "green", "blue"}) {
10 old.push(plate);
11 plates.push(plate);
12 }
13 System.out.println(old + " top: " + old.peek());
14 System.out.println(plates + " top: " + plates.peek());
15
16 old.add(0, "gold");
17 System.out.println(old + " bottom: " + old.get(0));
18 }
19}
Prints
›[red, green, blue] top: blue
›[blue, green, red] top: blue
›[gold, red, green, blue] bottom: gold

Both stacks have blue on top, but they print in opposite orders: [red, green, blue] for the Stack, bottom first, and [blue, green, red] for the deque, top first. Then old.add(0, "gold"), a list method, slides a gold plate in under all the others. A stack should never allow that, but a Stack is a Vector, so it cannot refuse.

Stack
ArrayDeque used as a stack
In Java since 1.0. It extends Vector.
In Java since 6. It implements Deque.
Also a list: get(i), add(i, x) and remove(i) reach into the middle.
Only its two ends can be reached.
Its methods take a lock so that several threads can share it safely. Most programs never need that.
No locks. Its documentation says it is likely to be faster than Stack.
Prints bottom first.
Prints top first.
pop() on an empty stack throws EmptyStackException.
pop() on an empty deque throws NoSuchElementException.
Accepts null as an element.
Refuses null with a NullPointerException.

What a stack is good for#

Undo is one job for a stack. A stack suits any problem where the most recent item has to be dealt with first, or where items have to come back in reverse order.

Matching brackets#

Code is full of brackets that come in pairs: every ( needs a ), every [ a ] and every { a }. They must also close in the reverse of the order they opened. (a[i] + b) * 2 is fine. (a[i) + b] is not, because the ) arrives while the [ is still open. Code editors check this as you type, and with a stack you can write the check yourself:

  1. 1Read the text one character at a time.
  2. 2Push every opening bracket.
  3. 3At a closing bracket, pop the newest opening bracket. If there was none, or the two do not belong together, the text is unbalanced.
  4. 4At the end, the text is balanced only if the stack is empty. Anything left on it was opened and never closed.
Brackets.java
1import java.util.ArrayDeque;
2import java.util.Deque;
3
4public class Main {
5 public static void main(String[] args) {
6 System.out.println(balanced("(a[i] + b) * 2"));
7 System.out.println(balanced("(a[i) + b]"));
8 System.out.println(balanced("((x)"));
9 }
10
11 static boolean balanced(String text) {
12 Deque<Character> open = new ArrayDeque<>();
13 for (char c : text.toCharArray()) {
14 if (c == '(' || c == '[' || c == '{') {
15 open.push(c);
16 } else if (c == ')' || c == ']' || c == '}') {
17 if (open.isEmpty() || !pair(open.pop(), c)) {
18 return false;
19 }
20 }
21 }
22 return open.isEmpty();
23 }
24
25 static boolean pair(char opening, char closing) {
26 return (opening == '(' && closing == ')')
27 || (opening == '[' && closing == ']')
28 || (opening == '{' && closing == '}');
29 }
30}
Prints
›true
›false
›false

It prints true, false and false. Step through the second call: ( and [ are pushed, then the ) pops [, and pair says those two do not belong together. In the third call each ) finds an opening bracket to pop, but one ( is still on the stack when the text runs out.

Two details make the code work. The stack is a Deque<Character> because a collection holds objects: push(c) boxes each char into a Character, and passing open.pop() to pair, which takes a char, unboxes it again. And in open.isEmpty() || !pair(open.pop(), c), the || only looks at its right side when the left side is false (the short-circuit rule from operators), so pop() never runs on an empty stack.

Reversing#

A stack gives elements back in the opposite order, so pushing every item and then popping them all reverses them. Push one, two, three and four, and four ends up on top, so the pops come out four, three, two, one. The same trick reverses the letters of a word or the stops of a bus route for the journey home.

Put the lines in orderReverse.java
Put the lines in order so the program pushes each word onto a stack, then pops them all and prints four three two one.

Tap a line, then tap where it should go. Or use the arrows.

The back button#

A web browser keeps a stack of the pages behind you. Opening a new page pushes the page you are leaving, and Back pops the most recent one and takes you there. Pressing Back twice takes you two pages back, in the reverse of the order you visited them.

ExerciseBrowser.java
Finish the loop so it plays the clicks like a browser. A page name opens that page; BACK returns to the previous page. When there is nothing to go back to, print Nothing to go back to and stay on the current page.

A queue: first in, first out#

A queue is the line at a counter. People join at the back and are served from the front, so whoever has waited longest goes next. You met it in the collections overview: offer adds at the back, peek looks at the front, and poll takes the front element out. The rule is first in, first out, or FIFO, the opposite of a stack.

A printer shared by an office works this way. Here each job is a record holding who sent it and how many pages it has, and the loop plays the printer, which prints one page a minute:

PrintQueue.java
1import java.util.ArrayDeque;
2import java.util.Queue;
3
4public class Main {
5 public static void main(String[] args) {
6 Queue<PrintJob> printer = new ArrayDeque<>();
7 printer.offer(new PrintJob("Ines", 30));
8 printer.offer(new PrintJob("Tom", 2));
9 printer.offer(new PrintJob("Ravi", 5));
10 System.out.println(printer.size() + " jobs waiting, next: " + printer.peek());
11
12 int minute = 0;
13 while (!printer.isEmpty()) {
14 PrintJob job = printer.poll();
15 System.out.println("Minute " + minute + ": " + job.pages() + " pages for " + job.owner());
16 minute += job.pages();
17 }
18 System.out.println("All done at minute " + minute);
19 }
20}
21
22record PrintJob(String owner, int pages) {}
Prints
›3 jobs waiting, next: PrintJob[owner=Ines, pages=30]
›Minute 0: 30 pages for Ines
›Minute 30: 2 pages for Tom
›Minute 32: 5 pages for Ravi
›All done at minute 37

It prints 3 jobs waiting, next: PrintJob[owner=Ines, pages=30], using the toStringthat every record gets for free. Then the jobs come out in the order they were sent: Ines's 30 pages at minute 0, Tom's 2 pages at minute 30 and Ravi's 5 pages at minute 32, then All done at minute 37. A while (!printer.isEmpty()) loop with poll() inside is the usual way to work through a queue until nothing is left. Tom waited half an hour for two pages; a priority queue fixes that later on this page.

Two ways to ask: an exception or a special value#

Queue has a second set of methods for the same three jobs. While the queue has elements, the two sets behave the same. They differ when the job cannot be done:

JobThrows an exception if it cannotReturns a special value if it cannot
Add an element at the backadd(e)offer(e) returns false
Take the front element outremove()poll() returns null
Look at the front elementelement()peek() returns null
The six Queue methods, grouped as the Java SE 25 API documentation groups them.
TwoWays.java
1import java.util.ArrayDeque;
2import java.util.Queue;
3
4public class Main {
5 public static void main(String[] args) {
6 Queue<String> jobs = new ArrayDeque<>();
7 System.out.println(jobs.offer("essay.pdf") + " " + jobs.add("photo.png"));
8 System.out.println(jobs.peek() + " " + jobs.element());
9 System.out.println(jobs.poll() + " " + jobs.remove());
10
11 System.out.println(jobs.poll() + " " + jobs.peek());
12 System.out.println(jobs.element());
13 }
14}
Prints
›true true
›essay.pdf essay.pdf
›essay.pdf photo.png
›null null
!Exception in thread "main" java.util.NoSuchElementException
! at Main.main(Main.java:12)
Stops on line 12

NoSuchElementException

There was nothing left to take.

The first three lines show each pair agreeing: both adds return true, peek and element both see essay.pdf, and poll and remove each take one job out. With the queue empty, they part ways. poll() and peek() return null, so the fourth line is null null, and element() throws a NoSuchElementException that stops the program on the last line. remove() would throw the same exception.

For adding, the difference only shows in a bounded queue, one with a fixed limit on its size. When it is full, add throws an IllegalStateException and offer returns false. An ArrayDeque grows whenever it needs room, so on an ArrayDeque both always succeed. Bounded queues turn up mostly in programs that run several threads at once, a topic that starts with introduction to threads.

Pick by what an empty queue means
If an empty queue is normal, like a printer waiting for its next job, use poll and peek and check for null, or ask isEmpty() first. If an empty queue would mean a mistake in your program, remove and element stop the program at the line where the mistake shows, which makes it easier to find.
Quick check
A help desk program hands the next ticket in a Queue<String> to whichever agent becomes free. Most of the time there are no tickets, and then the agent waits. Which call should it use?
Why not an ArrayList?
You could keep a line in an ArrayList, adding at the end and serving from the front with remove(0). But each remove(0) shifts every remaining element one place to the left, as you saw in the ArrayList lesson, so the longer the line, the slower each serve. An ArrayDeque takes from the front without moving anything; the section on costs shows how.

A deque: both ends at once#

Music apps often give you two choices for a song: play it next, which puts it at the front of what is coming up, or add it to the queue, which puts it at the end. The list of songs coming up needs both ends. That is a deque, short for double-ended queue and said “deck”: a collection you can add to and take from at the front and at the back.

The Deque interface has twelve methods for its two ends. They come in the same two kinds as the queue methods: one throws when it cannot do its job, the other returns a special value.

Front: throwsFront: special valueBack: throwsBack: special value
AddaddFirst(e)offerFirst(e)addLast(e)offerLast(e)
Take outremoveFirst()pollFirst()removeLast()pollLast()
LookgetFirst()peekFirst()getLast()peekLast()
The twelve Deque methods, as the Java SE 25 API documentation lays them out. The special value is false for offerFirst and offerLast, and null for the rest.

The stack and queue methods you have used are these same methods under shorter names. push is addFirst, pop is removeFirst and peek is peekFirst, so a stack lives at the front. offer is offerLast and poll is pollFirst, so a queue joins at the back and leaves from the front. That is how one class, ArrayDeque, does both jobs.

UpNext.java
1import java.util.ArrayDeque;
2import java.util.Deque;
3
4public class Main {
5 public static void main(String[] args) {
6 Deque<String> upNext = new ArrayDeque<>();
7 upNext.addLast("Blue Hour");
8 upNext.addLast("Paper Boats");
9 upNext.addFirst("Night Drive");
10 System.out.println(upNext);
11
12 System.out.println("Now playing: " + upNext.pollFirst());
13 System.out.println("Removed from the end: " + upNext.pollLast());
14 System.out.println(upNext + " " + upNext.peekFirst() + " " + upNext.peekLast());
15
16 upNext.pollFirst();
17 System.out.println(upNext.pollFirst() + " " + upNext.pollLast());
18 }
19}
Prints
›[Night Drive, Blue Hour, Paper Boats]
›Now playing: Night Drive
›Removed from the end: Paper Boats
›[Blue Hour] Blue Hour Blue Hour
›null null

It prints [Night Drive, Blue Hour, Paper Boats]. Night Drive was added last, but at the front, so it plays first. pollLast then removes Paper Boats from the end, and the one song left is both the first and the last: [Blue Hour] Blue Hour Blue Hour. After one more pollFirst the deque is empty, so the last line prints null null.

Quick check
A program runs d.push(1); d.push(2); d.offer(3); on an empty Deque<Integer>. What does d.pop() return next?

A palindrome check#

A palindrome reads the same forwards and backwards. In string practice you checked one with two index variables walking in from the ends. A deque does the same job without any index arithmetic: put the letters in, then take one from each end and compare, until one letter or none is left.

Palindrome.java
1import java.util.ArrayDeque;
2import java.util.Deque;
3
4public class Main {
5 public static void main(String[] args) {
6 System.out.println(isPalindrome("Never odd or even"));
7 System.out.println(isPalindrome("Java"));
8 }
9
10 static boolean isPalindrome(String phrase) {
11 Deque<Character> letters = new ArrayDeque<>();
12 for (char c : phrase.toLowerCase().toCharArray()) {
13 if (Character.isLetter(c)) {
14 letters.addLast(c);
15 }
16 }
17 while (letters.size() > 1) {
18 char first = letters.pollFirst();
19 char last = letters.pollLast();
20 if (first != last) {
21 return false;
22 }
23 }
24 return true;
25 }
26}
Prints
›true
›false

It prints true for “Never odd or even” and falsefor “Java”. The first loop keeps only the letters, in lower case, so the spaces and the capital N do not spoil the check. Step through the second loop and watch the deque shrink from both ends in the memory view. char first = letters.pollFirst(); unboxes the Character that pollFirst returns. That is safe here because the loop only runs while two or more letters are left. On an empty deque pollFirst returns null, and unboxing null throws a NullPointerException.

Key idea

A deque adds and removes at both ends. Use only its front and it is a stack. Add at the back and take from the front and it is a queue.

PriorityQueue: the smallest comes out first#

Think back to the emergency room. Patients are not seen in the order they arrive: each one is checked for how urgent the case is, and the most urgent goes next. A priority queue works like that. Every pollhands out the element that comes first in some order, whatever order the elements went in. Java's PriorityQueue hands out the smallest element first.

Here the elements are the days left before each of five homework deadlines, and the queue answers the question that matters: which one is due soonest?

Deadlines.java
1import java.util.PriorityQueue;
2import java.util.Queue;
3
4public class Main {
5 public static void main(String[] args) {
6 Queue<Integer> daysLeft = new PriorityQueue<>();
7 daysLeft.offer(5);
8 daysLeft.offer(1);
9 daysLeft.offer(8);
10 daysLeft.offer(3);
11 daysLeft.offer(2);
12 System.out.println("Most urgent: " + daysLeft.peek());
13
14 while (!daysLeft.isEmpty()) {
15 System.out.print(daysLeft.poll() + " ");
16 }
17 System.out.println();
18 }
19}
Prints
›Most urgent: 1
›1 2 3 5 8

It prints Most urgent: 1 and then 1 2 3 5 8, sorted, though the numbers went in as 5, 1, 8, 3 and 2. peek() shows the smallest element without removing it, and each poll()removes the smallest one left. “Smallest” means first in the elements' natural order, the one their compareTo method defines. A PriorityQueue is a Queue, so offer, poll and peek mean what they always mean. Only the rule for which element waits at the front has changed.

Predict the outputPrinted.java
1import java.util.PriorityQueue;
2
3public class Main {
4 public static void main(String[] args) {
5 PriorityQueue<Integer> queue = new PriorityQueue<>();
6 queue.offer(5);
7 queue.offer(1);
8 queue.offer(8);
9 queue.offer(3);
10 queue.offer(2);
11 System.out.println(queue);
12
13 while (!queue.isEmpty()) {
14 System.out.print(queue.poll() + " ");
15 }
16 }
17}

The same five numbers, but now the program prints the whole queue before it polls. Which output is right?

The first line is [1, 2, 8, 5, 3], neither sorted nor in the order the numbers went in, and yet the polls still come out sorted. Both facts come from the way a PriorityQueue stores its elements.

Inside a PriorityQueue: the heap#

Keeping the elements fully sorted would mean shifting many of them on almost every offer. A PriorityQueue does much less work: it keeps its elements in an arrangement called a binary heap. Picture a tree drawn upside down. One element sits at the top, the root. Below each element hang at most two more, its children, and the element above a child is its parent. The heap keeps one rule: no element is larger than its children. Follow any path down from the root and the numbers never get smaller, so the root is always the smallest of all.

Java keeps this tree in an ordinary array, filled row by row: the root at index 0, its two children at 1 and 2, their children at 3, 4, 5 and 6, and so on. Simple arithmetic finds the neighbours of the element at index i:

  • its children are at 2 * i + 1 and 2 * i + 2;
  • its parent is at (i - 1) / 2, in Java's whole-number division, so the parent of index 4 is (4 - 1) / 2, which is 1.

Printing a PriorityQueue, or walking it with a for-each loop, goes through that array from index 0 to the end. So what you see is the heap, which promises only that the first element is the smallest. The memory view under Deadlines.java draws the same array. Here is the queue from the Predict as a tree and as its array. Offer and poll some numbers and watch every step:

Heap labPriorityQueue<Integer> · size 5
0123412853

The same heap as Java's array, with each index under its slot

1
2
8
5
3

The queue after offer(5), offer(1), offer(8), offer(3) and offer(2). Java prints it as [1, 2, 8, 5, 3]. Offer a number or poll one, and watch each step.

the number being movedthe number it is compared withSmall grey numbers are array indexes.
System.out.println(queue) prints
[1, 2, 8, 5, 3]
peek() returns
1
poll() has returned, in order
nothing yet

Offering: add at the end, then sift up#

  1. 1The new element goes into the next free slot of the array. In the tree, that is the next free place in the bottom row, which fills from left to right.
  2. 2If it is smaller than its parent, the two swap places. Then it is compared with its new parent in the same way.
  3. 3It stops when its parent is smaller or equal, or when it reaches the root.

Moving an element up like this is called sifting up. Offer 0 in the lab and watch it climb to the root. Each swap lifts it one row, and a heap has few rows: 10 rows hold a thousand elements, and 20 rows hold a million. So even in a queue of a million, an offer makes at most 19 swaps.

Polling: take the root, then sift down#

  1. 1The root, at index 0, is the smallest element. It comes out, and it is what poll returns.
  2. 2The last element of the array moves into the empty root.
  3. 3It is compared with the smaller of its children. If it is larger, the two swap places, and the same happens one row lower.
  4. 4It stops when it is no larger than its children, or when it has no children.

This is sifting down. Poll the lab's queue until it is empty and read the bottom line: the numbers come out in sorted order, though the array is only ever kept as a heap.

Key idea

A PriorityQueue keeps a heap, not a sorted list. The smallest element is always at index 0, so peek and poll give the right answer, but printing the queue or looping over it shows the array in heap order.

Want every element in order?
Poll until the queue is empty, as Deadlines.java does. That empties the queue, so if you still need it, poll a copy instead: new PriorityQueue<>(daysLeft) makes one with the same elements, ordered the same way.

Other orders with a Comparator#

The natural order is not always the one you want. Pass a Comparator to the constructor and the queue hands out whatever comes first by that comparator. These are the comparators from Comparable, Comparator and sorting.

Back to the printer, where Tom's two pages waited half an hour behind Ines's thirty. If the printer always takes the shortest job waiting, short jobs stop getting stuck behind long ones. Only the line that creates the queue changes:

ShortestFirst.java
1import java.util.Comparator;
2import java.util.PriorityQueue;
3import java.util.Queue;
4
5public class Main {
6 public static void main(String[] args) {
7 Queue<PrintJob> printer = new PriorityQueue<>(Comparator.comparing(PrintJob::pages));
8 printer.offer(new PrintJob("Ines", 30));
9 printer.offer(new PrintJob("Tom", 2));
10 printer.offer(new PrintJob("Ravi", 5));
11 System.out.println(printer.size() + " jobs waiting, next: " + printer.peek());
12
13 int minute = 0;
14 while (!printer.isEmpty()) {
15 PrintJob job = printer.poll();
16 System.out.println("Minute " + minute + ": " + job.pages() + " pages for " + job.owner());
17 minute += job.pages();
18 }
19 System.out.println("All done at minute " + minute);
20 }
21}
22
23record PrintJob(String owner, int pages) {}
Prints
›3 jobs waiting, next: PrintJob[owner=Tom, pages=2]
›Minute 0: 2 pages for Tom
›Minute 2: 5 pages for Ravi
›Minute 7: 30 pages for Ines
›All done at minute 37

Now Tom's job prints at minute 0 and Ravi's at minute 2, and Ines's pages still finish at minute 37. Before, the three jobs spent 62 minutes waiting between them; now they spend 9. The loop did not change at all, because printer was declared as a Queue and both classes keep that promise.

For the opposite of the natural order, Comparator.reverseOrder() is a ready-made comparator, and reversed() turns around any comparator you build:

LargestFirst.java
1import java.util.Comparator;
2import java.util.PriorityQueue;
3
4public class Main {
5 public static void main(String[] args) {
6 PriorityQueue<Integer> highestFirst = new PriorityQueue<>(Comparator.reverseOrder());
7 for (int score : new int[] {72, 95, 61, 88}) {
8 highestFirst.offer(score);
9 }
10 System.out.println(highestFirst.poll() + " " + highestFirst.poll());
11
12 PriorityQueue<PrintJob> biggestFirst = new PriorityQueue<>(Comparator.comparing(PrintJob::pages).reversed());
13 biggestFirst.offer(new PrintJob("Ines", 30));
14 biggestFirst.offer(new PrintJob("Tom", 2));
15 biggestFirst.offer(new PrintJob("Ravi", 5));
16 System.out.println(biggestFirst.poll().owner() + " " + biggestFirst.poll().owner());
17 }
18}
19
20record PrintJob(String owner, int pages) {}
Prints
›95 88
›Ines Ravi

It prints 95 88, the two highest scores, and Ines Ravi, the owners of the two biggest jobs.

Ties come out in any order
When two elements are equal by the comparator, the documentation says the tie is broken arbitrarily: the one added first is not promised to come out first. If the order of ties matters, add a tie-breaker with thenComparing, as in Comparator.comparing(PrintJob::pages).thenComparing(PrintJob::owner). To serve equal jobs in the order they arrived, give each job an arrival number and compare by it last.
No comparator means natural order
A PriorityQueue made without a comparator calls compareTo on its elements. A record such as PrintJob does not implement Comparable, so offering one to new PriorityQueue<PrintJob>() throws a ClassCastException at the first offer. Pass a comparator, or have the class implement Comparable.

A priority queue can also pick the largest few items out of a long list without sorting all of it. Keep a queue of at most three scores: offer each score, and whenever the queue holds four, poll the smallest. The weakest of the best three always waits at the front, ready to be dropped.

ExerciseTopThree.java
Fill in the loop so best never holds more than three scores, and ends up holding the three highest. The last loop then prints them from lowest to highest: 88, 95 and 99.

Costs, and choosing between them#

Once a collection holds thousands of elements, the work each operation does starts to matter. Here is that work, counted in steps rather than seconds:

OperationWork doneAs the collection grows
ArrayDeque: push, pop, offer, poll, peek and the First and Last methodsReads or writes one slot at one end. Nothing else moves.Stays the same
ArrayDeque: contains(x), remove(x)Checks the elements one by one.Grows in step with the size
PriorityQueue: peek, sizeReads index 0, or a count it keeps.Stays the same
PriorityQueue: offer, pollSifts one element up or down, at most one swap per row.Grows with the number of rows: about 20 for a million elements
PriorityQueue: contains(x), remove(x)Checks the array one by one, since a heap is not sorted.Grows in step with the size
ArrayList as a queue: remove(0)Shifts every other element one place left.Grows in step with the size
Based on the ArrayDeque and PriorityQueue descriptions in the Java SE 25 API documentation.

One detail hides in the first row. Now and then an ArrayDeque's array fills up, and it copies every element into a bigger array, as an ArrayList does. That happens rarely enough that, averaged over many operations, the cost of each one stays the same. The documentation calls this amortized constant time.

Big-O gives these costs names
“Stays the same”, “grows with the number of rows” and “grows in step with the size” have short names: O(1), O(log n) and O(n). How fast is it? Big-O in plain words explains them, with a table of what every collection costs, a few lessons from now.
LinkedList is a Deque too
LinkedList implements both List and Deque, so new LinkedList<>() would work in every stack, queue and deque program on this page. The ArrayDeque documentation says ArrayDeque is likely to be faster when used as a queue. The next lesson, linked lists, builds a linked list from scratch and shows where the difference comes from.

Which one to use#

You want backUseTypical jobs
The newest elementA stack: a Deque<E> with push, pop and peekUndo, the back button, matching brackets, reversing
The oldest elementA queue: a Queue<E> with offer, poll and peekPrint jobs, customers waiting, messages to handle in the order they came
Either endA Deque<E> with the First and Last methodsPlay next and add to queue, palindromes, keeping only the latest few items
The smallest, or the first by some orderA PriorityQueue, with a comparator for other ordersDeadlines, shortest job first, the top few scores, events in time order

For the first three rows, create an ArrayDeque. Declare the variable as the interface whose methods you call, Deque or Queue, so the rest of the program only depends on the rule it needs.

Quick check
A chat app keeps the 50 newest messages of a conversation, newest at the top. Each new message goes in at the top, and once there are more than 50, the oldest is dropped. Which fits best?

Try it

Answer each one in your head or on paper first, then open the answer.

1
You push 1, 2 and 3 onto an empty ArrayDeque, then pop once. What does System.out.println(stack) print, and why in that order?
2
Which Deque methods do push, pop and peek stand for, and which end of the deque does a stack use?
3
Run the bracket checker by hand on {[}]. Which character makes it return false, and what is on top of the stack at that moment?
4
A PriorityQueue<Integer> prints [2, 4, 3, 9, 5]. What does poll() return, and what does the queue print afterwards?
5
In a heap's array, where are the children of the element at index 4, and where is its parent?
6
At one point in a program the queue should never be empty, and if it is, something has gone wrong earlier. Which method should take the front element there, poll() or remove()?

Practice

Work through these on your own. Each one builds on this page.

1
Add redo to Undo.java. Keep a second stack, redo. Undo pops an action from history and pushes it onto redo; redo pops from redo and pushes it back onto history. Doing a brand new action must clear redo. Explain in a sentence why.
2
Make the printer take turns. It prints at most 5 pages of a job at a time; if pages are left, the job goes to the back of the queue with that many fewer pages. Run it for jobs of 12, 3 and 7 pages and print each turn, such as Ines: 5 pages, 7 left.
3
A shop shows the five products a shopper viewed most recently, newest first, each product at most once. Write a method static void viewed(Deque<String> recent, String product) that updates the deque, and test it with a sequence of views that includes a repeat.
4
Write static List<Integer> sorted(List<Integer> numbers) that offers every number into a PriorityQueue and then polls them all into a new list. Then change one line so it sorts from largest to smallest.
5
A to-do app holds record Task(String name, int priority), where 1 is the most urgent. Build a PriorityQueue that hands out the most urgent task first and, among equal priorities, the tasks in alphabetical order of name. Offer six tasks, some with equal priorities, and poll them all.

Further reading#

Spotted a mistake, or a part that did not make sense? Report it by email