Queues, deques and priority queues
List, Map and Set cover most days. The remaining shapes answer a different
question: not "what is in here?" but "what should I do next?"
Queue: first in, first out
Work arrives, work is handled in order.
Queue<Delivery> pending = new ArrayDeque<>();
pending.offer(delivery); // add to the back
Delivery next = pending.poll(); // take from the front, null if empty
Delivery peek = pending.peek(); // look without taking, null if empty
Every Queue method comes in two flavours, and choosing correctly matters:
| Throws on failure | Returns a special value | Does |
|---|---|---|
add(e) |
offer(e) — returns false |
insert |
remove() |
poll() — returns null |
take from the front |
element() |
peek() — returns null |
look at the front |
Use the second column. An empty queue is a completely normal state, and
poll() returning null is easier to handle than catching
NoSuchElementException:
Delivery next;
while ((next = pending.poll()) != null) {
process(next);
}
The first column is right only when an empty queue genuinely means a bug.
Deque: both ends
A double-ended queue — push and pop at either end. It is the most useful of these and the one to default to.
Deque<Delivery> deque = new ArrayDeque<>();
deque.addFirst(urgent); // jump the queue
deque.addLast(normal); // join the back
Delivery front = deque.pollFirst();
Delivery back = deque.pollLast();
It is also your stack
Deque<String> undo = new ArrayDeque<>();
undo.push("added Priya"); // == addFirst
undo.push("added Arjun");
String last = undo.pop(); // "added Arjun" — == pollFirst
Do not use java.util.Stack. It extends Vector, so every method is
synchronized whether you want it or not, and — worse — it iterates in the wrong
order, from the bottom up rather than the top down. It is a 1995 class kept for
compatibility. ArrayDeque is faster and correct.
Likewise, do not use LinkedList as a queue out of habit. ArrayDeque is
faster for essentially every queue and stack operation, because it is a circular
array rather than a chain of node objects — one allocation instead of one per
element, and contiguous memory the CPU cache can actually use.
The one thing ArrayDeque will not do is hold null, because null is its
"empty" signal from poll() and peek(). That is a feature, not a limitation.
PriorityQueue: most important first
Not insertion order — priority order, decided by a Comparator.
// Deliveries with the most tiffins first, because they take longest to pack.
Queue<Delivery> queue = new PriorityQueue<>(
Comparator.comparingInt(Delivery::tiffins).reversed());
queue.offer(new Delivery(date, "Priya", 2));
queue.offer(new Delivery(date, "Arjun", 7));
queue.offer(new Delivery(date, "Kavita", 1));
queue.poll(); // Arjun, 7
queue.poll(); // Priya, 2
queue.poll(); // Kavita, 1
Two things surprise people, and both follow from it being a binary heap:
Iterating does not give you sorted order. Only poll() does.
for (Delivery d : queue) { … } // heap order, which looks arbitrary
System.out.println(queue); // likewise — not sorted
The heap only guarantees that the head is the smallest. If you want everything in order, poll until empty, or sort a copy.
Ties are broken arbitrarily. Two deliveries with the same count come out in no defined order. If that matters, add a tiebreaker to the comparator — the same rule as pagination in the full-stack course:
Comparator.comparingInt(Delivery::tiffins).reversed()
.thenComparing(Delivery::date)
Cost is O(log n) to insert and to remove the head, O(1) to peek. That is
what makes it the right tool for a scheduler, a top-N, or anything where you
repeatedly want "the next most important".
Top-N without sorting everything
A genuinely useful trick:
// The five busiest days, from a million deliveries, without sorting a million.
PriorityQueue<Delivery> topFive = new PriorityQueue<>(
Comparator.comparingInt(Delivery::tiffins)); // smallest at the head
for (Delivery d : allDeliveries) {
topFive.offer(d);
if (topFive.size() > 5) {
topFive.poll(); // drop the smallest
}
}
Keep the smallest at the head so the weakest candidate is the one evicted.
O(n log 5) instead of O(n log n), and constant memory.
LinkedHashMap and LinkedHashSet
Not queues, but they solve the ordering problem people usually reach for a list to solve.
Map<String, Integer> counts = new LinkedHashMap<>(); // insertion order kept
Set<String> customers = new LinkedHashSet<>(); // likewise
A HashMap has no order and its iteration order can change between runs and
between JVM versions. If your output needs to be stable — a report, a test
assertion, anything a human reads — use the Linked variant. It costs a little
memory for the linked list of entries and nothing in lookup time.
And a LinkedHashMap can be told to evict:
// An LRU cache in six lines.
Map<String, Rates> cache = new LinkedHashMap<>(16, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<String, Rates> eldest) {
return size() > 100;
}
};
The true is access-order rather than insertion-order, so reading an entry
moves it to the end. That is a least-recently-used cache, and it is worth
knowing it exists before you reach for a library.
Which one
| Need | Use |
|---|---|
| Work handled in arrival order | ArrayDeque as a Queue |
| A stack | ArrayDeque — never Stack |
| Add and remove at both ends | ArrayDeque |
| Most important first | PriorityQueue |
| Top N of many | PriorityQueue of size N |
| Hand work between threads | LinkedBlockingQueue |
| A map that keeps its order | LinkedHashMap |
| An LRU cache | LinkedHashMap with removeEldestEntry |
ArrayDeque appears four times, which is the point. When in doubt, it is
ArrayDeque.
Check your work
Why prefer offer/poll/peek: an empty queue is normal, and a null is
easier to handle than an exception.
Why not java.util.Stack: it extends Vector, is needlessly synchronized,
and iterates bottom-up.
Why not LinkedList as a queue: ArrayDeque is a circular array — one
allocation, cache-friendly — rather than a node per element.
Why ArrayDeque forbids null: null is the empty signal for poll and
peek.
Why iterating a PriorityQueue is not sorted: it is a heap, and only the
head is guaranteed.
How to get the top N cheaply: a PriorityQueue of size N with the smallest
at the head, evicting as you go.
Why LinkedHashMap for output: HashMap order is unspecified and can
change between runs and JVM versions.
What the true in LinkedHashMap's constructor does: access order, which
with removeEldestEntry gives you an LRU cache.
Practice
- Fill an
ArrayDequeas a queue and drain it with thewhile ((x = poll()) != null)loop. - Call
remove()on an emptyArrayDequeand read the exception. Thenpoll(). - Use the same
ArrayDequeas a stack withpush/pop. Confirm the order. - Do the same with
java.util.Stack, then iterate both and compare the order. - Put three deliveries in a
PriorityQueueordered by tiffins. Print the queue directly, then poll them all. Explain the difference. - Add two deliveries with equal tiffins and poll repeatedly across several runs. Then add a tiebreaker.
- Implement top-5 over 100,000 random deliveries with a
PriorityQueue. Time it against sorting the whole list. - Put ten entries in a
HashMapand aLinkedHashMapand print both. Run it three times. - Build the LRU cache above with a limit of 3. Access an old key and confirm it survives the next eviction.
- Try to add
nullto anArrayDeque, then to aLinkedList. Explain the difference.
Next: the contract that makes HashMap and HashSet work at all.
Stuck on this lesson?
Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.
About the internship