If you've ever stood in line at a bubble tea shop, you already understand a queue. That's basically the whole idea — but in code, this simple concept turns into one of the most useful data structures you'll meet. In this post we'll walk through what a queue is, how to implement one in Java, and then level it up into a priority queue.
This is written for someone seeing it for the first time, so I won't assume much beyond "you've written a few Java classes."
1. The mental model
Picture a checkout line at the supermarket:
front rear
↓ ↓
[ A ] ← [ B ] ← [ C ] ← [ D ] ← (new person joins here)
↑
(cashier serves whoever is here next)
Two rules govern this line:
- New people join at the rear.
- The cashier always serves whoever is at the front. That's it. That's a queue. And the rule it follows has a famous three-letter name: FIFO — First In, First Out. The first person in line is the first person out.
Compare this to a stack, which is LIFO (Last In, First Out) — like a stack of plates where you only take the top one. Same family of "linear" structures, different access rule.
2. Where queues live in Java
Before we build one, let's see where Queue sits in Java's official Collections Framework:
Iterable
│
Collection
┌────┼─────┐
List Queue Set
│ │ │
ArrayList PriorityQueue HashSet
LinkedList Deque LinkedHashSet
Vector ArrayDeque TreeSet
Stack
A few takeaways:
Queueis a sibling ofListandSet— they're allCollections.Queueis an interface, not a concrete class. You don'tnew Queue(). You use an implementation likeLinkedListorPriorityQueue.Deque(double-ended queue) is a more flexible cousin that allows add/remove at both ends. We won't focus on it here.
3. The two operations that define a queue
You only really need two:
| Operation | What it does | Where it happens |
|---|---|---|
enqueue(x) | Add x to the queue | At the rear |
dequeue() | Remove and return the next element | From the front |
You'll often also see helpers like peek() (look at the front without removing), size(), and isEmpty(). But enqueue and dequeue are the heart of it.
Try it yourself
I built a tiny visualizer — type a value, hit ENQUEUE, watch it appear at the rear. Hit DEQUEUE to pull from the front. The borders highlight which node is the front (red) and which is the rear (blue).
4. Implementing a queue: why a linked list wins
Here's a subtle but important question: should I use an ArrayList or a LinkedList under the hood?
Let's think about what each operation costs.
With an ArrayList:
enqueue(add at end) → cheap, just appends. ✅dequeue(remove from index 0) → ⚠️ expensive! Every other element has to shift left by one. That's an O(n) hit on every removal. With aLinkedList:enqueue(add at tail) → O(1). ✅dequeue(remove from head) → O(1). ✅ Both queue operations are constant-time with a linked list. That's why the textbook recommends a linked list as the backing structure for a queue.
Queue conceptually Queue implemented as a LinkedList
┌───┬───┬───┐ ┌───┐ ┌───┐ ┌───┐
│ A │ B │ C │ │ A │ → │ B │ → │ C │
└───┴───┴───┘ └───┘ └───┘ └───┘
front rear first last
(dequeue (enqueue
from here) to here)
5. Two ways to wire a GenericQueue to a LinkedList
Once you've decided to use a linked list, you still have a design choice. Java's textbook author Y. Daniel Liang lays out two paths.
Option A: Inheritance — GenericQueue extends LinkedList
Your queue is-a linked list, and inherits all of its methods.
- ✅ Easy to write.
- ❌ But… your queue now exposes every
LinkedListmethod (likeadd(int index, E e),get(i), etc.). That breaks the queue's contract — users can sneakily insert in the middle, peek at index 5, all sorts of things a real queue shouldn't allow.
Option B: Composition — GenericQueue has-a LinkedList
Your queue owns a private linked list and only exposes enqueue, dequeue, getSize.
- ✅ Encapsulates the implementation. The user can only do queue things.
- ✅ You could swap the internal
LinkedListfor something else later without breaking callers. - This is the preferred design.
(a) Inheritance (b) Composition
LinkedList ◁──── GenericQueue GenericQueue ◆──── LinkedList
(holds one privately)
Rule of thumb: Prefer composition over inheritance unless your subclass is genuinely a more specific kind of the parent. A queue isn't a "kind of linked list" — it's a different abstraction that happens to use one internally.
6. GenericQueue<E> — the code
Here's the textbook implementation using composition. It's short enough to memorize:
public class GenericQueue<E> {
private java.util.LinkedList<E> list = new java.util.LinkedList<>();
public void enqueue(E e) {
list.addLast(e); // add at rear
}
public E dequeue() {
return list.removeFirst(); // remove from front
}
public int getSize() {
return list.size();
}
@Override
public String toString() {
return "Queue: " + list.toString();
}
}
A few things worth noticing:
<E>is a generic type parameter. It lets you buildGenericQueue<String>,GenericQueue<Integer>, orGenericQueue<MyOwnClass>from the same definition.addLastandremoveFirstare methods Java'sLinkedListalready provides. We're just renaming them to the queue vocabulary.- The internal
listisprivate— outside code can't reach in and break invariants.
Testing it
GenericQueue<String> queue = new GenericQueue<>();
queue.enqueue("Tom"); // Queue: [Tom]
queue.enqueue("Susan"); // Queue: [Tom, Susan]
queue.enqueue("Kim");
queue.enqueue("Michael"); // Queue: [Tom, Susan, Kim, Michael]
System.out.println(queue.dequeue()); // Tom
System.out.println(queue.dequeue()); // Susan
System.out.println(queue); // Queue: [Kim, Michael]
Notice how Tom — the first one in — is also the first one out. FIFO in action.
7. Priority Queues — when FIFO isn't enough
Now imagine an emergency room.
A patient with a stab wound arrives at 10:00 AM. A patient with a sprained ankle arrives at 10:05 AM. Should the ankle be treated first because they arrived first? Of course not.
That's the limitation of a plain queue: it doesn't care how important an element is. In real life, sometimes you need to jump the line.
Priority Queue: A queue where each element has a priority, and the highest-priority element comes out first — regardless of insertion order.
Some people call this "largest-in, first-out" behavior, though as you'll see, "largest" can mean either biggest number or smallest number depending on how you set it up.
Visualizing it
Here's the classic textbook picture: elements are dumped into a container with priorities, and removal always pulls out the highest-priority one. With 8, 3, 18, 15, 13 in the bowl (higher number = higher priority), 18 leaves first.
(Try toggling the "higher = higher priority" / "lower = higher priority" mode at the top — same data, different winner.)
8. Java's built-in PriorityQueue
Good news: Java ships one. You don't have to build it yourself.
import java.util.PriorityQueue;
It implements the Queue<E> interface, so the method names look familiar:
| Method | What it does | Returns |
|---|---|---|
offer(E e) | Add an element | boolean |
poll() | Remove and return the highest-priority element | E (or null) |
peek() | Look at the highest-priority element without removing | E (or null) |
remove(Object o) | Remove a specific object | boolean |
contains(Object o) | Check if it's in the queue | boolean |
size() | How many elements | int |
clear() | Empty it | void |
A note on naming:
offer/poll/peekare the queue-aware versions that returnnullorfalseinstead of throwing on edge cases (empty queue, capacity full). The olderadd/removeexist too but can throw exceptions. Prefer the new names.
How does it know what "highest priority" means?
Two ways:
- Natural ordering. If your elements implement
Comparable<E>,PriorityQueueuses that. ForString, natural order is alphabetical, so"Georgia"comes out before"Texas". - Custom
Comparator. Pass one to the constructor and you control the order.
9. Example 1 — strings, two orderings
import java.util.*;
public class PriorityQueueDemo {
public static void main(String[] args) {
// Default: natural ordering (alphabetical for Strings)
PriorityQueue<String> queue1 = new PriorityQueue<>();
queue1.offer("Oklahoma");
queue1.offer("Indiana");
queue1.offer("Georgia");
queue1.offer("Texas");
System.out.println("Priority queue using Comparable:");
while (queue1.size() > 0) {
System.out.print(queue1.poll() + " ");
}
// → Georgia Indiana Oklahoma Texas
// Reversed order via Collections.reverseOrder()
PriorityQueue<String> queue2 =
new PriorityQueue<>(4, Collections.reverseOrder());
queue2.offer("Oklahoma");
queue2.offer("Indiana");
queue2.offer("Georgia");
queue2.offer("Texas");
System.out.println("\nPriority queue using Comparator:");
while (queue2.size() > 0) {
System.out.print(queue2.poll() + " ");
}
// → Texas Oklahoma Indiana Georgia
}
}
Same elements, opposite outputs — just by swapping in Collections.reverseOrder().
10. Example 2 — your own objects
What if you want to queue something Java doesn't know how to compare? Like a Customer?
You have to teach it. Implement Comparable<Customer> and define compareTo:
public class Customer implements Comparable<Customer> {
private Integer id;
private String name;
public Customer(Integer id, String name) {
this.id = id;
this.name = name;
}
public Integer getID() { return id; }
public String getName() { return name; }
@Override
public int compareTo(Customer c) {
return this.getID().compareTo(c.getID());
}
@Override
public String toString() {
return "Customer [id=" + id + ", name=" + name + "]";
}
}
Now you can drop Customer objects into a PriorityQueue and it'll know what to do:
PriorityQueue<Customer> customerQueue =
new PriorityQueue<>(Collections.reverseOrder());
customerQueue.add(new Customer(3, "Donald"));
customerQueue.add(new Customer(1, "Chong"));
customerQueue.add(new Customer(2, "Ali"));
customerQueue.add(new Customer(4, "Bala"));
Customer c = customerQueue.peek();
if (c != null) {
System.out.println(c.getName() + " is in the queue");
while ((c = customerQueue.poll()) != null) {
System.out.println(c);
}
}
Output:
Bala is in the queue
Customer [id=4, name=Bala]
Customer [id=3, name=Donald]
Customer [id=2, name=Ali]
Customer [id=1, name=Chong]
Because we used reverseOrder(), the biggest id wins. Bala (id=4) is the VIP.
compareTocheat sheet:
- Returns negative →
thisis "less than"other- Returns zero → equal priority
- Returns positive →
thisis "greater than"otherBy default, "greater" = "later out". Reverse it with a
Comparatorif you want the largest first.
11. Quick check — can you answer these?
Before moving on, see if you can answer these without scrolling back up.
- Where in a queue do you insert? Where do you delete?
- What does FIFO stand for?
- Why is a
LinkedLista better choice than anArrayListto implement a queue? - What are the two preferred methods to add and remove in
java.util.PriorityQueue(the ones that don't throw)? - If you put
5, 1, 8, 3into a defaultPriorityQueue<Integer>and callpoll()four times, what comes out and in what order?
Answers
- Insert at the rear (back/tail). Delete from the front (head).
- First In, First Out.
- Removing from the front of an
ArrayListis O(n) because every other element has to shift. With a doubly-linked list, head removal is O(1). offer(e)to add,poll()to remove.1, 3, 5, 8— natural ordering forIntegeris ascending, so smallest comes out first.
12. Exercise — build your own
Want a workout? Try implementing a queue using an array instead of a linked list. Call it ArrayQueue<E>. You'll need to think about:
- Two index pointers — one for the front, one for the rear.
- What happens when
rearreaches the end of the array? (Hint: a circular array wrapsrearback to0.) - How and when to
resize()when the array fills up. Sketch of the API:
public class ArrayQueue<E> {
public ArrayQueue();
public ArrayQueue(int initialCapacity);
public void enqueue(E e);
public E dequeue();
public E getElement(); // peek
public boolean isEmpty();
public int size();
private void resize(); // grow when full
}
This is a classic interview-style exercise. You learn a lot by getting the circular-array bookkeeping right.
13. Where to go next
Queues show up everywhere once you start looking — task schedulers, BFS in graph algorithms, print spoolers, message brokers, request handling in web servers. Priority queues power Dijkstra's shortest-path algorithm and A* search. The simple FIFO idea is doing a lot of work behind the scenes in real systems.
Coming up next in this series: trees — the structure that makes priority queues fast under the hood (via something called a binary heap), and a whole lot more.