Skip to content
intermediatePhase 13 · Java Collections

Choosing the Right Collection

Decision framework for selecting the correct collection for any DSA problem.

45m
0 problems
Topic Progress0%

Decision Framework

Decision Framework

Choosing the right collection is one of the most impactful design decisions. Use this framework:

Step 1: What is the primary access pattern?

  • Indexed access → List
  • Key-value lookup → Map
  • Uniqueness → Set
  • Ordering (FIFO/LIFO/priority) → Queue/Deque/Stack

Step 2: What performance do you need?

  • O(1) lookup → HashMap, HashSet, ArrayDeque
  • O(log n) sorted → TreeMap, TreeSet
  • O(n) scan → any collection

Step 3: What ordering guarantees?

  • No ordering → HashMap, HashSet
  • Insertion order → LinkedHashMap, LinkedHashSet
  • Sorted → TreeMap, TreeSet
  • Access order (LRU) → LinkedHashMap
import java.util.*;

public class DecisionFramework {
    public static void main(String[] args) {
        // Example: Counting words in a document
        // Need: key-value pairs, fast lookup by word
        // Choice: HashMap<String, Integer>
        Map<String, Integer> wordCount = new HashMap<>();
        String[] words = "the cat sat on the mat the cat".split(" ");
        for (String w : words) {
            wordCount.merge(w, 1, Integer::sum);
        }
        System.out.println("Word count: " + wordCount);

        // Example: Find unique characters in order
        // Need: unique elements, insertion order
        // Choice: LinkedHashSet
        String text = "programming";
        Set<Character> uniqueOrdered = new LinkedHashSet<>();
        for (char c : text.toCharArray()) {
            uniqueOrdered.add(c);
        }
        System.out.println("Unique ordered: " + uniqueOrdered);

        // Example: Recently accessed items (LRU)
        // Need: access order, bounded size
        // Choice: LinkedHashMap with accessOrder=true
        LinkedHashMap<String, Integer> lru = new LinkedHashMap<>(16, 0.75f, true) {
            @Override
            protected boolean removeEldestEntry(Map.Entry<String, Integer> eldest) {
                return size() > 3;
            }
        };
        lru.put("a", 1); lru.put("b", 2); lru.put("c", 3);
        lru.get("a"); lru.put("d", 4);
        System.out.println("LRU: " + lru); // {c=3, a=1, d=4}

        // Example: Process tasks by priority
        // Need: priority ordering
        // Choice: PriorityQueue
        PriorityQueue<String> tasks = new PriorityQueue<>();
        tasks.offer("Low priority");
        tasks.offer("High priority");
        tasks.offer("Medium priority");
        System.out.println("Next task: " + tasks.poll()); // High priority
    }
}

Quick reference:

  • Need fast lookup? → HashMap
  • Need sorted? → TreeMap or TreeSet
  • Need queue? → ArrayDeque or PriorityQueue
  • Need indexed access? → ArrayList
  • Need uniqueness with order? → LinkedHashSet

Common Scenarios

Common Scenarios

Real-world collection selection patterns:

import java.util.*;

public class CommonScenarios {
    public static void main(String[] args) {
        // Scenario 1: Cache / Lookup Table
        // Use: HashMap
        Map<String, Object> cache = new HashMap<>();

        // Scenario 2: Duplicate-free collection with order
        // Use: LinkedHashSet
        List<String> input = Arrays.asList("c", "a", "b", "a", "c");
        Set<String> uniqueOrdered = new LinkedHashSet<>(input);
        System.out.println("Unique ordered: " + uniqueOrdered); // [c, a, b]

        // Scenario 3: Sorted unique elements
        // Use: TreeSet
        TreeSet<Integer> sorted = new TreeSet<>(Arrays.asList(5, 3, 1, 4, 2));
        System.out.println("Sorted: " + sorted); // [1, 2, 3, 4, 5]

        // Scenario 4: Stack operations
        // Use: ArrayDeque
        Deque<String> stack = new ArrayDeque<>();
        stack.push("a"); stack.push("b"); stack.push("c");
        System.out.println("Pop: " + stack.pop()); // c

        // Scenario 5: Queue / BFS
        // Use: ArrayDeque
        Queue<Integer> queue = new ArrayDeque<>();
        queue.offer(1); queue.offer(2); queue.offer(3);
        System.out.println("Poll: " + queue.poll()); // 1

        // Scenario 6: Priority scheduling
        // Use: PriorityQueue
        PriorityQueue<int[]> jobs = new PriorityQueue<>(
            Comparator.comparingInt(a -> a[1])
        );
        jobs.offer(new int[]{1, 5}); // [id, priority]
        jobs.offer(new int[]{2, 1});
        jobs.offer(new int[]{3, 3});
        System.out.println("Next job: " + Arrays.toString(jobs.poll())); // [2, 1]

        // Scenario 7: Frequency counting
        // Use: HashMap with merge()
        String text = "hello world hello java world hello";
        Map<String, Integer> freq = new HashMap<>();
        for (String word : text.split(" ")) {
            freq.merge(word, 1, Integer::sum);
        }
        System.out.println("Frequency: " + freq);

        // Scenario 8: Group by key
        // Use: HashMap + List
        List<String> names = Arrays.asList("Alice", "Bob", "Anna", "Bill", "Charlie");
        Map<Character, List<String>> grouped = new HashMap<>();
        for (String name : names) {
            grouped.computeIfAbsent(name.charAt(0), k -> new ArrayList<>()).add(name);
        }
        System.out.println("Grouped: " + grouped);

        // Scenario 9: Thread-safe collection
        // Use: Collections.synchronizedList() or ConcurrentHashMap
        List<String> syncList = Collections.synchronizedList(new ArrayList<>());
        Map<String, Integer> concurrentMap = new java.util.concurrent.ConcurrentHashMap<>();
    }
}

Scenario → Collection mapping:

Scenario Collection
Cache / Lookup HashMap
Unique + Order LinkedHashSet
Sorted Unique TreeSet
Stack ArrayDeque
Queue / BFS ArrayDeque
Priority Queue PriorityQueue
Frequency Count HashMap + merge()
Group By Key HashMap + List
LRU Cache LinkedHashMap
Thread-Safe ConcurrentHashMap

DSA Patterns

DSA Patterns and Collection Choices

Many algorithm patterns have specific collection requirements:

import java.util.*;

public class DSAPatterns {
    // Pattern 1: Two Sum - HashMap for O(n) lookup
    public static int[] twoSum(int[] nums, int target) {
        Map<Integer, Integer> map = new HashMap<>();
        for (int i = 0; i < nums.length; i++) {
            int complement = target - nums[i];
            if (map.containsKey(complement)) {
                return new int[]{map.get(complement), i};
            }
            map.put(nums[i], i);
        }
        return new int[]{};
    }

    // Pattern 2: Top K - min-heap of size K
    public static List<Integer> topK(int[] nums, int k) {
        PriorityQueue<Integer> minHeap = new PriorityQueue<>();
        for (int num : nums) {
            minHeap.offer(num);
            if (minHeap.size() > k) minHeap.poll();
        }
        return new ArrayList<>(minHeap);
    }

    // Pattern 3: BFS - Queue
    public static List<Integer> bfs(Map<Integer, List<Integer>> graph, int start) {
        Queue<Integer> queue = new ArrayDeque<>();
        Set<Integer> visited = new HashSet<>();
        List<Integer> order = new ArrayList<>();
        queue.offer(start);
        visited.add(start);
        while (!queue.isEmpty()) {
            int node = queue.poll();
            order.add(node);
            for (int neighbor : graph.getOrDefault(node, Collections.emptyList())) {
                if (!visited.contains(neighbor)) {
                    queue.offer(neighbor);
                    visited.add(neighbor);
                }
            }
        }
        return order;
    }

    // Pattern 4: DFS - Stack (or recursion)
    public static List<Integer> dfs(Map<Integer, List<Integer>> graph, int start) {
        Deque<Integer> stack = new ArrayDeque<>();
        Set<Integer> visited = new HashSet<>();
        List<Integer> order = new ArrayList<>();
        stack.push(start);
        while (!stack.isEmpty()) {
            int node = stack.pop();
            if (visited.contains(node)) continue;
            visited.add(node);
            order.add(node);
            for (int neighbor : graph.getOrDefault(node, Collections.emptyList())) {
                if (!visited.contains(neighbor)) {
                    stack.push(neighbor);
                }
            }
        }
        return order;
    }

    // Pattern 5: Sliding Window - Deque
    public static int[] slidingWindowMax(int[] nums, int k) {
        Deque<Integer> deque = new ArrayDeque<>();
        int[] result = new int[nums.length - k + 1];
        for (int i = 0; i < nums.length; i++) {
            while (!deque.isEmpty() && deque.peekFirst() < i - k + 1) {
                deque.pollFirst();
            }
            while (!deque.isEmpty() && nums[deque.peekLast()] < nums[i]) {
                deque.pollLast();
            }
            deque.offerLast(i);
            if (i >= k - 1) result[i - k + 1] = nums[deque.peekFirst()];
        }
        return result;
    }

    public static void main(String[] args) {
        System.out.println("Two Sum: " + Arrays.toString(twoSum(new int[]{2,7,11,15}, 9)));
        System.out.println("Top K: " + topK(new int[]{3,1,5,12,2}, 2));
        System.out.println("Sliding max: " + Arrays.toString(slidingWindowMax(new int[]{1,3,-1,-3,5,3,6,7}, 3)));
    }
}

DSA → Collection mapping:

Pattern Collections Used
Two Sum / Hash Map HashMap
Top K Elements PriorityQueue (min-heap of size K)
BFS ArrayDeque (Queue) + HashSet (visited)
DFS ArrayDeque (Stack) + HashSet (visited)
Sliding Window Max ArrayDeque (monotonic deque)
Merge K Sorted PriorityQueue + indices
Median Stream Two PriorityQueues (max + min heap)
LRU Cache LinkedHashMap (access order)

Practice Problems

0/3solved
Choosing the Right Collection Implementation

Implement Choosing the Right Collection in Java. Include proper error handling and follow Java conventions.

Solution
// Java implementation:
// 1. Proper class structure
// 2. Error handling
// 3. JavaDoc comments
// 4. Unit tests
Choosing the Right Collection Time Complexity

Analyze the time and space complexity of Choosing the Right Collection operations. Optimize for common use cases.

Solution
// Complexity analysis:
// - Time: depends on implementation
// - Space: consider auxiliary space
// - Trade-offs between time and space
Choosing the Right Collection Java Best Practices

Apply Java best practices when using Choosing the Right Collection. Consider immutability, thread safety, and clean code.

Solution
// Best practices:
// 1. Use immutable objects where possible
// 2. Thread-safe implementations
// 3. Proper exception handling
// 4. Resource management (try-with-resources)
// 5. JavaDoc documentation

Quiz

1. You need to store user preferences where lookup by key is the most frequent operation. Which collection should you use?

Question 1 options

2. You need to process tasks in order of priority, not insertion order. Which collection should you use?

Question 2 options

3. You need to maintain insertion order and reject duplicates. Which collection should you use?

Question 3 options

4. Which collection is best for implementing a stack?

Question 4 options

Flashcards

Question

What collection should you use for fast key-value lookup?

Answer

HashMap. It provides O(1) average time for get() and put(). Use TreeMap if you need sorted keys (O(log n)).

Question

When would you use ArrayDeque over ArrayList?

Answer

When you need frequent insert/remove at both ends. ArrayDeque is O(1) at both ends. ArrayList is O(1) at end but O(n) at beginning.

Question

What collection is used for BFS and DFS algorithms?

Answer

BFS uses ArrayDeque as a Queue (FIFO). DFS uses ArrayDeque as a Stack (LIFO). Both typically use a HashSet to track visited nodes.

Question

How do you choose between HashMap, TreeMap, and LinkedHashMap?

Answer

HashMap: fastest, no ordering. LinkedHashMap: insertion/access order. TreeMap: sorted by key, O(log n). Choose based on ordering needs.

Question

What is Choosing the Right Collection?

Answer

Choosing the Right Collection is a key concept in Java programming.

Revision Notes

Key Takeaways

  • 1.Match collection to your primary access pattern
  • 2.Default to ArrayList for indexed access, HashMap for key-value lookup
  • 3.Use ArrayDeque for both stack and queue operations
  • 4.Choose LinkedHashMap when insertion order matters
  • 5.Use PriorityQueue when priority ordering is needed

Interview Tips

  • Explain your collection choice with time complexity justification
  • Know the performance tradeoffs between HashMap, TreeMap, LinkedHashMap
  • Discuss when to use ArrayDeque vs LinkedList for stack/queue
  • Be ready to identify the right collection for any DSA pattern

Cheat Sheet

Choosing Collections Decision Guide

Primary Access Pattern

  • Indexed access → ArrayList
  • Key-value lookup → HashMap
  • Uniqueness → HashSet
  • Sorted unique → TreeSet
  • FIFO queue → ArrayDeque
  • Priority → PriorityQueue
  • Stack → ArrayDeque

Performance

  • O(1) → HashMap, HashSet, ArrayDeque
  • O(log n) → TreeMap, TreeSet, PriorityQueue
  • O(n) → ArrayList insert/remove, LinkedList get

Ordering

  • None → HashMap, HashSet
  • Insertion → LinkedHashMap, LinkedHashSet
  • Sorted → TreeMap, TreeSet
  • Access (LRU) → LinkedHashMap

DSA Patterns

  • Two Sum → HashMap
  • Top K → PriorityQueue
  • BFS → ArrayDeque + HashSet
  • DFS → ArrayDeque + HashSet
  • Sliding Window → ArrayDeque