Java

Time Complexity of Java Collections

A practical reference for Java collection time complexity, including ArrayList, LinkedList, HashMap, TreeMap, HashSet, and PriorityQueue.

Java collections are often chosen from memory: ArrayList for lists, HashMap for key-value lookups, HashSet for uniqueness, and PriorityQueue for heap behavior. Time complexity helps explain why those defaults work and where they stop working.

Use this guide as a practical backend reference. Big-O is not a benchmark, but it is a useful way to compare how collection operations grow as data grows.

Quick reference table

CollectionOperationAverageWorstPractical note
ArrayListget by indexO(1)O(1)Fast random access through a backing array.
ArrayListappendO(1)O(n)Usually constant, but resize copies elements.
ArrayListinsert or remove near middleO(n)O(n)Elements after the position must shift.
LinkedListget by indexO(n)O(n)Must walk nodes from the front or back.
LinkedListadd or remove with iterator positionO(1)O(1)Only after the position is already known.
HashMapget or putO(1)O(n)Depends on hash distribution and collision behavior.
TreeMapget or putO(log n)O(log n)Maintains sorted keys with a balanced tree.
HashSetcontains or addO(1)O(n)Backed by hashing, like HashMap keys.
TreeSetcontains or addO(log n)O(log n)Sorted set behavior.
PriorityQueuepeekO(1)O(1)Reads the heap root.
PriorityQueueoffer or pollO(log n)O(log n)Insert and remove adjust heap order.
ArrayDequeadd or remove at endsO(1)O(n)Usually constant, with occasional resize.

ArrayList complexity

ArrayList stores references in a resizable array.

Getting by index is O(1) because the list can jump directly to the position. Iteration is also efficient in practice because the backing array is compact and cache-friendly.

Appending is usually O(1), but occasionally the list must allocate a larger array and copy existing elements. That single append can be O(n), while the amortized cost across many appends is still O(1).

Insertion or removal near the beginning or middle is O(n) because elements must shift.

List<String> users = new ArrayList<>();
users.add("alice");        // amortized O(1)
String first = users.get(0); // O(1)

For most backend service code, ArrayList is the default list choice.

LinkedList complexity

LinkedList stores each element in a node with links to neighboring nodes.

Adding or removing can be O(1) when the code already has the node position through an iterator. But getting an item by index is O(n), and many real operations need a search before the cheap insert or remove can happen.

This is why LinkedList is often slower than people expect. It has higher memory overhead and more pointer chasing than ArrayList.

If you need queue behavior, consider ArrayDeque before reaching for LinkedList.

HashMap complexity

HashMap is usually described as O(1) average-case for get, put, and containsKey.

That average assumes useful hash codes, a reasonable load factor, and a table that is not overloaded. When collisions grow, lookup can require more work. Modern Java includes collision handling improvements, but good key design still matters.

Map<String, Integer> counts = new HashMap<>();
counts.put("java", 1);       // average O(1)
counts.get("java");          // average O(1)

Set an initial capacity when loading a large known number of entries. Changing the load factor is rarely needed for normal application code.

TreeMap and TreeSet complexity

TreeMap and TreeSet keep keys or values sorted. Their common operations are O(log n), not O(1), because the structure maintains ordering.

That cost is worth paying when you need sorted iteration, range queries, nearest keys, or ordered set behavior.

Use HashMap or HashSet when you only need lookup by equality. Use TreeMap or TreeSet when ordering is part of the requirement.

HashSet complexity

HashSet is backed by hashing. add, contains, and remove are average O(1), with similar caveats to HashMap.

Use it when you need uniqueness and fast membership checks.

Set<Long> seenUserIds = new HashSet<>();
if (seenUserIds.add(userId)) {
    // first time seeing this user id
}

This pattern is often cleaner and faster than repeatedly scanning a list for duplicates.

PriorityQueue complexity

PriorityQueue is backed by a heap. Reading the highest-priority item with peek is O(1). Adding with offer and removing with poll are O(log n).

This makes it useful for “give me the next smallest” or “give me the next highest priority” workflows.

PriorityQueue<Integer> queue = new PriorityQueue<>();
queue.offer(30);
queue.offer(10);
queue.offer(20);

int next = queue.poll(); // 10

If you need to repeatedly remove the minimum or maximum item, a priority queue is often a better fit than sorting the whole collection after each change.

Complexity is not the whole decision

Big-O compares growth, but backend code also depends on:

  • Memory overhead.
  • Garbage collection pressure.
  • Cache locality.
  • Thread-safety requirements.
  • Ordering requirements.
  • Serialization shape.
  • Database and network costs around the collection.

For example, LinkedList can look attractive in a simple complexity table, but ArrayList often wins in real application code because it is compact and easy for the CPU to scan.

Common mistakes

The first mistake is choosing a collection only from one operation. A list may be built, searched, sorted, filtered, serialized, and returned. The whole workflow matters.

The second mistake is ignoring average versus worst case. HashMap is usually fast, but poor hash behavior or unusual keys can change the story.

The third mistake is using sorted collections without needing sorted behavior. If ordering does not matter, O(1) average hash lookup is usually a better starting point than O(log n) tree lookup.

The fourth mistake is using a list when a set or map would express the real operation better.

Practical backend defaults

Use these defaults unless a specific requirement says otherwise:

  • Use ArrayList for ordered lists that you append, iterate, and return.
  • Use HashMap for key-value lookup.
  • Use HashSet for uniqueness and membership checks.
  • Use ArrayDeque for stack or queue behavior in single-threaded code.
  • Use PriorityQueue for repeated priority-based removal.
  • Use TreeMap or TreeSet when sorted order or range navigation matters.
  • Use concurrent collections only when ownership and threading require them.

Then measure hot paths with realistic data.

Use the Big-O Cheat Sheet as a quick lookup tool while reviewing code. For deeper explanations, read Big-O Notation Explained, ArrayList vs LinkedList in Java, HashMap Load Factor Explained, and Java PriorityQueue Explained.

You can also browse the Java Collections topic cluster for the full learning path.