12 essential data structures for Java developers. Each one explained with Big O complexity, animated visuals, and real code samples you can copy.
T[]Fixed-size, contiguous block of memory. Elements are stored sequentially and accessed by index in constant time. The foundation of most other data structures.
arr[0] = 7
Complexity
When to use
// Declaration and initialization
int[] numbers = new int[5];
int[] primes = {2, 3, 5, 7, 11};
// Access and modify
primes[0] = 13; // O(1)
int third = primes[2]; // O(1) - direct index access
// Iterate
for (int p : primes)
System.out.print(p + " ");
// Search and sort
Arrays.sort(primes); // O(n log n)
int idx = Arrays.binarySearch(primes, 7); // O(log n)
String[] words = {"cat", "ant", "bat"};
Arrays.sort(words); // O(n log n)ArrayList<T>Resizable array that automatically grows when capacity is exceeded. The most commonly used data structure in most languages. Doubles its internal storage when full, giving amortized O(1) appends.
arr[0] = 7
Complexity
When to use
List<String> names = new ArrayList<>(List.of("Alice", "Bob"));
names.add("Charlie"); // O(1) amortized
names.add(1, "Diana"); // O(n) - shifts elements right
names.remove("Bob"); // O(n) - shifts elements left
names.remove(0); // O(n) - shifts elements left
boolean has = names.contains("Charlie"); // O(n)
int idx = names.indexOf("Diana"); // O(n)
String first = names.get(0); // O(1)
// Sort and iterate
Collections.sort(names); // O(n log n)
names.forEach(n -> System.out.print(n + " "));ArrayDeque<T>Last-In-First-Out (LIFO) collection. Only the top element is accessible. Used for tracking state that must be unwound in reverse order.
Push 10
Complexity
When to use
Deque<Integer> stack = new ArrayDeque<>(); // preferred over Stack
stack.push(10); // O(1)
stack.push(20);
stack.push(30);
int top = stack.peek(); // O(1) - 30, no removal
int val = stack.pop(); // O(1) - 30, removed
System.out.println(stack.size()); // 2
System.out.println(stack.contains(10)); // O(n) - true
// Classic interview pattern: valid parentheses
boolean isValid(String s) {
Deque<Character> st = new ArrayDeque<>();
for (char c : s.toCharArray()) {
if (c == '(') st.push(')');
else if (st.isEmpty() || st.pop() != c) return false;
}
return st.isEmpty();
}Queue<T>First-In-First-Out (FIFO) collection. Elements are added at the back and removed from the front. Fundamental for breadth-first processing.
Enqueue 10
Complexity
When to use
Queue<String> queue = new ArrayDeque<>();
queue.offer("Task A"); // O(1)
queue.offer("Task B");
queue.offer("Task C");
String first = queue.peek(); // O(1) - "Task A"
String next = queue.poll(); // O(1) - "Task A", removed
// Classic interview pattern: BFS level-order traversal
void bfs(TreeNode root) {
Queue<TreeNode> q = new ArrayDeque<>();
q.offer(root);
while (!q.isEmpty()) {
TreeNode node = q.poll();
System.out.print(node.val + " ");
if (node.left != null) q.offer(node.left);
if (node.right != null) q.offer(node.right);
}
}HashMap<K,V>Maps keys to values using a hash function for near-constant-time lookups. The single most important data structure for coding interviews. Every language has a built-in implementation.
hash("age") = 0
Complexity
When to use
Map<String, Integer> map = new HashMap<>();
map.put("apple", 3); // O(1)
map.put("banana", 5); // O(1)
map.put("apple", 10); // O(1) update
boolean has = map.containsKey("banana"); // O(1) - true
map.remove("banana"); // O(1)
// Safe lookup with default
int count = map.getOrDefault("cherry", 0); // O(1) - 0
// Classic interview pattern: Two Sum
int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> seen = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
int need = target - nums[i];
if (seen.containsKey(need))
return new int[]{seen.get(need), i};
seen.put(nums[i], i);
}
return new int[]{};
}HashSet<T>Unordered collection of unique elements. Uses hashing internally for O(1) membership testing. Supports mathematical set operations like union, intersection, and difference.
hash("age") = 0
Complexity
When to use
Set<Integer> set = new HashSet<>(Set.of(1, 2, 3, 4, 5));
set.add(6); // O(1) - true (added)
set.add(3); // O(1) - false (duplicate)
set.remove(1); // O(1)
boolean has = set.contains(4); // O(1) - true
// Set operations
Set<Integer> other = new HashSet<>(Set.of(4, 5, 6, 7));
set.retainAll(other); // intersection - set = {4, 5, 6}
set.addAll(other); // union - set = {4, 5, 6, 7}
set.removeAll(other); // difference - set = {}
// Classic interview pattern: contains duplicate
boolean containsDuplicate(int[] nums) {
Set<Integer> seen = new HashSet<>();
for (int n : nums) if (!seen.add(n)) return true;
return false;
}LinkedList<T>Sequence of nodes where each node points to the next (singly linked) or both next and previous (doubly linked). Efficient insertion and deletion at any known position, but no index-based access.
traversing: 5
Complexity
When to use
LinkedList<Integer> list = new LinkedList<>();
list.addLast(10); // O(1)
list.addLast(20);
list.addFirst(5); // O(1)
list.add(1, 15); // O(n) - traverse to index
boolean found = list.contains(20); // O(n)
list.remove(Integer.valueOf(20)); // O(n) - find then unlink
// Deque operations (double-ended queue)
int first = list.peekFirst(); // O(1) - 5
int last = list.peekLast(); // O(1) - 10
// Iterate
for (int val : list)
System.out.print(val + " "); // 5 15 10
// LRU Cache pattern: LinkedList + HashMap
// HashMap for O(1) lookup, LinkedList for O(1) reorderTreeSet<T>Collection of unique elements maintained in sorted order, typically backed by a balanced binary search tree (red-black tree). Supports range queries and O(log n) min/max.
search(8)
Complexity
When to use
TreeSet<Integer> sorted = new TreeSet<>(List.of(5, 3, 8, 1, 9));
// Internal order: 1, 3, 5, 8, 9 (red-black tree)
sorted.add(4); // O(log n)
sorted.remove(3); // O(log n)
boolean has = sorted.contains(8); // O(log n) - true
int min = sorted.first(); // O(log n) - 1
int max = sorted.last(); // O(log n) - 9
// Range queries
SortedSet<Integer> range = sorted.subSet(4, 9); // [4, 9)
System.out.println(sorted.floor(6)); // 5 - greatest <= 6
System.out.println(sorted.ceiling(6)); // 8 - smallest >= 6
System.out.println(sorted.higher(5)); // 8 - strictly greaterTreeMap<K,V>Key-value pairs maintained in sorted key order, typically backed by a balanced BST. Enables ordered iteration and range lookups that hash maps cannot provide.
search(8)
Complexity
When to use
TreeMap<String, Integer> tm = new TreeMap<>();
tm.put("banana", 2); // O(log n)
tm.put("apple", 5);
tm.put("cherry", 1);
tm.put("date", 3); // O(log n)
// Iterates in sorted key order
for (var entry : tm.entrySet())
System.out.println(entry.getKey() + ": " + entry.getValue());
// apple: 5, banana: 2, cherry: 1, date: 3
// Navigation methods
String firstKey = tm.firstKey(); // "apple"
String floorKey = tm.floorKey("cat"); // "banana" (greatest <= "cat")
Map.Entry<String, Integer> low = tm.firstEntry(); // apple=5
SortedMap<String, Integer> sub = tm.subMap("apple", "cherry");PriorityQueue<T>Collection where elements are dequeued by priority rather than insertion order. Typically implemented as a binary heap. Essential for shortest-path algorithms and top-K problems.
min-heap
Complexity
When to use
// Min-heap by default
PriorityQueue<Integer> pq = new PriorityQueue<>();
pq.offer(30); // O(log n)
pq.offer(10);
pq.offer(20);
int min = pq.peek(); // O(1) - 10
int next = pq.poll(); // O(log n) - 10, removed
// Max-heap using reversed comparator
PriorityQueue<Integer> maxPQ = new PriorityQueue<>(
Comparator.reverseOrder());
// Classic interview pattern: K closest points
int[][] kClosest(int[][] points, int k) {
PriorityQueue<int[]> pq = new PriorityQueue<>(
(a, b) -> (a[0]*a[0]+a[1]*a[1]) - (b[0]*b[0]+b[1]*b[1]));
for (int[] p : points) pq.offer(p);
int[][] res = new int[k][];
for (int i = 0; i < k; i++) res[i] = pq.poll();
return res;
}ConcurrentHashMap<K,V>Thread-safe hash map designed for concurrent read/write access from multiple threads. Uses fine-grained locking or lock-free techniques instead of a single global lock.
hash("age") = 0
Complexity
When to use
ConcurrentHashMap<String, Integer> cache = new ConcurrentHashMap<>();
// Thread-safe atomic operations
cache.put("hits", 0); // O(1)
cache.putIfAbsent("sessions", 0); // O(1) - only if absent
cache.merge("hits", 1, Integer::sum); // O(1) - atomic increment
// Compute if absent - factory called once even under contention
int val = cache.computeIfAbsent("users", key -> {
return expensiveComputation(key); // called only if key missing
});
// Safe bulk operations
cache.forEach(1, (k, v) ->
System.out.println(k + ": " + v));
// Atomic compute
cache.compute("hits", (k, v) -> v == null ? 1 : v + 1);ByteBufferZero-copy view over a contiguous region of memory. Lets you reference a portion of an array or buffer without allocating new memory. Critical for performance-sensitive parsing and processing.
Span[0..3] = [1, 2, 3]
Complexity
When to use
// Arrays.copyOfRange - creates a new sub-array (copies data)
int[] data = {1, 2, 3, 4, 5};
int[] slice = Arrays.copyOfRange(data, 1, 4); // O(k) - [2, 3, 4]
// ByteBuffer - closest to Span, a view over byte memory
ByteBuffer buf = ByteBuffer.allocate(256); // heap buffer
ByteBuffer direct = ByteBuffer.allocateDirect(256); // off-heap
buf.putInt(42); // O(1) - write at position
buf.flip(); // switch from write to read mode
int val = buf.getInt(); // O(1) - read 42
// Slice creates a shared view (no copy, like Span)
ByteBuffer view = buf.slice(); // O(1) - shared underlying memory
// Java has no stack-allocated Span equivalent
// Use ByteBuffer for zero-copy views of byte data
// Use List.subList() for zero-copy list viewsAverage-case time complexity. * = amortized.
| Structure | Access | Search | Insert | Delete |
|---|---|---|---|---|
| Array | O(1) | O(n) | O(n) | O(n) |
| Dynamic Array | O(1) | O(n) | O(1)* | O(n) |
| Stack | O(n) | O(n) | O(1)* | O(1) |
| Queue | O(n) | O(n) | O(1)* | O(1) |
| Hash Map | O(1) | O(1) | O(1)* | O(1) |
| Hash Set | N/A | O(1) | O(1)* | O(1) |
| Linked List | O(n) | O(n) | O(1) | O(1) |
| Sorted Set | O(n) | O(log n) | O(log n) | O(log n) |
| Sorted Map | O(log n) | O(log n) | O(log n) | O(log n) |
| Priority Queue | O(n) | O(n) | O(log n) | O(log n) |
| Concurrent Map | O(1) | O(1) | O(1)* | O(1) |
| Memory View | O(1) | O(n) | N/A | N/A |
| I need to... | Use |
|---|---|
| Store items by index, resize dynamically | List / Dynamic Array |
| Map keys to values with O(1) lookup | HashMap / Dictionary |
| Track unique items, check existence in O(1) | HashSet / Set |
| Last-in-first-out (undo, DFS, brackets) | Stack |
| First-in-first-out (BFS, task queues) | Queue |
| Keep elements sorted at all times | SortedSet / TreeSet |
| Process items by priority (Dijkstra, top-K) | PriorityQueue / Heap |
| Insert/delete at a known position in O(1) | LinkedList |
| Sorted key-value pairs | SortedDictionary / TreeMap |
| Thread-safe shared cache | ConcurrentDictionary |
| Slice arrays/strings without copying | Span / Slice / memoryview |
The most commonly used are dynamic arrays (List/ArrayList/vector), hash maps (Dictionary/HashMap/dict), and hash sets. For interviews, also know stacks, queues, trees, and priority queues. These cover 90%+ of coding interview problems.
Start with the dynamic array and hash map. Together they solve the majority of interview problems. Then learn stacks (for DFS, bracket matching) and queues (for BFS). After that, tackle trees, heaps, and graphs.
No. Big O measures algorithmic complexity, not language-specific performance. A hash map lookup is O(1) whether you use Python dict, Java HashMap, or C# Dictionary. Constant factors differ (C++ is faster than Python in wall-clock time), but Big O is the same.
Yes. java.util.PriorityQueue is a min-heap. For a max-heap, pass Collections.reverseOrder() as the comparator.
Practice with an AI interviewer that asks you to implement, optimize, and explain data structure choices in real time.
Try a free mock interviewarrow_forward