Time Complexity of Common Operations — Python, Java, JavaScript, C++

A per-language time-complexity reference for the data-structure operations coding interviews actually test — dict / HashMap / Map / unordered_map, list / ArrayList / Array / vector, and the deque, heap, and sorted-structure lines side by side.

The lookup reference for a single question: "how fast is <operation> on <data structure> in <language>?" Same operations across four languages, in tables you can screenshot. When you need the algorithm-level view — sorting complexities, graph traversals, language gotchas as prose — see the Big-O cheat sheet. This post is the tighter, comparison-focused sibling.

How to read the tables

  • Amortised (*) means averaged over a sequence of operations. list.append is amortised O(1) even though a single call might resize the backing array in O(n); over any long sequence, the average is O(1).
  • Worst case in parentheses where a common failure mode diverges from the amortised bound. HashMap.get in Java is amortised O(1) but O(log n) worst-case per bucket post-Java-8 (tree-bucket fallback).
  • n = element count unless otherwise noted. k is a scale variable that isn't n (key length, distinct values, batch size).

Hash map — dict / HashMap / Map / unordered_map

OperationPython dictJava HashMapJavaScript MapC++ unordered_map
get(k) / d[k]O(1)*O(1)* (O(log n) worst)O(1)*O(1)* (O(n) worst)
put(k, v) / d[k] = vO(1)*O(1)*O(1)*O(1)*
delete(k) / del d[k]O(1)*O(1)*O(1)*O(1)*
contains(k) / k in dO(1)*O(1)*O(1)*O(1)*
IterationO(n)O(n + capacity)O(n)O(n + capacity)
CopyO(n)O(n)O(n)O(n)

Per-language notes:

  • Python: insertion-ordered since 3.7. dict.get(k, default) never raises. dict.pop(k, default) combines get + delete.
  • Java: post-Java-8, buckets that fill with collisions convert to red-black trees — worst case per bucket becomes O(log n) instead of O(n). LinkedHashMap preserves insertion order at a small constant-factor cost.
  • JavaScript: Map is spec'd as sublinear. Keys can be any type (unlike Object[k] which coerces to string). Iteration is insertion-ordered.
  • C++: unordered_map amortised O(1) but O(n) worst case on adversarial hashing (each bucket a linked list, no tree fallback). Rehashes when load factor exceeds max_load_factor (default 1.0). For guaranteed O(log n), use std::map (balanced BST).

Hash set — set / HashSet / Set / unordered_set

OperationPython setJava HashSetJavaScript SetC++ unordered_set
add(x)O(1)*O(1)*O(1)*O(1)*
remove(x)O(1)*O(1)*O(1)*O(1)*
contains(x) / x in sO(1)*O(1)*O(1)*O(1)*
Union a | bO(len(a) + len(b))O(a + b)manual, O(a + b)O(a + b)
Intersection a & bO(min(a, b))via retainAll — O(a + b)manual, O(a + b)O(a + b)
Difference a - bO(len(a))via removeAll — O(a + b)manual, O(a + b)O(a + b)
IterationO(n)O(n)O(n)O(n)

Same worst-case caveats as the hash-map row — collision-heavy inputs degrade to O(n) per operation in Python/C++, O(log n) in Java-8+.

Dynamic array — list / ArrayList / Array / vector

OperationPython listJava ArrayListJavaScript ArrayC++ vector
[i] accessO(1)O(1)O(1)O(1)
[i] = x assignO(1)O(1)O(1)O(1)
append(x) / push(x)O(1)*O(1)*O(1)*O(1)*
pop() (end)O(1)O(1)O(1)O(1)
pop(0) / shift()O(n)O(n)O(n)O(n)
insert(0, x) / unshift(x)O(n)O(n)O(n)O(n)
insert(i, x) (mid)O(n)O(n)O(n)O(n)
remove(x) (by value)O(n)O(n)O(n)O(n)
contains(x) / x in aO(n)O(n)O(n)O(n)
sort()O(n log n) TimsortO(n log n) Timsort/dual-pivot QS†O(n log n) TimSortO(n log n) IntroSort
Slice / subarray copyO(k)O(k)O(k)O(k)
ReverseO(n)O(n)O(n)O(n)
Concatenate a + bO(a + b)O(a + b)O(a + b)O(a + b)

† Java: Arrays.sort(int[]) uses dual-pivot Quicksort — O(n²) worst case. Arrays.sort(Object[]) and Collections.sort use Timsort — O(n log n) worst case.

The bolded rows are the interview-classic traps. Every one of them turns an "O(n) solution" into an accidental O(n²):

  • Using list.pop(0) in a BFS queue → per-op O(n), overall O(V²). Reach for collections.deque.
  • Membership testing with x in list inside a loop → O(n · loop_iterations). Convert to a set first.
  • Building a string by repeated concat with += → O(n²) because str is immutable. Collect + join.

Deque — double-ended queue

OperationPython dequeJava ArrayDequeJavaScript (no built-in)C++ deque
append(x) rightO(1)O(1)Array.push — O(1)*O(1)
appendleft(x)O(1)O(1)Array.unshift — O(n)O(1)
pop() rightO(1)O(1)Array.pop — O(1)O(1)
popleft()O(1)O(1)Array.shift — O(n)O(1)
Random access [i]O(n)O(1)O(1)O(1)
IterationO(n)O(n)O(n)O(n)

Notes:

  • Python deque: O(1) at both ends, O(n) for arbitrary indexing. If you need both O(1) ends and random access, use two lists or roll a fixed-size ring buffer.
  • Java ArrayDeque: prefer over the legacy Stack (synchronised, slow) and LinkedList (poor cache locality) whenever you need stack or queue semantics.
  • JavaScript: no native deque. Array gives O(1) at the right only; using unshift/shift for the left is O(n). For BFS, use Array with a head-index counter, or a small custom class.
  • C++ std::deque: O(1) at both ends and O(1) random access — it's not a linked list, it's a segmented array. Worse cache locality than std::vector but strictly more flexible.

Binary heap — priority queue

OperationPython heapqJava PriorityQueueJavaScript (no built-in)C++ priority_queue
PushO(log n)O(log n)O(log n)
PopO(log n)O(log n)O(log n)
Peek (top)O(1)O(1)O(1)
Build from arrayO(n) via heapifyO(n) via constructorO(n) via constructor
nlargest(k, iter)O(n log k)manualmanual
Delete arbitraryO(n)O(n)O(n)
Increase / decrease keyO(n) (Python) / O(n) (default)O(n)O(n)

Notes:

  • Python heapq: min-heap only. For a max-heap, push negated values (-num) or (-priority, item). heapq.heapify(list) is O(n), not O(n log n) — a common interview claim to get right.
  • Java PriorityQueue: min-heap by default. Pass Comparator.reverseOrder() for a max-heap, or a custom comparator for tuples/objects.
  • JavaScript: no built-in heap. Common workaround is a small class over an array; libraries like js-priority-queue exist. Rolling your own is a fifteen-line exercise.
  • C++ priority_queue: max-heap by default. For min-heap: priority_queue<int, vector<int>, greater<int>>. std::make_heap on a container is O(n).

For "decrease key" (Dijkstra's classic need), the standard-library heaps don't support it directly in O(log n). Workaround: push the new (smaller) priority and let the old entry expire on pop with a stale-check. That's what most interview Dijkstra solutions do.

Sorted structures — balanced BST equivalents

Not all languages have one built in. When they're missing, you either roll a balanced BST yourself (rare in interviews) or reach for a sorted-array-plus-binary-search combination.

OperationPython (no built-in)Java TreeMap / TreeSetJavaScript (no built-in)C++ std::map / std::set
Insertvia sortedcontainers.SortedList: O(log n)O(log n)manual, O(log n) if rolledO(log n)
DeleteO(log n)O(log n)manualO(log n)
Lookup by keyO(log n)O(log n)manualO(log n)
Floor / ceiling / next / prevO(log n)O(log n) via floorKey etc.O(log n) via lower_bound etc.
Kth smallestO(log n) with augmented tree; O(k) with iterationO(k) with iterationO(k) with iteration
In-order iterationO(n)O(n)O(n)

Python competitive-coding standard: from sortedcontainers import SortedList, SortedDict, SortedSet. Non-stdlib but pip-installable and widely used. LeetCode ships it in their Python runtime.

Linked list

OperationPython (no built-in — use deque)Java LinkedListJavaScript (no built-in)C++ std::list (doubly)
Access [i]O(n)O(n)
Insert at headO(1)O(1)
Insert at tailO(1)O(1)
Insert at arbitrary position (with iterator)O(1)O(1)
Delete (with node pointer)O(1)O(1)
SearchO(n)O(n)

Python doesn't ship a linked list — collections.deque is a segmented ring buffer that covers most linked-list use cases with better cache locality. Java's LinkedList is almost always worse in practice than ArrayList or ArrayDeque; use it only when you specifically need O(1) mid-list splicing via an iterator.

String

Strings are their own topic because they're immutable in Python, Java, and JavaScript. Every "modification" allocates a new string.

OperationPython strJava StringJavaScript stringC++ std::string
[i] accessO(1)O(1)O(1)O(1)
LengthO(1)O(1)O(1)O(1)
Concatenate a + bO(a + b)O(a + b)O(a + b)†O(a + b)
Concat in loop with +=O(n²)O(n²)O(n)† modern enginesO(n²) if reallocating
Slice / substringO(k)O(k) post-Java-7O(k)O(k)
find / indexOf (naïve)O(n · m)O(n · m)O(n · m)O(n · m)
find (with KMP-like optimisations)implementation-dependentO(n) in some cases
replace (all)O(n)O(n)O(n)O(n)
splitO(n)O(n)O(n)O(n)
join(list, sep)O(total_length)O(total_length)O(total_length)O(total_length)

† JavaScript engines use rope structures for string concatenation, so a + b is often O(1) internally. But relying on this optimisation across engines is fragile; the safe rule is still "collect into an array and .join('')".

The safe rule in every language: never build a string via repeated concat inside a loop. Collect chunks in a list/array/StringBuilder, then join in one call.

  • Python: "".join(pieces)
  • Java: StringBuilder with .append(), then .toString()
  • JavaScript: array.push(chunk); ...; array.join("")
  • C++: std::string with .reserve() up front, then .append() — or std::ostringstream

Common surprises

Grouped by "which O(n) trap have I probably fallen into":

"Why is my BFS O(V²)?" — You used list.pop(0) (Python), Array.shift() (JavaScript), or a LinkedList.pollFirst() where ArrayDeque.pollFirst() was expected. See the BFS vs DFS post.

"Why is my sliding-window O(n · k)?" — You wrote if x in window where window is a list, not a set or dict. x in list is O(n).

"Why is my string-building solution TLE-ing?" — Repeated s = s + t inside a loop is O(n²) in Python and Java. Use "".join(pieces) or StringBuilder.

"Why is my Dijkstra slower than the analyst-expected bound?" — Python's heapq lacks decrease-key. Push duplicate entries with fresher priorities and skip stale ones on pop. That's the amortised O((V + E) log V) implementation — the theoretical O((V + E) log V) with true decrease-key needs a Fibonacci heap and is rarely worth writing.

"Why does my Java HashMap have such a stable worst case?" — Post-Java-8 tree buckets. When collisions in one bucket exceed a threshold (TREEIFY_THRESHOLD = 8), the bucket becomes a red-black tree — O(log n) per bucket, not O(n).

How to argue in an interview

Interviewers grade the argument, not just the answer. The three-line script that works in every language:

  1. Name the operation and structure by their concrete type. "Reading nums[i] on a Python list is O(1)." Not "array indexing is O(1)" — you'll get pressed on whether it's a Python list or a NumPy array or a linked-list-in-disguise.
  2. State whether the bound is amortised or worst-case. "Amortised O(1) for dict.get — hash-map get averages O(1), even if a single collision-heavy lookup could be O(n)."
  3. If the worst case matters for the problem, defend it. "Assuming Python's default hash function isn't adversarially chosen, this is O(n) overall. If the interviewer expects worst-case bounds, I'd downgrade to O(n²)."

Interviewers rarely push all the way into hash-collision theory. They want to see you know the number and the caveat.

Cross-references

The tables aren't wallpaper. They're the invariants every pattern is cashing in on.

Algotrek

A tutor for every algorithm pattern. Visual walkthroughs, smart hints, feedback that names your mistake.

Sign up

The path

Adaptive. Teaching-first. Built for engineers who'd rather understand than grind.

© 2026 Algotrek. All rights reserved. Live