Queue Data Structure Complete Guide - Java Explained With All Operations
Queue Data Structure Complete Guide - Java Explained With All Operations

Queue Data Structure Complete Guide - Java Explained With All Operations

Learn everything about Queue data structure in Java. Includes types of queues, all operations, real life examples, time & space complexity, and top LeetCode problems to practice.

9 views
0
0
Listen to articleAudio version

Introduction

If you have been learning Data Structures and Algorithms, you have probably already spent time with arrays, linked lists, and stacks. Now it is time to meet one of the most important and widely used data structures in computer science — the Queue.

Queue is not just a theoretical concept. It powers some of the most critical systems you use every day — from how your printer handles jobs, to how your CPU schedules tasks, to how Google Maps finds the shortest path between two locations. Understanding Queue deeply means understanding how real systems work.

In this complete guide we will cover absolutely everything — what a Queue is, how it differs from a Stack, every type of Queue, all operations with code, Java implementations, time and space complexity, common interview questions, and the most important LeetCode problems that use Queue.

What Is a Queue?

A Queue is a linear data structure that follows the FIFO principle — First In First Out. This means the element that was added first is the one that gets removed first.

Think of it exactly like a real-world queue (a line of people). The person who joined the line first gets served first. No cutting in line, no serving from the back — strict order from front to back.

This is the fundamental difference between a Queue and a Stack:

  1. StackLIFO (Last In First Out) — like a stack of plates, you take from the top
  2. QueueFIFO (First In First Out) — like a line of people, you serve from the front

Real Life Examples of Queue

Before writing a single line of code, let us understand where queues appear in real life. This will make every technical concept feel natural.

Printer Queue — when you send multiple documents to print, they print in the order they were sent. The first document sent prints first.

CPU Task Scheduling — your operating system manages running processes in a queue. Tasks get CPU time in the order they arrive (in basic scheduling).

Customer Service Call Center — when you call a helpline and are put on hold, you are placed in a queue. The first caller on hold gets connected first.

WhatsApp Messages — messages are delivered in the order they are sent. The first message sent is the first one received.

BFS (Breadth First Search) — every time you use Google Maps or any navigation app to find the shortest path, it uses BFS internally which is entirely powered by a Queue.

Ticket Booking Systems — online booking portals process requests in the order they arrive. First come first served.

Queue Terminology — Key Terms You Must Know

Before diving into code, let us get the vocabulary right:

Front — the end from which elements are removed (dequeued). This is where the "first person in line" stands.

Rear (or Back) — the end at which elements are added (enqueued). New arrivals join here.

Enqueue — the operation of adding an element to the rear of the queue. Like joining the back of a line.

Dequeue — the operation of removing an element from the front of the queue. Like the first person in line being served and leaving.

Peek (or Front) — looking at the front element without removing it. Like seeing who is first in line without serving them yet.

isEmpty — checking whether the queue has no elements.

isFull — relevant for fixed-size queues, checking whether no more elements can be added.

Types of Queues

This is where most beginners get confused. There is not just one type of Queue — there are several variations each designed to solve specific problems.

1. Simple Queue (Linear Queue)

The most basic form. Elements enter from the rear and leave from the front. Strict FIFO, nothing fancy.

Enqueue → [ 1 | 2 | 3 | 4 | 5 ] → Dequeue
rear front

Problem with Simple Queue: In array-based implementation, once elements are dequeued from the front, those slots cannot be reused even if there is space. This wastes memory. This is why Circular Queue was invented.

2. Circular Queue

In a Circular Queue, the rear wraps around to the front when it reaches the end of the array. The last position connects back to the first, forming a circle. This solves the wasted space problem of simple queues.

[1] [2] [3]
/ \
[6] [4]
\ /
[5] ← rear

Used in: CPU scheduling, memory management, traffic light systems, streaming buffers.

3. Double Ended Queue (Deque)

A Deque (pronounced "deck") allows insertion and deletion from both ends — front and rear. It is the most flexible queue type.

Enqueue Front → [ 1 | 2 | 3 | 4 | 5 ] → Dequeue Front
Enqueue Rear → [ 1 | 2 | 3 | 4 | 5 ] → Dequeue Rear

Two subtypes:

  1. Input Restricted Deque — insertion only at rear, deletion from both ends
  2. Output Restricted Deque — deletion only at front, insertion at both ends

Used in: browser history (back and forward), undo-redo operations, sliding window problems.

4. Priority Queue

Elements are not served in FIFO order — instead each element has a priority and the element with the highest priority is served first regardless of when it was added.

Think of an emergency room. A patient with a critical injury jumps ahead of someone with a minor cut even if they arrived later.

Two types:

  1. Max Priority Queue — highest value = highest priority
  2. Min Priority Queue — lowest value = highest priority

Used in: Dijkstra's shortest path, Huffman encoding, A* search algorithm, task scheduling with priorities.

5. Blocking Queue

A thread-safe queue used in multi-threading. If the queue is empty, a thread trying to dequeue will wait (block) until an element is available. If the queue is full, a thread trying to enqueue will wait until space is available.

Used in: Producer-Consumer problems, thread pool implementations, Java's java.util.concurrent package.

Queue Operations and Time Complexity

Every queue operation has a specific time complexity that you must know cold for interviews.

OperationDescriptionTime Complexity
EnqueueAdd element to rearO(1)
DequeueRemove element from frontO(1)
Peek/FrontView front elementO(1)
isEmptyCheck if queue is emptyO(1)
SizeNumber of elementsO(1)
SearchFind a specific elementO(n)

Space Complexity: O(n) — where n is the number of elements stored.

All core queue operations are O(1). This is what makes Queue so powerful — no matter how many elements are in the queue, adding and removing always takes constant time.

Implementing Queue in Java — All Ways

Java gives you multiple ways to use a Queue. Let us go through each one.

Way 1: Using LinkedList (Most Common)

LinkedList implements the Queue interface in Java. This is the most commonly used Queue implementation.

import java.util.LinkedList;
import java.util.Queue;

Queue<Integer> queue = new LinkedList<>();

// Enqueue — add to rear
queue.offer(10);
queue.offer(20);
queue.offer(30);

// Peek — view front without removing
System.out.println(queue.peek()); // 10

// Dequeue — remove from front
System.out.println(queue.poll()); // 10
System.out.println(queue.poll()); // 20

// Check empty
System.out.println(queue.isEmpty()); // false

// Size
System.out.println(queue.size()); // 1

offer() vs add() — both add to the queue. add() throws an exception if the queue is full (for bounded queues). offer() returns false instead. Always prefer offer().

poll() vs remove() — both remove from front. remove() throws an exception if queue is empty. poll() returns null. Always prefer poll().

peek() vs element() — both view the front. element() throws exception if empty. peek() returns null. Always prefer peek().

Way 2: Using ArrayDeque (Fastest)

ArrayDeque is faster than LinkedList for Queue operations because it uses a resizable array internally with no node allocation overhead.

import java.util.ArrayDeque;
import java.util.Queue;

Queue<Integer> queue = new ArrayDeque<>();

queue.offer(1);
queue.offer(2);
queue.offer(3);

System.out.println(queue.peek()); // 1
System.out.println(queue.poll()); // 1
System.out.println(queue.size()); // 2

When to use ArrayDeque over LinkedList? Use ArrayDeque whenever possible for Queue or Stack operations. It is faster because it avoids the overhead of node objects that LinkedList creates for every element. In competitive programming and interviews, ArrayDeque is the preferred choice.

Way 3: Using Deque (Double Ended Queue)

import java.util.ArrayDeque;
import java.util.Deque;

Deque<Integer> deque = new ArrayDeque<>();

// Add to front
deque.offerFirst(10);
// Add to rear
deque.offerLast(20);
deque.offerLast(30);

// Remove from front
System.out.println(deque.pollFirst()); // 10
// Remove from rear
System.out.println(deque.pollLast()); // 30

// Peek front and rear
System.out.println(deque.peekFirst()); // 20
System.out.println(deque.peekLast()); // 20

Way 4: Using PriorityQueue

import java.util.PriorityQueue;

// Min Heap — smallest element has highest priority
PriorityQueue<Integer> minPQ = new PriorityQueue<>();
minPQ.offer(30);
minPQ.offer(10);
minPQ.offer(20);
System.out.println(minPQ.poll()); // 10 — smallest comes out first

// Max Heap — largest element has highest priority
PriorityQueue<Integer> maxPQ = new PriorityQueue<>((a, b) -> b - a);
maxPQ.offer(30);
maxPQ.offer(10);
maxPQ.offer(20);
System.out.println(maxPQ.poll()); // 30 — largest comes out first

Way 5: Implementing Queue From Scratch Using Array

Understanding the underlying implementation helps you in interviews when asked to build one from scratch.

class MyQueue {
private int[] arr;
private int front;
private int rear;
private int size;
private int capacity;
public MyQueue(int capacity) {
this.capacity = capacity;
arr = new int[capacity];
front = 0;
rear = -1;
size = 0;
}
public void enqueue(int val) {
if (size == capacity) {
System.out.println("Queue is full!");
return;
}
rear = (rear + 1) % capacity; // circular wrapping
arr[rear] = val;
size++;
}
public int dequeue() {
if (isEmpty()) {
System.out.println("Queue is empty!");
return -1;
}
int val = arr[front];
front = (front + 1) % capacity; // circular wrapping
size--;
return val;
}
public int peek() {
if (isEmpty()) return -1;
return arr[front];
}
public boolean isEmpty() {
return size == 0;
}
public int size() {
return size;
}
}

Notice the % capacity in enqueue and dequeue — that is what makes it a Circular Queue. Without this, once the rear reaches the end of the array, you cannot add more even if front has moved forward and freed up space.

Way 6: Implementing Queue Using Two Stacks

This is a very popular interview question — implement a Queue using two stacks. The idea is to use one stack for enqueue and another for dequeue.

class QueueUsingTwoStacks {
Stack<Integer> s1 = new Stack<>(); // for enqueue
Stack<Integer> s2 = new Stack<>(); // for dequeue
public void enqueue(int val) {
s1.push(val); // always push to s1
}
public int dequeue() {
if (s2.isEmpty()) {
// transfer all elements from s1 to s2
// this reverses the order, giving FIFO behavior
while (!s1.isEmpty()) {
s2.push(s1.pop());
}
}
return s2.pop();
}
public int peek() {
if (s2.isEmpty()) {
while (!s1.isEmpty()) {
s2.push(s1.pop());
}
}
return s2.peek();
}
public boolean isEmpty() {
return s1.isEmpty() && s2.isEmpty();
}
}

Why does this work?

When you transfer elements from s1 to s2, the order reverses. The element that was added first to s1 ends up on top of s2 — which means it gets dequeued first. FIFO achieved using two LIFOs!

Amortized time complexity: Each element is pushed and popped at most twice (once in s1, once in s2). So dequeue is O(1) amortized even though individual calls might take O(n).

This is LeetCode 232 — Implement Queue using Stacks.

Queue vs Stack — Side by Side

FeatureQueueStack
PrincipleFIFO — First In First OutLIFO — Last In First Out
Insert atRearTop
Remove fromFrontTop
Real lifeLine of peopleStack of plates
Java classLinkedList, ArrayDequeStack, ArrayDeque
Main useBFS, schedulingDFS, backtracking, parsing
PeekFront elementTop element

BFS — The Most Important Application of Queue

Breadth First Search (BFS) is the single most important algorithm that uses a Queue. Understanding BFS is why Queue matters so much in DSA.

BFS explores a graph or tree level by level — all nodes at distance 1 first, then all at distance 2, and so on. A Queue naturally enforces this level-by-level behavior.

public void bfs(int start, List<List<Integer>> graph) {
Queue<Integer> queue = new LinkedList<>();
boolean[] visited = new boolean[graph.size()];
queue.offer(start);
visited[start] = true;
while (!queue.isEmpty()) {
int node = queue.poll(); // process front node
System.out.print(node + " ");
for (int neighbor : graph.get(node)) {
if (!visited[neighbor]) {
visited[neighbor] = true;
queue.offer(neighbor); // add unvisited neighbors to rear
}
}
}
}

Why Queue and not Stack for BFS? Queue ensures you process all neighbors of a node before going deeper. Stack would take you deep into one path first — that is DFS, not BFS. The FIFO property is what guarantees level-by-level exploration.

BFS with Queue is used in:

  1. Shortest path in unweighted graphs
  2. Level order traversal of trees
  3. Finding connected components
  4. Word ladder problems
  5. Rotten oranges, flood fill, and matrix BFS problems

Level Order Traversal — BFS on Trees

One of the most common Queue problems in interviews is Level Order Traversal of a binary tree.

public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> result = new ArrayList<>();
if (root == null) return result;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
int levelSize = queue.size(); // number of nodes at current level
List<Integer> level = new ArrayList<>();
for (int i = 0; i < levelSize; i++) {
TreeNode node = queue.poll();
level.add(node.val);
if (node.left != null) queue.offer(node.left);
if (node.right != null) queue.offer(node.right);
}
result.add(level);
}
return result;
}

The key trick here is using queue.size() at the start of each while loop iteration to know exactly how many nodes belong to the current level. Process exactly that many nodes, then move to the next level.

This is LeetCode 102 — Binary Tree Level Order Traversal.

Sliding Window Maximum — Monotonic Deque

One of the most impressive Queue applications is the Sliding Window Maximum problem using a Monotonic Deque. This is the queue equivalent of the Monotonic Stack pattern you saw in stack problems.

The idea — maintain a deque that stores indices of elements in decreasing order. The front always holds the index of the maximum element in the current window.

public int[] maxSlidingWindow(int[] nums, int k) {
Deque<Integer> deque = new ArrayDeque<>(); // stores indices
int[] result = new int[nums.length - k + 1];
int idx = 0;
for (int i = 0; i < nums.length; i++) {
// remove indices that are out of the current window
while (!deque.isEmpty() && deque.peekFirst() < i - k + 1) {
deque.pollFirst();
}
// remove indices whose values are smaller than current
// they can never be the maximum for any future window
while (!deque.isEmpty() && nums[deque.peekLast()] < nums[i]) {
deque.pollLast();
}
deque.offerLast(i);
// window is fully formed, record maximum (front of deque)
if (i >= k - 1) {
result[idx++] = nums[deque.peekFirst()];
}
}
return result;
}

This gives O(n) time for what would otherwise be an O(n×k) problem. This is LeetCode 239 — Sliding Window Maximum.

Java Queue Interface — Complete Method Reference

Here is every method you will ever need from Java's Queue and Deque interfaces:

Queue Methods:

offer(e) — add to rear, returns false if full (preferred over add) poll() — remove from front, returns null if empty (preferred over remove) peek() — view front without removing, returns null if empty (preferred over element) isEmpty() — returns true if no elements size() — returns number of elements contains(o) — returns true if element exists

Deque Additional Methods:

offerFirst(e) — add to front offerLast(e) — add to rear pollFirst() — remove from front pollLast() — remove from rear peekFirst() — view front peekLast() — view rear

PriorityQueue Specific:

offer(e) — add with natural ordering or custom comparator poll() — remove element with highest priority peek() — view highest priority element without removing

Common Interview Questions About Queue

These are the questions interviewers ask to test your understanding of queues conceptually — not just coding.

Q1. What is the difference between Queue and Stack? Queue is FIFO — elements are removed in the order they were added. Stack is LIFO — the most recently added element is removed first. Queue removes from the front, Stack removes from the top.

Q2. Why is ArrayDeque preferred over LinkedList for Queue in Java? ArrayDeque uses a resizable array internally and has better cache locality and no node allocation overhead. LinkedList creates a new node object for every element added, which means more garbage collection pressure. ArrayDeque is faster in practice for most Queue use cases.

Q3. When would you use a PriorityQueue instead of a regular Queue? When the order of processing depends on priority rather than arrival order. For example in a hospital, critical patients are treated before minor cases regardless of when they arrived. Or in Dijkstra's algorithm, always processing the shortest known distance first.

Q4. How is Queue used in BFS? BFS uses a Queue to explore nodes level by level. The starting node is enqueued first. Each time a node is dequeued, all its unvisited neighbors are enqueued. Since Queue is FIFO, all neighbors of a node are processed before going deeper — guaranteeing level-by-level exploration.

Q5. What is the difference between poll() and remove() in Java Queue? Both remove the front element. remove() throws NoSuchElementException if the queue is empty. poll() returns null instead of throwing. Always use poll() for safer code.

Q6. Can a Queue have duplicates? Yes. Queue does not have any restriction on duplicate values unlike Sets. The same value can appear multiple times in a Queue.

Q7. What is a Blocking Queue and when is it used? A Blocking Queue is a thread-safe Queue used in multi-threaded applications. When a thread tries to dequeue from an empty queue, it blocks (waits) until an element is available. When a thread tries to enqueue into a full queue, it blocks until space is available. Used in Producer-Consumer patterns.

Top LeetCode Problems on Queue

Here are the most important LeetCode problems that use Queue — organized from beginner to advanced:

Beginner Level:

232. Implement Queue using Stacks — implement Queue with two stacks, classic interview question

225. Implement Stack using Queues — reverse of 232, implement Stack using Queue

933. Number of Recent Calls — sliding window with Queue

Intermediate Level:

102. Binary Tree Level Order Traversal — BFS on tree, must know

107. Binary Tree Level Order Traversal II — same but bottom up

994. Rotting Oranges — multi-source BFS on grid

1091. Shortest Path in Binary Matrix — BFS shortest path

542. 01 Matrix — multi-source BFS, distance to nearest 0

127. Word Ladder — BFS on word graph, classic

Advanced Level:

239. Sliding Window Maximum — monotonic deque, must know

862. Shortest Subarray with Sum at Least K — monotonic deque with prefix sums

407. Trapping Rain Water II — 3D BFS with priority queue

787. Cheapest Flights Within K Stops — BFS with constraints

Queue Cheat Sheet — Everything at a Glance

Create a Queue:

Queue<Integer> q = new LinkedList<>(); // standard
Queue<Integer> q = new ArrayDeque<>(); // faster, preferred
Deque<Integer> dq = new ArrayDeque<>(); // double ended
PriorityQueue<Integer> pq = new PriorityQueue<>(); // min heap
PriorityQueue<Integer> pq = new PriorityQueue<>((a,b) -> b-a); // max heap

Core Operations:

q.offer(x); // enqueue
q.poll(); // dequeue
q.peek(); // front element
q.isEmpty(); // check empty
q.size(); // number of elements

Deque Operations:

dq.offerFirst(x); // add to front
dq.offerLast(x); // add to rear
dq.pollFirst(); // remove from front
dq.pollLast(); // remove from rear
dq.peekFirst(); // view front
dq.peekLast(); // view rear

BFS Template:

Queue<Integer> queue = new LinkedList<>();
queue.offer(start);
visited[start] = true;

while (!queue.isEmpty()) {
int node = queue.poll();
for (int neighbor : graph.get(node)) {
if (!visited[neighbor]) {
visited[neighbor] = true;
queue.offer(neighbor);
}
}
}

Conclusion

Queue is one of those data structures that appears simple on the surface but has incredible depth once you start exploring its variations and applications. From the basic FIFO concept to Circular Queues, Deques, Priority Queues, Monotonic Deques, and BFS — each layer adds a new tool to your problem-solving arsenal.

Here is the learning path to follow based on everything covered in this guide:

Start with understanding FIFO vs LIFO and when each applies. Then get comfortable with Java's Queue interface — offer, poll, peek. Practice the BFS template until it feels automatic. Then move to Level Order Traversal problems. Once BFS clicks, tackle multi-source BFS problems like Rotting Oranges. Finally learn the Monotonic Deque pattern for sliding window problems.

Master these and you will handle every Queue problem in any coding interview with confidence.

Ai Assistant Kas
Queue Data Structure Complete Guide - Java Explained With All Operations | Kode$word