Data structures in Java are ways of organizing and storing data so that programs can access, update, search, and process information efficiently.
Java provides both built-in structures such as arrays and a powerful collection framework containing classes and interfaces such as
ArrayList, LinkedList,
HashMap, HashSet,
Queue, and Deque.
Choosing the right data structure affects both the performance and design of a Java application.
For example, an ArrayList is useful when indexed access is common,
while a HashMap is useful when data needs to be accessed using unique keys.
Understanding these differences is important for Java development, problem solving, and coding interviews.
Common Data Structures in Java
Java provides several data structures through arrays, the Java Collections Framework, and related utility classes. The most commonly used structures include:
- Array: Stores a fixed-size sequence of elements.
- ArrayList: A resizable array implementation.
- LinkedList: A linked structure that can also implement list and deque behavior.
- Stack: Represents LIFO-style operations, although
Dequeis generally preferred for new code. - Queue: Represents FIFO-style processing.
- Deque: Supports insertion and removal from both ends.
- HashMap: Stores key-value pairs.
- HashSet: Stores unique elements.
- TreeMap: Stores key-value pairs in sorted key order.
- TreeSet: Stores unique elements in sorted order.
- PriorityQueue: Processes elements according to priority.
1. Arrays in Java
An array stores multiple values of the same type using a fixed-size structure. Elements are accessed using zero-based indexes.
int[] numbers = {10, 20, 30, 40, 50};
System.out.println(numbers[0]);
System.out.println(numbers[2]);
Arrays provide constant-time indexed access because an element can be located directly using its index. However, inserting or deleting elements in the middle generally requires shifting other elements.
2. ArrayList in Java
ArrayList is a resizable array implementation provided by the Java Collections Framework.
Unlike a normal array, its size can grow or shrink as elements are added or removed.
import java.util.ArrayList;
ArrayList<String> languages = new ArrayList<>();
languages.add("Java");
languages.add("Python");
languages.add("C#");
System.out.println(languages.get(1));
ArrayList is a good choice when applications frequently access elements by index
and need a collection whose size can change dynamically.
3. LinkedList in Java
Java's LinkedList is a doubly linked list implementation.
Each node maintains references to neighboring nodes, allowing elements to be linked together.
import java.util.LinkedList;
LinkedList<String> tasks = new LinkedList<>();
tasks.add("Design");
tasks.add("Development");
tasks.add("Testing");
tasks.addFirst("Planning");
tasks.addLast("Deployment");
System.out.println(tasks);
A linked list can be useful when frequent insertion or removal operations occur at known positions, especially when working with the ends of the list. Indexed access is slower than an array-based list.
4. Stack in Java
A stack follows the Last In, First Out (LIFO) principle. The element added most recently is the first element removed.
Java provides the legacy Stack class, but
Deque, commonly implemented with ArrayDeque,
is generally preferred when implementing a stack in modern Java code.
import java.util.ArrayDeque;
import java.util.Deque;
Deque<String> stack = new ArrayDeque<>();
stack.push("Page 1");
stack.push("Page 2");
stack.push("Page 3");
System.out.println(stack.pop());
System.out.println(stack.peek());
Stacks are commonly used for undo operations, expression processing, recursion-related algorithms, backtracking, and depth-first search.
5. Queue in Java
A queue follows the First In, First Out (FIFO) principle. The element that enters first is normally processed first.
import java.util.ArrayDeque;
import java.util.Queue;
Queue<String> queue = new ArrayDeque<>();
queue.offer("Task A");
queue.offer("Task B");
queue.offer("Task C");
System.out.println(queue.poll());
System.out.println(queue.peek());
Queues are useful for task processing, scheduling, buffering, breadth-first search, and systems where work should be handled in arrival order.
6. Deque in Java
A Deque, or double-ended queue, allows elements to be added
and removed from both the front and the back.
import java.util.ArrayDeque;
import java.util.Deque;
Deque<Integer> numbers = new ArrayDeque<>();
numbers.addFirst(20);
numbers.addLast(30);
numbers.addFirst(10);
System.out.println(numbers.removeFirst());
System.out.println(numbers.removeLast());
A deque can be used as both a queue and a stack. It is also useful in sliding-window algorithms and other problems that require access to both ends.
7. HashMap in Java
HashMap stores data as key-value pairs.
Each key must be unique within the map, while multiple keys can map to equal values.
import java.util.HashMap;
import java.util.Map;
Map<String, Integer> scores = new HashMap<>();
scores.put("Alice", 90);
scores.put("Bob", 85);
scores.put("Charlie", 95);
System.out.println(scores.get("Bob"));
System.out.println(scores.containsKey("Alice"));
Hash maps are commonly used for fast key-based lookups, frequency counting, caching, configuration data, and mapping identifiers to objects.
8. HashSet in Java
HashSet stores unique elements.
Adding the same value more than once does not create another copy of that value.
import java.util.HashSet;
import java.util.Set;
Set<String> technologies = new HashSet<>();
technologies.add("Java");
technologies.add("Python");
technologies.add("Java");
System.out.println(technologies);
System.out.println(technologies.contains("Python"));
A HashSet is useful when uniqueness matters and ordering is not the primary requirement.
It is commonly used for duplicate detection and membership checks.
9. TreeMap and TreeSet
Java also provides tree-based collections for situations where sorted ordering is required.
TreeMap maintains keys in sorted order, while
TreeSet maintains unique elements in sorted order.
import java.util.TreeMap;
import java.util.TreeSet;
TreeMap<Integer, String> students = new TreeMap<>();
students.put(103, "Charlie");
students.put(101, "Alice");
students.put(102, "Bob");
System.out.println(students);
TreeSet<Integer> numbers = new TreeSet<>();
numbers.add(30);
numbers.add(10);
numbers.add(20);
System.out.println(numbers);
Tree-based collections typically provide logarithmic-time operations and are useful when sorted traversal, ordered keys, or range-based operations are important.
10. PriorityQueue in Java
PriorityQueue processes elements according to their priority rather than simply their insertion order.
By default, the smallest element has the highest priority.
import java.util.PriorityQueue;
import java.util.Queue;
Queue<Integer> priorities = new PriorityQueue<>();
priorities.offer(30);
priorities.offer(10);
priorities.offer(20);
while (!priorities.isEmpty()) {
System.out.println(priorities.poll());
}
Priority queues are frequently used in scheduling algorithms, shortest-path algorithms, task prioritization, and heap-based problem solving.
Java Data Structures and Typical Complexity
The following table provides a practical overview of common operations. Actual performance can depend on the operation, implementation, and collection state.
| Data Structure | Access | Search | Insert | Delete |
|---|---|---|---|---|
| Array | O(1) | O(n) | O(n) | O(n) |
| ArrayList | O(1) | O(n) | O(1) amortized at end | O(n) |
| LinkedList | O(n) | O(n) | O(1) after locating position | O(1) after locating node |
| HashMap | O(1) average by key | O(1) average by key | O(1) average | O(1) average |
| HashSet | Not index-based | O(1) average | O(1) average | O(1) average |
| TreeMap | By key | O(log n) | O(log n) | O(log n) |
| TreeSet | Not index-based | O(log n) | O(log n) | O(log n) |
| PriorityQueue | Peek O(1) | O(n) | O(log n) | O(log n) for head |
Choosing the Right Java Data Structure
The right collection depends on how the application needs to access and modify data. Instead of choosing a collection only because it is familiar, consider the operations that will happen most frequently.
- Use an array when the number of elements is fixed and indexed access is important.
- Use ArrayList when you need a resizable indexed collection.
- Use LinkedList when linked-list or deque behavior is specifically useful.
- Use ArrayDeque for most stack and queue implementations.
- Use HashMap when values need to be associated with unique keys.
- Use HashSet when duplicate values should be prevented.
- Use TreeMap when key-value data needs sorted keys.
- Use TreeSet when unique values must remain sorted.
- Use PriorityQueue when the next element should be selected according to priority.
Java Collections Framework
Many of these structures are part of the Java Collections Framework.
Interfaces such as List,
Set, Queue,
and Map allow code to depend on a general behavior
while choosing an appropriate implementation.
import java.util.ArrayList;
import java.util.List;
List<String> users = new ArrayList<>();
users.add("Alice");
users.add("Bob");
users.add("Charlie");
for (String user : users) {
System.out.println(user);
}
Programming against interfaces such as List makes it easier
to change the underlying implementation when application requirements change.
Practical Example: Counting Frequencies
A common data-structure problem is counting how many times each value occurs.
A HashMap is a natural choice because each distinct value can act as a key.
import java.util.HashMap;
import java.util.Map;
int[] numbers = {2, 3, 2, 5, 3, 2};
Map<Integer, Integer> frequency = new HashMap<>();
for (int number : numbers) {
frequency.put(
number,
frequency.getOrDefault(number, 0) + 1
);
}
System.out.println(frequency);
This pattern appears frequently in coding problems involving duplicate detection, frequency analysis, grouping, and lookup operations.
Primitive Values and Generic Collections
Java collections work with objects rather than primitive types.
For example, an ArrayList uses
Integer instead of the primitive
int.
List<Integer> numbers = new ArrayList<>();
numbers.add(10);
numbers.add(20);
numbers.add(30);
int first = numbers.get(0);
Java automatically performs boxing and unboxing in many common cases, allowing primitive values to work conveniently with generic collection types.
Common Mistakes When Using Java Data Structures
- Using an array when the collection needs to grow dynamically.
- Using
LinkedListsimply because insertion is theoretically efficient without considering how the position is located. - Using
HashMapwhen sorted key order is required. - Assuming
HashSetpreserves insertion order. - Using a list when the primary requirement is fast membership checking.
- Using the legacy
Stackclass whenDequeis more appropriate for stack behavior. - Ignoring the memory and performance characteristics of a chosen collection.
- Choosing a data structure before identifying the application's most frequent operations.
Why Data Structures Matter in Java
Data structures are fundamental to writing efficient Java programs. They appear in backend services, APIs, databases, search systems, caching, scheduling, graph processing, and coding interview problems.
Learning the Java Collections Framework together with the underlying concepts of arrays, linked lists, stacks, queues, trees, heaps, graphs, and hashing helps developers understand not only which collection to use, but also why it is appropriate for a particular problem.
Key Takeaways
- Java provides arrays and a rich Collections Framework for organizing data.
ArrayListis useful for resizable indexed collections.HashMapprovides efficient average-case key-based lookup.HashSetis useful when unique values are required.TreeMapandTreeSetmaintain sorted data.ArrayDequecan be used for stack and queue behavior.PriorityQueueis useful when elements must be processed by priority.- The best data structure depends on the operations and performance requirements of the application.