الطوابير (Queues)
في هذا الدرس 11 موضوعات
هذا الدرس متاح حاليًا باللغة الإنجليزية فقط، ولم تُنشر نسخته العربية بعد.
Lesson Overview
A stack serves the newest item first. A queue does the opposite: it serves the oldest item first. The picture is a line at a shop: new customers join at the back, the cashier serves the person at the front, and nobody jumps the line.
This order is called FIFO: first in, first out. It is the natural shape of fairness and of work that must be done in the order it arrived: print jobs, tasks waiting for a worker, and the messages that move between services.
By the end you will use Queue with offer, poll, and peek, understand why a naive array queue is slow and how a circular array or a linked list fixes it, and be able to choose between a stack and a queue for a given problem.
Why this matters: queues are everywhere in backends, from the request waiting for a thread to a message broker between systems. Later modules build on the idea you learn here.
This lesson builds on Lessons 1 to 4 and on the Queues and deques lesson of the Collections module (BEG-030).
Learning Objectives
By the end of this lesson you will be able to:
- Define a queue and FIFO.
- Use
offer,poll, andpeekon aQueue, and predict their behavior on an empty queue. - Explain why a queue stored in a plain array with shifting is slow (O(n) per
poll). - Explain how a circular array and a linked list make both ends O(1).
- Implement a queue with a tail reference.
- Choose between a stack and a queue from the shape of a problem.
Written Explanation
The operations
| Operation | Meaning | Cost (good implementation) |
|---|---|---|
offer(x) | Join the back of the line (enqueue) | O(1) |
poll() | Remove and return the front item, or null if empty (dequeue) | O(1) |
peek() | Look at the front item without removing it, or null if empty | O(1) |
Trace:
offer(A) front -> A <- back
offer(B) front -> A B <- back
offer(C) front -> A B C <- back
poll() -> A front -> B C <- backThe first in (A) was the first out. That is FIFO.
Java: Queue and ArrayDeque
Queue<E> is the interface for this behavior, and ArrayDeque is a common implementation. The JDK documentation for ArrayDeque says it is likely to be faster than LinkedList when used as a queue, and that it prohibits null items.
Queue<String> q = new ArrayDeque<>();
q.offer("Ana");
q.offer("Omar");
String next = q.poll(); // "Ana"The Queue interface has two versions of some operations. offer, poll, and peek signal problems with a special value (false or null). The older-style add, remove, and element throw exceptions instead. For a general queue, prefer offer and poll, and remember that poll returns null when the queue is empty. Since ArrayDeque does not allow null items, a null from poll always means "empty".
Why an array queue is tricky
Suppose you store the queue in a plain array where the front is always index 0. offer writes at size (cheap). But poll removes index 0, and to keep the front at index 0 you must shift every other item left: O(n) each time. That is the problem from Lesson 1. A queue built on this idea is correct but slow, and the slowness grows with the queue's length.
Two standard fixes make both ends O(1):
- A linked list with
headandtail.offerattaches at the tail;polldetaches the head. Nothing shifts. - A circular array (ring buffer). Keep an index
headfor the front and let positions wrap around the end of the array using the remainder operator.polljust movesheadforward by one; no shifting.
capacity 3: [ _ , 2 , 3 ] head = 1, size = 2
offer(4) goes to (head + size) % 3 = 0 -> [ 4 , 2 , 3 ]The back of the queue wrapped around to the start of the array. ArrayDeque works on a related idea: an array with indexes that move at both ends, growing by copying when full, as in Lesson 2.
A linked queue
void offer(T v) {
Node<T> n = new Node<>(v);
if (tail == null) { head = n; tail = n; }
else { tail.next = n; tail = n; }
size++;
}
T poll() {
if (head == null) return null;
T v = head.value;
head = head.next;
if (head == null) tail = null; // queue became empty
size--;
return v;
}This is Lesson 3's addLast and removeFirst. The line if (head == null) tail = null is the same trap as before: when the last node leaves, the tail must not keep pointing at it.
Choosing: stack or queue?
Ask in which order items should come back out.
| Newest first | Oldest first |
|---|---|
| Stack (LIFO) | Queue (FIFO) |
| Undo, back button, call stack, bracket matching | Print jobs, task processing, customers waiting, messages |
Queues also drive breadth-first search (you will meet this when graphs appear) and round-robin scheduling, where each task takes a turn and then rejoins the back.
Other kinds of queue
Real systems have variations that you will meet later: a priority queue serves the most important item first rather than the oldest, a bounded queue has a maximum length, and a deque (double-ended queue) lets you add and remove at both ends. ArrayDeque is a deque, which is why it works as both a stack and a queue.
Examples
Example 1: a short line (simple)
Queue<String> q = new ArrayDeque<>();
q.offer("Ana");
q.offer("Omar");
q.offer("Lina");
System.out.println(q.peek());
System.out.println(q.poll());
System.out.println(q.size() + " left, next is " + q.peek());
q.poll(); q.poll();
System.out.println(q.poll());Real output:
Ana
Ana
2 left, next is Omar
nullpeek looked at Ana without removing her; poll removed her. The last poll found an empty queue and returned null rather than throwing.
Example 2: processing print jobs (practical)
Queue<String> jobs = new ArrayDeque<>();
jobs.offer("report.pdf");
jobs.offer("photo.png");
jobs.offer("notes.txt");
while (!jobs.isEmpty()) {
System.out.println("printing " + jobs.poll());
}Output:
printing report.pdf
printing photo.png
printing notes.txtJobs are processed in arrival order. This while (!isEmpty()) poll() loop is the standard way to drain a queue.
Example 3: a ring buffer that wraps around (realistic)
A fixed-capacity queue, such as a buffer holding the last few incoming events, avoids shifting by wrapping:
static class RingQueue {
private final int[] data;
private int head, size;
RingQueue(int cap) { data = new int[cap]; }
boolean offer(int v) {
if (size == data.length) return false; // full
data[(head + size) % data.length] = v;
size++;
return true;
}
Integer poll() {
if (size == 0) return null;
int v = data[head];
head = (head + 1) % data.length;
size--;
return v;
}
}With capacity 3 the program offered 1, 2, 3, 4, polled twice, offered 5 and 6, and polled four times. Real output:
true true true false
1 2
true true
3 5 6 nullThe fourth offer(4) was rejected because the buffer was full. After two polls there were two free slots, so 5 and 6 went into positions that wrapped around the end of the array. The values still came out in the order they went in: 3, 5, 6. No item was ever shifted.
Backend Insight
Queues are the backbone of asynchronous work in backends:
- Work queues. A web request that triggers slow work, such as sending an email or generating a report, can put a job on a queue and return immediately; background workers take jobs from the front.
- Message queues. Systems such as message brokers pass messages between services in order, which is the idea behind the module on messaging (MID-026 in the roadmap). They are queues at a larger scale, with extra concerns such as persistence and delivery guarantees that this module does not cover.
- Backpressure. If items arrive faster than they leave, a queue grows. An unbounded queue then eats memory, so real systems limit its length or push back on the sender. That is why bounded queues exist.
- Fairness. FIFO means no task waits forever behind later arrivals.
Remember the cost model you built: a queue is only O(1) at both ends if the structure underneath supports it. That is why ArrayDeque and linked implementations are chosen, and an ArrayList with remove(0) is not.
Common Mistakes
Mistake 1: Using a plain list and remove(0) as a queue
- Incorrect:
List<String> q = new ArrayList<>(); ... q.remove(0);Each dequeue shifts the rest: O(n). - Correct:
Queue<String> q = new ArrayDeque<>(); ... q.poll();
Mistake 2: Forgetting the tail when the queue empties
In a linked queue, when the last node is polled, set tail = null too, or the next offer will attach to a node that is no longer in the queue.
Mistake 3: Treating null from poll as a value
poll returns null when the queue is empty. Check isEmpty() or null before using the result. And never offer(null) to an ArrayDeque; it throws NullPointerException.
Mistake 4: Mixing stack and queue method names
On a Deque, push adds at the front and offer adds at the back. Mixing them in one object gives confusing order. Use one style per object.
Exercises
Try each exercise yourself before opening the solutions.
Level A: Foundation
A1. offer("X"), offer("Y"), offer("Z"), poll(), poll(). What does peek() return?
أظهر الحل A1
A1. After three offers the queue is X, Y, Z (front first). Two polls remove X and Y. peek() returns Z.
A2. What does poll() return on an empty Queue? What does remove() do?
أظهر الحل A2
A2. poll() returns null. remove() throws NoSuchElementException.
A3. Choose stack or queue: (a) support tickets answered in the order received, (b) pages in a "go back" history, (c) a game's turn order where each player plays and rejoins the end.
أظهر الحل A3
A3. (a) queue, (b) stack, (c) queue (the player who just played goes to the back).
Level B: Application
B1. A queue is stored in an array with the front at index 0 and holds 10,000 items. How many items shift on one poll? How many in a circular array?
أظهر الحل B1
B1. Shifting array: 9,999 items move left on each poll. Circular array: no items move; only the head index changes.
B2. Write a loop that moves tasks A, B, C through a round-robin: poll one, print it, offer it back to the end, repeat 5 times. What is printed?
أظهر الحل B2
B2.
Queue<String> tasks = new ArrayDeque<>();
tasks.offer("A"); tasks.offer("B"); tasks.offer("C");
for (int i = 0; i < 5; i++) {
String t = tasks.poll();
System.out.print(t + " ");
tasks.offer(t);
}It prints A B C A B (real run).
B3. In the ring buffer with capacity 4, head = 3 and size = 2. At which array index does the next offer write?
أظهر الحل B3
B3. (head + size) % capacity = (3 + 2) % 4 = 1. The write goes to index 1, having wrapped around.
Level C: Challenge
C1. Write LinkedQueue<T> with offer, poll, peek, and isEmpty using nodes with head, tail, and size. Which line prevents the stale tail?
أظهر الحل C1
C1.
static class LinkedQueue<T> {
private Node<T> head, tail;
private int size;
void offer(T v) {
Node<T> n = new Node<>(v);
if (tail == null) { head = n; tail = n; }
else { tail.next = n; tail = n; }
size++;
}
T poll() {
if (head == null) return null;
T v = head.value;
head = head.next;
if (head == null) tail = null; // queue became empty
size--;
return v;
}
T peek() { return head == null ? null : head.value; }
boolean isEmpty() { return size == 0; }
}The line if (head == null) tail = null; prevents the stale tail. Testing with a, b, c: two polls gave ab, peek showed c, polling again then once more returned null with isEmpty() true, and a later offer("z") then poll() returned z (real run).
C2. Tickets arrive at two per second and are completed at one per second. After 60 seconds, how many are waiting in an unbounded queue? What problem does that show, and what could a backend do about it?
أظهر الحل C2
C2. Arrivals minus completions is one per second, so after 60 seconds about 60 tickets are waiting, and the queue keeps growing. In a real system an unbounded queue grows until memory or patience runs out. A backend can add more workers, limit the queue length and reject or delay new work (backpressure), or speed up processing.
Mini Challenge
Build a bank counter simulation. Use a Queue<String> of customer names. Write a loop for 8 steps in which, at every step, one customer is served (if any), and on steps 1, 3, and 5 two new customers arrive first (name them C1, C2, and so on).
- Print what happens each step: who was served and how many are waiting.
- After step 8, print who is still waiting.
Check your work: customers are served in the exact order they arrived, and at no step do you call poll on an empty queue without handling null.
Summary & Next Lesson
Key takeaways
- A queue serves items in arrival order: FIFO;
offer,poll,peekare O(1) in a good implementation. - In Java, use
Queue<T> q = new ArrayDeque<>();pollandpeekreturnnullwhen empty. - An array queue that shifts on every
pollis O(n); a circular array or a linked list with a tail keeps both ends O(1). - When a linked queue becomes empty, reset the tail as well as the head.
- Stack for "newest first", queue for "oldest first"; message queues are this idea at a larger scale.
Next lesson
Next is "Choosing: ArrayList vs LinkedList vs ArrayDeque". It collects every cost you have seen into one decision table, so you can pick the right JDK structure quickly.
Self-check
- What does FIFO mean?
- What do
offer,poll, andpeekdo? - Why is
ArrayList.remove(0)a poor dequeue? - How does a circular array avoid shifting?
- Stack or queue: tasks processed in arrival order?
أظهر الإجابات
- First in, first out: the oldest item leaves first.
offeradds at the back,pollremoves and returns the front (ornull), andpeeklooks at the front without removing it.- Removing index 0 shifts all the other items, so it costs O(n).
- A
headindex moves forward and positions wrap around the end using the remainder operator, so no items move. - A queue.
References
- Oracle, Java SE 21 API, java.util.ArrayDeque. Supports:
offer,poll,peeksemantics (null on empty), null elements prohibited, and likely to be faster thanLinkedListas a queue. - Oracle, Java SE 21 API, java.util.Deque. Supports: the deque interface providing both stack and queue operations.
- All outputs came from running the code on JDK 21. The throwing/special-value distinction between
add/removeandoffer/pollis general knowledge of theQueueinterface that was not fetched from its own page for this lesson. The message-queue remarks are conceptual and not verified against any product documentation.
التطبيق
طبّق هذا الدرس بمختبر الوحدة وتمارينها واختبارها.
تعليم الدرس كمكتمل اختياري ويمكنك التراجع عنه في أي وقت. يبقى تقدمك محفوظًا في هذا المتصفح.