Coding Interview Patterns Module 33 – Pattern recognition and mixed practice
Operation costs across five languages
What common operations on lists, maps, sets, heaps and strings cost in Python, JavaScript, Java, C++ and Go, with the hidden linear costs measured.
What you will learn
- Find the cost of common standard-library operations in each language
- Identify hidden O(n) operations such as list.pop(0), shift and insertion in the middle
- Cite official documentation for each cost
- Measure an operation's growth by doubling n and timing it
Before you start
What you’ll learn · Before you start
What you will learn
- Find the cost of common standard-library operations in each language
- Identify hidden O(n) operations such as list.pop(0), shift and insertion in the middle
- Cite official documentation for each cost
- Measure an operation's growth by doubling n and timing it
Before you start
On this page
Draining an 80,000-item Python list with pop(0) moves about 3.2 billion items; a deque moves none. Most
standard-library operations cost what you would guess: reading by index is O(1), appending is O(1)
amortised cost: The cost of an operation averaged over a long run of them. An append to an array list is usually one step, and the occasional copy into a larger array, spread over the appends that filled it, keeps n appends at O(n) together., a hash lookup is O(1) expected cost: The cost an operation has on average over typical inputs, such as O(1) for a hash-table lookup whose keys spread over the buckets. Inputs chosen to defeat it, such as keys that all land in one bucket, can cost far more., and sorting is O(n log n). The costs that change the complexity of a solution are the
few that look constant and are linear: taking the first item off a Python list, a JavaScript array or a Java
ArrayList, inserting into the middle of any array, asking whether a value is in a list, and in Java and Go, building a
string with +=. One of them inside a loop over n items turns an O(n) plan into O(n²). This page puts the costs of
five languages in one table, each from the language’s own documentation, and measures the two hidden costs that
coding rounds catch most often.
Most costs agree across the five languages; a few differ
Most operations cost the same in all five languages; the third column says where one differs.
| Operation | Usual cost | Where the languages differ |
|---|---|---|
| Read or write by index | O(1) | Nowhere |
| Append at the end | O(1) amortised | Nowhere |
| Remove the first item | O(n) on an array | O(1) with a Python deque, a Java ArrayDeque, a C++ deque, or Go’s s = s[1:] |
| Insert or delete in the middle | O(n) | Nowhere |
| Is x in an unsorted list? | O(n) | Nowhere |
| Hash map get and put | O(1) expected | JavaScript’s specification asks only that a Map be sublinear on average |
| The largest key ≤ t | O(log n) with an sorted map: A map that keeps its keys in sorted order while entries are added and removed, so it can give the smallest key, the largest, or the nearest one above or below a value, each in O(log n). Java's TreeMap and C++'s std::map are sorted maps. More in “Choosing your interview language” | Only Java (TreeMap) and C++ (std::map) have one; elsewhere, binary search a sorted list |
| Heap push and pop | O(log n) | JavaScript has no heap |
| Sort n items | O(n log n) | JavaScript’s specification sets no cost; V8, the engine of Node.js, uses Timsort |
| Build a string from pieces | Linear with a builder or a join | += in a loop is quadratic in Java and Go |
| Take a part of a list or string | O(k), a copy | O(1) views: Java’s subList, Go’s slices, C++’s string_view |
The tools behind each row, by language:
- Python: list (
append,pop(),pop(0),insert),collections.deque, dict and set,bisecton a sorted list,heapq,str.join. - JavaScript: arrays (
push,pop,shift,splice,includes), Map and Set,join; no deque, heap or ordered map. - Java: ArrayList, ArrayDeque, HashMap, TreeMap with
floorKey, PriorityQueue, StringBuilder. - C++: vector, deque, unordered_map, map with
upper_bound, priority_queue, std::string and string_view. - Go: slices with the
slicespackage, map,sort.Searchon a sorted slice, container/heap, strings.Builder.
“Amortised” means that an occasional step is expensive, when the array is copied into a larger one, but n steps cost O(n) together. “Expected” costs hold for typical keys; adversarial keys can make a hash table linear per operation. The ECMAScript specification defines operations as algorithms rather than costs, so the JavaScript costs follow its steps, and the sort is V8’s, whose engineers describe the Timsort it uses.
Checkpoint
Check: Costs that agree, and where they differ
Taking the front of an array moves everything behind it
The most common of them is a queue taken from the front of an array. Draining a Python list with pop(0) moves every
remaining item one place left on each call, about n²/2 moves in all. Step through four items:
Program · trace/pop_front.py
queue = [10, 20, 30, 40]
moved = 0
while queue:
first = queue.pop(0)
moved += len(queue)
print(first, queue)
print("moved:", moved) Variables
- changed in this step
Printed so far
Nothing printed yet10 [20, 30, 40] 20 [30, 40] 30 [40] 40 [] moved: 6
Every step was recorded from a run with Python 3.14.8 on macOS 26 arm64.
The drawing shows the variables when the program ends, and the program's 21 steps are listed below.
Change the program and run it here. Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
Every step as text
- Start. No line has run yet, so there are no variables. Python starts with line 1.
- Line 1.
queue = [10, 20, 30, 40]makesqueuea list of 4 items. - Line 2.
moved = 0setsmovedto0. - Line 3.
while queue:tests its condition: it is true (queueis[10, 20, 30, 40]), so the body runs. - Line 4.
first = queue.pop(0)setsfirstto10. Key point pop(0) took 10, and 20, 30 and 40 each moved up one place to fill position 0. A deque would have moved none of them. - Line 5.
moved += len(queue)changesmovedfrom0to3. - Line 6.
print(first, queue)prints10 [20, 30, 40]. - Line 3.
while queue:tests its condition: it is true (queueis[20, 30, 40]), so the body runs again (pass 2). - Line 4.
first = queue.pop(0)setsfirstto20(it was10). - Line 5.
moved += len(queue)changesmovedfrom3to5. - Line 6.
print(first, queue)prints20 [30, 40]. - Line 3.
while queue:tests its condition: it is true (queueis[30, 40]), so the body runs again (pass 3). - Line 4.
first = queue.pop(0)setsfirstto30(it was20). - Line 5.
moved += len(queue)changesmovedfrom5to6. - Line 6.
print(first, queue)prints30 [40]. - Line 3.
while queue:tests its condition: it is true (queueis[40]), so the body runs again (pass 4). - Line 4.
first = queue.pop(0)setsfirstto40(it was30). The last pop moves nothing, because nothing is left behind the front. - Line 5.
moved += len(queue)movedkeeps its value,6. - Line 6.
print(first, queue)prints40 []. - Line 3.
while queue:tests its condition: it is false (queueis[]), so the loop ends. - Line 7.
print("moved:", moved)printsmoved: 6. That was the last line, so the program ends. Four pops moved 3 + 2 + 1 + 0 = 6 items, which is n(n − 1)/2 for a list of n items. For 80,000 items that is about 3.2 × 10⁹ moves.
Timed for growing n, beside a deque and a head index:
"""Draining a queue of n items from the front, three ways, timed for growing n (the best of three runs).
list.pop(0) moves every remaining item one place to the left, so draining n items costs about n²/2 moves.
deque.popleft() and a list read through a head index cost O(1) per item.
"""
from collections import deque
from time import perf_counter
def drain_pop0(n):
queue = list(range(n))
total = 0
while queue:
total += queue.pop(0)
return total
def drain_deque(n):
queue = deque(range(n))
total = 0
while queue:
total += queue.popleft()
return total
def drain_head(n):
queue = list(range(n))
total = head = 0
while head < len(queue):
total += queue[head]
head += 1
return total
def ms(f, n, repeat=3):
"""The best of `repeat` timed runs, in milliseconds: other programs can only make a run slower, never faster."""
best = float("inf")
for _ in range(repeat):
start = perf_counter()
result = f(n)
best = min(best, (perf_counter() - start) * 1000)
assert result == n * (n - 1) // 2
return best
print(f"{'n':>9} {'list.pop(0)':>12} {'deque.popleft()':>16} {'head index':>11}")
for n in (20_000, 40_000, 80_000):
print(f"{n:>9,} {ms(drain_pop0, n):>9.1f} ms {ms(drain_deque, n):>13.1f} ms {ms(drain_head, n):>8.1f} ms") Output
n list.pop(0) deque.popleft() head index 20,000 67.3 ms 1.3 ms 1.7 ms 40,000 539.5 ms 1.8 ms 7.3 ms 80,000 2032.2 ms 3.7 ms 10.0 ms
This output changes from run to run: the times depend on the computer and on what else it is doing
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 drain.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
The same queue in JavaScript, where shift() moves the rest of the array down, against a head index that only
counts:
// Draining a queue of n numbers from the front, two ways, timed for growing n.
//
// shift() is specified to move every remaining element down by one place, so draining n items is O(n²) unless the
// engine finds a shortcut. Reading through a head index costs O(1) per item.
function drainShift(n) {
const queue = Array.from({ length: n }, (_, i) => i);
let total = 0;
while (queue.length > 0) total += queue.shift();
return total;
}
function drainHead(n) {
const queue = Array.from({ length: n }, (_, i) => i);
let total = 0;
for (let head = 0; head < queue.length; head++) total += queue[head];
return total;
}
// The best of three timed runs, in milliseconds: other programs can only make a run slower, never faster.
function ms(f, n) {
let best = Infinity;
for (let r = 0; r < 3; r++) {
const start = performance.now();
const result = f(n);
best = Math.min(best, performance.now() - start);
if (result !== (n * (n - 1)) / 2) throw new Error('wrong total');
}
return best.toFixed(1).padStart(8);
}
console.log(`${'n'.padStart(9)} ${'shift()'.padStart(11)} ${'head index'.padStart(11)}`);
for (const n of [20_000, 40_000, 80_000]) {
console.log(`${n.toLocaleString('en-US').padStart(9)} ${ms(drainShift, n)} ms ${ms(drainHead, n)} ms`);
} Output
n shift() head index 20,000 50.9 ms 1.0 ms 40,000 236.0 ms 1.7 ms 80,000 845.8 ms 3.8 ms
This output changes from run to run: the times depend on the computer and on what else it is doing
Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node drain.mjs
Your numbers will differ, but the shape will not. Each doubling of n roughly quadruples the time of pop(0) and
shift(), sometimes more, while the deque and the head index roughly double. At 80,000 items the quadratic versions take most of a second or
more, and the linear ones a few milliseconds. A breadth-first search with a list as its queue is the classic way to meet this cost in an interview: it passes the
samples and times out on the largest graph.
The fix depends on the language: collections.deque in Python, a head index that moves forward in JavaScript,
ArrayDeque in Java, std::deque or std::queue in C++, and in Go re-slicing with s = s[1:], which shares the array
and moves nothing. The fix in 14 languages, on a breadth-first search of eight nodes, each taking the front without
moving the rest:
Python 3.14 · main.py
from collections import deque
# The lesson's graph: 8 nodes, each edge listed once.
EDGES = [(0, 1), (0, 2), (0, 3), (1, 4), (2, 4), (2, 5), (3, 6), (4, 7), (5, 7), (6, 7)]
N = 8
neighbours: list[list[int]] = [[] for _ in range(N)]
for a, b in EDGES:
neighbours[a].append(b)
neighbours[b].append(a)
distance = [-1] * N
distance[0] = 0
queue = deque([0])
order: list[int] = []
most_waiting = moved = 0
while queue:
node = queue.popleft() # nothing behind the front moves
moved += len(queue) # what removing the front of an array would shift
order.append(node)
for nxt in neighbours[node]:
if distance[nxt] == -1:
distance[nxt] = distance[node] + 1
queue.append(nxt)
most_waiting = max(most_waiting, len(queue))
print("visit order:", *order)
print("distances:", *distance)
print("most nodes waiting at once:", most_waiting)
print("items a front removal from an array would move:", moved) In Python: deque.popleft() takes the front in O(1); the collections documentation gives a list O(n) memory movement for pop(0), every item behind the front shifting one place.
Recorded with CPython 3.14.8 on macOS 26 arm64. It runs in your browser with Pyodide 314.0.7 (CPython 3.14.2). To run it yourself: python3 main.py
JavaScript Node 24 · main.mjs
// The lesson's graph: 8 nodes, each edge listed once.
const EDGES = [
[0, 1],
[0, 2],
[0, 3],
[1, 4],
[2, 4],
[2, 5],
[3, 6],
[4, 7],
[5, 7],
[6, 7],
];
const N = 8;
const neighbours = Array.from({ length: N }, () => []);
for (const [a, b] of EDGES) {
neighbours[a].push(b);
neighbours[b].push(a);
}
const distance = new Array(N).fill(-1);
distance[0] = 0;
const queue = [0];
let head = 0;
const order = [];
let mostWaiting = 0;
let moved = 0;
while (head < queue.length) {
const node = queue[head++]; // nothing behind the front moves
moved += queue.length - head; // what removing the front of an array would shift
order.push(node);
for (const next of neighbours[node]) {
if (distance[next] === -1) {
distance[next] = distance[node] + 1;
queue.push(next);
}
}
mostWaiting = Math.max(mostWaiting, queue.length - head);
}
console.log(`visit order: ${order.join(" ")}`);
console.log(`distances: ${distance.join(" ")}`);
console.log(`most nodes waiting at once: ${mostWaiting}`);
console.log(`items a front removal from an array would move: ${moved}`); In JavaScript: JavaScript has no queue type, and shift() moves every later element down one index, so the queue is an array read through a head index that only moves forward.
Recorded with Node.js 24.21.0 LTS (V8 13.6) on macOS 26 arm64. It runs in your browser with QuickJS (quickjs-emscripten 0.32.0). To run it yourself: node main.mjs
Java 25 LTS · Main.java
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Main {
// The lesson's graph: 8 nodes, each edge listed once.
static final int[][] EDGES = {
{0, 1}, {0, 2}, {0, 3}, {1, 4}, {2, 4}, {2, 5}, {3, 6}, {4, 7}, {5, 7}, {6, 7}
};
static final int N = 8;
static String joined(List<Integer> values) {
StringBuilder line = new StringBuilder();
for (int value : values) {
line.append(line.length() == 0 ? "" : " ").append(value);
}
return line.toString();
}
public static void main(String[] args) {
List<List<Integer>> neighbours = new ArrayList<>();
for (int i = 0; i < N; i++) {
neighbours.add(new ArrayList<>());
}
for (int[] edge : EDGES) {
neighbours.get(edge[0]).add(edge[1]);
neighbours.get(edge[1]).add(edge[0]);
}
int[] distance = new int[N];
Arrays.fill(distance, -1);
distance[0] = 0;
ArrayDeque<Integer> queue = new ArrayDeque<>();
queue.add(0);
List<Integer> order = new ArrayList<>();
int mostWaiting = 0;
int moved = 0;
while (!queue.isEmpty()) {
int node = queue.poll(); // nothing behind the front moves
moved += queue.size(); // what removing the front of an array would shift
order.add(node);
for (int next : neighbours.get(node)) {
if (distance[next] == -1) {
distance[next] = distance[node] + 1;
queue.add(next);
}
}
mostWaiting = Math.max(mostWaiting, queue.size());
}
List<Integer> distances = new ArrayList<>();
for (int d : distance) {
distances.add(d);
}
System.out.println("visit order: " + joined(order));
System.out.println("distances: " + joined(distances));
System.out.println("most nodes waiting at once: " + mostWaiting);
System.out.println("items a front removal from an array would move: " + moved);
}
} In Java: ArrayDeque, whose documentation says most operations run in amortized constant time, takes the front with poll(); ArrayList.remove(0) would shift every later element.
Recorded with OpenJDK 25.0.4.1 LTS (Eclipse Temurin) on macOS 26 arm64. It runs in your browser with a TeaVM 0.16.0 build in QuickJS (quickjs-emscripten 0.32.0). To run it yourself: java Main.java
C++23 · main.cpp
#include <algorithm>
#include <array>
#include <print>
#include <queue>
#include <string>
#include <utility>
#include <vector>
// The lesson's graph: 8 nodes, each edge listed once.
constexpr std::array<std::pair<int, int>, 10> edges = {{{0, 1},
{0, 2},
{0, 3},
{1, 4},
{2, 4},
{2, 5},
{3, 6},
{4, 7},
{5, 7},
{6, 7}}};
constexpr int n = 8;
std::string joined(const std::vector<int> &values) {
std::string line;
for (const int value : values) {
line += (line.empty() ? "" : " ") + std::to_string(value);
}
return line;
}
int main() {
std::vector<std::vector<int>> neighbours(n);
for (const auto &[a, b] : edges) {
neighbours[a].push_back(b);
neighbours[b].push_back(a);
}
std::vector<int> distance(n, -1);
distance[0] = 0;
std::queue<int> queue;
queue.push(0);
std::vector<int> order;
std::size_t most_waiting = 0;
std::size_t moved = 0;
while (!queue.empty()) {
const int node = queue.front();
queue.pop(); // nothing behind the front moves
moved += queue.size(); // what removing the front of an array would shift
order.push_back(node);
for (const int next : neighbours[node]) {
if (distance[next] == -1) {
distance[next] = distance[node] + 1;
queue.push(next);
}
}
most_waiting = std::max(most_waiting, queue.size());
}
std::println("visit order: {}", joined(order));
std::println("distances: {}", joined(distance));
std::println("most nodes waiting at once: {}", most_waiting);
std::println("items a front removal from an array would move: {}", moved);
} In C++: std::queue sits on std::deque by default, which inserts and removes at both ends in constant time; erasing the front of a std::vector moves every later element.
Recorded with Apple clang 21.0.0 (C++23) and GCC 16.2.0 on macOS 26 arm64. It runs in your browser with Clang in WebAssembly (YoWASP) 22.0.0-git20542-10. To run it yourself: clang++ -std=c++23 -O2 -Wall -Wextra -Werror -pedantic -fsanitize=address,undefined main.cpp -o main && ./main
C# 14 · .NET 10 · app.cs
using System.Globalization;
CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;
// The lesson's graph: 8 nodes, each edge listed once.
(int A, int B)[] edges =
[
(0, 1), (0, 2), (0, 3), (1, 4), (2, 4),
(2, 5), (3, 6), (4, 7), (5, 7), (6, 7),
];
const int N = 8;
var neighbours = new List<int>[N];
for (int i = 0; i < N; i++)
{
neighbours[i] = [];
}
foreach (var (a, b) in edges)
{
neighbours[a].Add(b);
neighbours[b].Add(a);
}
var distance = new int[N];
Array.Fill(distance, -1);
distance[0] = 0;
var queue = new Queue<int>();
queue.Enqueue(0);
var order = new List<int>();
int mostWaiting = 0;
int moved = 0;
while (queue.Count > 0)
{
int node = queue.Dequeue(); // nothing behind the front moves
moved += queue.Count; // what removing the front of an array would shift
order.Add(node);
foreach (int next in neighbours[node])
{
if (distance[next] == -1)
{
distance[next] = distance[node] + 1;
queue.Enqueue(next);
}
}
mostWaiting = Math.Max(mostWaiting, queue.Count);
}
Console.WriteLine($"visit order: {string.Join(" ", order)}");
Console.WriteLine($"distances: {string.Join(" ", distance)}");
Console.WriteLine($"most nodes waiting at once: {mostWaiting}");
Console.WriteLine($"items a front removal from an array would move: {moved}"); In C#: Queue<T>.Dequeue is an O(1) operation by its documentation, while List<T>.RemoveAt(0) is O(n), its n being Count minus the index.
Recorded with C# 14 on .NET 10.0.12 (SDK 10.0.401) on macOS 26 arm64. It runs in your browser with the .NET runtime for WebAssembly 10.0.12. To run it yourself: dotnet run app.cs
C23 · main.c
#include <stddef.h>
#include <stdio.h>
enum { N = 8, EDGE_COUNT = 10 };
// The lesson's graph: 8 nodes, each edge listed once.
static const int edges[EDGE_COUNT][2] = {{0, 1}, {0, 2}, {0, 3}, {1, 4},
{2, 4}, {2, 5}, {3, 6}, {4, 7},
{5, 7}, {6, 7}};
static void print_line(const char *label, const int *values, size_t count) {
printf("%s:", label);
for (size_t i = 0; i < count; i++) {
printf(" %d", values[i]);
}
printf("\n");
}
int main(void) {
int neighbours[N][N];
size_t degree[N] = {0};
for (size_t i = 0; i < EDGE_COUNT; i++) {
const int a = edges[i][0];
const int b = edges[i][1];
neighbours[a][degree[a]++] = b;
neighbours[b][degree[b]++] = a;
}
int distance[N];
for (size_t i = 0; i < N; i++) {
distance[i] = -1;
}
distance[0] = 0;
int queue[N];
size_t head = 0;
size_t tail = 0;
queue[tail++] = 0;
int order[N];
size_t visited = 0;
size_t most_waiting = 0;
size_t moved = 0;
while (head < tail) {
const int node = queue[head++]; // nothing behind the front moves
moved += tail - head; // what removing the front of an array would shift
order[visited++] = node;
for (size_t k = 0; k < degree[node]; k++) {
const int next = neighbours[node][k];
if (distance[next] == -1) {
distance[next] = distance[node] + 1;
queue[tail++] = next;
}
}
if (tail - head > most_waiting) {
most_waiting = tail - head;
}
}
print_line("visit order", order, visited);
print_line("distances", distance, N);
printf("most nodes waiting at once: %zu\n", most_waiting);
printf("items a front removal from an array would move: %zu\n", moved);
return 0;
} In C: C's standard library has no queue, so the queue is an array with a head and a tail index. Each node joins at most once, so N slots never fill.
Recorded with Apple clang 21.0.0 (C23) and GCC 16.2.0 on macOS 26 arm64. It runs in your browser with Clang in WebAssembly (YoWASP) 22.0.0-git20542-10. To run it yourself: clang -std=c23 -O2 -Wall -Wextra -Werror -pedantic -fsanitize=address,undefined main.c -o main && ./main
PHP 8.5 · main.php
<?php
declare(strict_types=1);
// The lesson's graph: 8 nodes, each edge listed once.
const EDGES = [[0, 1], [0, 2], [0, 3], [1, 4], [2, 4], [2, 5], [3, 6], [4, 7], [5, 7], [6, 7]];
const N = 8;
$neighbours = array_fill(0, N, []);
foreach (EDGES as [$a, $b]) {
$neighbours[$a][] = $b;
$neighbours[$b][] = $a;
}
$distance = array_fill(0, N, -1);
$distance[0] = 0;
$queue = new SplQueue();
$queue->enqueue(0);
$order = [];
$mostWaiting = 0;
$moved = 0;
while (!$queue->isEmpty()) {
$node = $queue->dequeue(); // nothing behind the front moves
$moved += count($queue); // what removing the front of an array would shift
$order[] = $node;
foreach ($neighbours[$node] as $next) {
if ($distance[$next] === -1) {
$distance[$next] = $distance[$node] + 1;
$queue->enqueue($next);
}
}
$mostWaiting = max($mostWaiting, count($queue));
}
echo 'visit order: ', implode(' ', $order), "\n";
echo 'distances: ', implode(' ', $distance), "\n";
echo "most nodes waiting at once: {$mostWaiting}\n";
echo "items a front removal from an array would move: {$moved}\n"; In PHP: SplQueue is a doubly linked list, so dequeue() unlinks the front node, while array_shift() renumbers every remaining key from zero.
Recorded with PHP 8.5.11 (CLI) on macOS 26 arm64. It runs in your browser with php-wasm 0.2.0 (PHP 8.5.2). To run it yourself: php main.php
Rust 1.99 · main.rs
use std::collections::VecDeque;
// The lesson's graph: 8 nodes, each edge listed once.
const EDGES: [(usize, usize); 10] = [
(0, 1),
(0, 2),
(0, 3),
(1, 4),
(2, 4),
(2, 5),
(3, 6),
(4, 7),
(5, 7),
(6, 7),
];
const N: usize = 8;
fn joined<T: ToString>(values: impl IntoIterator<Item = T>) -> String {
let parts: Vec<String> = values.into_iter().map(|v| v.to_string()).collect();
parts.join(" ")
}
fn main() {
let mut neighbours = vec![Vec::new(); N];
for (a, b) in EDGES {
neighbours[a].push(b);
neighbours[b].push(a);
}
let mut distance: Vec<Option<u32>> = vec![None; N];
distance[0] = Some(0);
let mut queue = VecDeque::from([0]);
let mut order = Vec::new();
let (mut most_waiting, mut moved) = (0, 0);
// pop_front: nothing behind the front moves
while let Some(node) = queue.pop_front() {
moved += queue.len(); // what removing the front of an array would shift
order.push(node);
let next_distance = distance[node].map(|d| d + 1);
for &next in &neighbours[node] {
if distance[next].is_none() {
distance[next] = next_distance;
queue.push_back(next);
}
}
most_waiting = most_waiting.max(queue.len());
}
println!("visit order: {}", joined(&order));
let distances = distance.iter().map(|d| d.map_or(-1, i64::from));
println!("distances: {}", joined(distances));
println!("most nodes waiting at once: {most_waiting}");
println!("items a front removal from an array would move: {moved}");
} In Rust: VecDeque is a ring buffer, and the std::collections table gives its remove(i) O(min(i, n - i)), so the front costs O(1), against O(n - i) for a Vec.
Recorded with rustc 1.99.0 (edition 2024) on macOS 26 arm64. It runs in your browser with a WebAssembly build on the WASI runner (browser_wasi_shim 0.4.2). To run it yourself: rustc --edition 2024 -O main.rs && ./main
TypeScript 6.0 · main.ts
// The lesson's graph: 8 nodes, each edge listed once.
const EDGES: [number, number][] = [
[0, 1],
[0, 2],
[0, 3],
[1, 4],
[2, 4],
[2, 5],
[3, 6],
[4, 7],
[5, 7],
[6, 7],
];
const N = 8;
const neighbours: number[][] = Array.from({ length: N }, () => []);
for (const [a, b] of EDGES) {
neighbours[a].push(b);
neighbours[b].push(a);
}
const distance: number[] = new Array<number>(N).fill(-1);
distance[0] = 0;
const queue: number[] = [0];
let head = 0;
const order: number[] = [];
let mostWaiting = 0;
let moved = 0;
while (head < queue.length) {
const node = queue[head++]; // nothing behind the front moves
moved += queue.length - head; // what removing the front of an array would shift
order.push(node);
for (const next of neighbours[node]) {
if (distance[next] === -1) {
distance[next] = distance[node] + 1;
queue.push(next);
}
}
mostWaiting = Math.max(mostWaiting, queue.length - head);
}
console.log(`visit order: ${order.join(" ")}`);
console.log(`distances: ${distance.join(" ")}`);
console.log(`most nodes waiting at once: ${mostWaiting}`);
console.log(`items a front removal from an array would move: ${moved}`); In TypeScript: As in JavaScript, the queue is a number[] read through a head index, since shift() would move every later element.
Recorded with TypeScript 6.0.3 (strict), run by Node.js 24.21.0 on macOS 26 arm64. It runs in your browser with QuickJS (quickjs-emscripten 0.32.0) after Sucrase 3.35.1. To run it yourself: npx -p typescript@6.0.3 tsc --noEmit --strict main.ts && node main.ts
Go 1.27 · main.go
package main
import (
"fmt"
"strconv"
"strings"
)
// edges is the lesson's graph: 8 nodes, each edge listed once.
var edges = [][2]int{{0, 1}, {0, 2}, {0, 3}, {1, 4}, {2, 4}, {2, 5}, {3, 6}, {4, 7}, {5, 7}, {6, 7}}
const n = 8
func joined(values []int) string {
parts := make([]string, len(values))
for i, v := range values {
parts[i] = strconv.Itoa(v)
}
return strings.Join(parts, " ")
}
func main() {
neighbours := make([][]int, n)
for _, e := range edges {
neighbours[e[0]] = append(neighbours[e[0]], e[1])
neighbours[e[1]] = append(neighbours[e[1]], e[0])
}
distance := make([]int, n)
for i := range distance {
distance[i] = -1
}
distance[0] = 0
queue := []int{0}
var order []int
mostWaiting, moved := 0, 0
for len(queue) > 0 {
node := queue[0]
queue = queue[1:] // nothing behind the front moves
moved += len(queue) // what removing the front of an array would shift
order = append(order, node)
for _, next := range neighbours[node] {
if distance[next] == -1 {
distance[next] = distance[node] + 1
queue = append(queue, next)
}
}
mostWaiting = max(mostWaiting, len(queue))
}
fmt.Println("visit order:", joined(order))
fmt.Println("distances:", joined(distance))
fmt.Println("most nodes waiting at once:", mostWaiting)
fmt.Println("items a front removal from an array would move:", moved)
} In Go: queue = queue[1:] makes a new slice over the same array and moves nothing; the array stays in memory until no slice refers to it.
Recorded with Go 1.27.1 on macOS 26 arm64. It runs in your browser with the Go compiler in WebAssembly 1.27.1. To run it yourself: go run main.go
Swift 6.4 · main.swift
// The lesson's graph: 8 nodes, each edge listed once.
let edges = [(0, 1), (0, 2), (0, 3), (1, 4), (2, 4), (2, 5), (3, 6), (4, 7), (5, 7), (6, 7)]
let n = 8
var neighbours = [[Int]](repeating: [], count: n)
for (a, b) in edges {
neighbours[a].append(b)
neighbours[b].append(a)
}
var distance = [Int](repeating: -1, count: n)
distance[0] = 0
var queue = [0]
var head = 0
var order: [Int] = []
var mostWaiting = 0
var moved = 0
while head < queue.count {
let node = queue[head]
head += 1 // nothing behind the front moves
moved += queue.count - head // what removing the front of an array would shift
order.append(node)
for next in neighbours[node] where distance[next] == -1 {
distance[next] = distance[node] + 1
queue.append(next)
}
mostWaiting = max(mostWaiting, queue.count - head)
}
print("visit order: \(order.map(String.init).joined(separator: " "))")
print("distances: \(distance.map(String.init).joined(separator: " "))")
print("most nodes waiting at once: \(mostWaiting)")
print("items a front removal from an array would move: \(moved)") In Swift: Array.removeFirst() is O(n) by its documentation, so the queue is an array read through a head index.
Recorded with Apple Swift 6.4 on macOS 26 arm64. It runs in your browser with a WebAssembly build on the WASI runner (browser_wasi_shim 0.4.2). To run it yourself: swiftc -O main.swift -o main && ./main
Ruby 4.0 · main.rb
# frozen_string_literal: true
# The lesson's graph: 8 nodes, each edge listed once.
EDGES = [[0, 1], [0, 2], [0, 3], [1, 4], [2, 4], [2, 5], [3, 6], [4, 7], [5, 7], [6, 7]].freeze
N = 8
neighbours = Array.new(N) { [] }
EDGES.each do |first, second|
neighbours[first] << second
neighbours[second] << first
end
distance = Array.new(N, -1)
distance[0] = 0
queue = [0]
head = 0
order = []
most_waiting = moved = 0
while head < queue.size
node = queue[head]
head += 1 # nothing behind the front moves
moved += queue.size - head # what removing the front of an array would shift
order << node
neighbours[node].each do |nxt|
next unless distance[nxt] == -1
distance[nxt] = distance[node] + 1
queue << nxt
end
most_waiting = [most_waiting, queue.size - head].max
end
puts "visit order: #{order.join(' ')}"
puts "distances: #{distance.join(' ')}"
puts "most nodes waiting at once: #{most_waiting}"
puts "items a front removal from an array would move: #{moved}" In Ruby: Ruby's documentation states no cost for Array#shift, so this version reads the array through a head index, whose cost is one step per item.
Recorded with CRuby 4.0.7 on macOS 26 arm64. It runs in your browser with ruby.wasm 2.10.1 (Ruby 4.0.0). To run it yourself: ruby main.rb
Kotlin 2.4 · Main.kt
// The lesson's graph: 8 nodes, each edge listed once.
val edges = listOf(0 to 1, 0 to 2, 0 to 3, 1 to 4, 2 to 4, 2 to 5, 3 to 6, 4 to 7, 5 to 7, 6 to 7)
const val N = 8
fun main() {
val neighbours = List(N) { mutableListOf<Int>() }
for ((a, b) in edges) {
neighbours[a].add(b)
neighbours[b].add(a)
}
val distance = IntArray(N) { -1 }
distance[0] = 0
val queue = ArrayDeque(listOf(0))
val order = mutableListOf<Int>()
var mostWaiting = 0
var moved = 0
while (queue.isNotEmpty()) {
val node = queue.removeFirst() // nothing behind the front moves
moved += queue.size // what removing the front of an array would shift
order.add(node)
for (next in neighbours[node]) {
if (distance[next] == -1) {
distance[next] = distance[node] + 1
queue.addLast(next)
}
}
mostWaiting = maxOf(mostWaiting, queue.size)
}
println("visit order: ${order.joinToString(" ")}")
println("distances: ${distance.joinToString(" ")}")
println("most nodes waiting at once: $mostWaiting")
println("items a front removal from an array would move: $moved")
} In Kotlin: kotlin.collections.ArrayDeque, the standard library's resizable-array deque, takes the front with removeFirst().
Recorded with Kotlin 2.4.20 on Java 25.0.4.1 on macOS 26 arm64. It runs in your browser with a WebAssembly build on the WASI runner (browser_wasi_shim 0.4.2). To run it yourself: kotlinc Main.kt -include-runtime -d main.jar && java -jar main.jar
Dart 3.13 · main.dart
import 'dart:collection';
import 'dart:math';
// The lesson's graph: 8 nodes, each edge listed once.
const edges = [
(0, 1),
(0, 2),
(0, 3),
(1, 4),
(2, 4),
(2, 5),
(3, 6),
(4, 7),
(5, 7),
(6, 7),
];
const n = 8;
void main() {
final neighbours = List.generate(n, (_) => <int>[]);
for (final (a, b) in edges) {
neighbours[a].add(b);
neighbours[b].add(a);
}
final distance = List.filled(n, -1);
distance[0] = 0;
final queue = ListQueue<int>()..add(0);
final order = <int>[];
var mostWaiting = 0;
var moved = 0;
while (queue.isNotEmpty) {
final node = queue.removeFirst(); // nothing behind the front moves
moved += queue.length; // what removing the front of an array would shift
order.add(node);
for (final next in neighbours[node]) {
if (distance[next] == -1) {
distance[next] = distance[node] + 1;
queue.add(next);
}
}
mostWaiting = max(mostWaiting, queue.length);
}
print('visit order: ${order.join(' ')}');
print('distances: ${distance.join(' ')}');
print('most nodes waiting at once: $mostWaiting');
print('items a front removal from an array would move: $moved');
} In Dart: ListQueue keeps a cyclic buffer, which its documentation says makes removing the front constant time; List.removeAt(0) moves every later element down one place.
Recorded with Dart 3.13.5 on macOS 26 arm64. It runs in your browser with a WebAssembly build by dart2wasm 3.13.5. To run it yourself: dart run main.dart
What every language printed
visit order: 0 1 2 3 4 5 6 7 distances: 0 1 1 1 2 2 2 3 most nodes waiting at once: 3 items a front removal from an array would move: 11
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs. Runs on this device, in your browser. The first run downloads JavaScript (about 0.6 MB), which is kept for the next runs. Runs on this device: a JavaScript build of this program (about 11 KB), made when the page was built. The first run also downloads the JavaScript runner (about 0.6 MB), which is kept for the next runs. To change the code, copy it and run it with the command below. Runs on this device: a WebAssembly build of this program (about 153 KB), made when the page was built. Edit runs your changes in the browser: the first edited run downloads the C and C++ compiler (about 29.1 MB), which is kept for the next runs. It is best on a computer. Runs on this device, in your browser. The first run downloads the C and C++ compiler (about 29.1 MB), which is kept for the next runs. It is best on a computer. Runs on this device: a .NET build of this program (about 3 KB), made when the page was built. The first run also downloads .NET (about 3.6 MB), which is kept for the next runs. Edit runs your changes in the browser: the first edited run downloads .NET (about 8.9 MB), which is kept for the next runs. Runs on this device, in your browser. The first run downloads .NET (about 8.9 MB), which is kept for the next runs. Runs on this device: a WebAssembly build of this program (about 8 KB), made when the page was built. Edit runs your changes in the browser: the first edited run downloads the C and C++ compiler (about 29.1 MB), which is kept for the next runs. It is best on a computer. Runs on this device, in your browser. The first run downloads the C and C++ compiler (about 29.1 MB), which is kept for the next runs. It is best on a computer. Runs on this device, in your browser. The first run downloads PHP (about 4.3 MB), which is kept for the next runs. Runs on this device: a WebAssembly build of this program (about 21 KB), made when the page was built. To change the code, copy it and run it with the command below. Runs on this device, in your browser. The first run downloads the JavaScript runner (about 0.9 MB), which is kept for the next runs. Runs on this device: a WebAssembly build of this program (about 713 KB), made when the page was built. Edit runs your changes in the browser: the first edited run downloads the Go compiler (about 18.7 MB), which is kept for the next runs. Runs on this device, in your browser. The first run downloads the Go compiler (about 18.7 MB), which is kept for the next runs. Runs on this device: a WebAssembly build of this program (about 1.7 MB), made when the page was built. To change the code, copy it and run it with the command below. Runs on this device, in your browser. The first run downloads Ruby (about 10.2 MB), which is kept for the next runs. Runs on this device: a WebAssembly build of this program (about 186 KB), made when the page was built. To change the code, copy it and run it with the command below. Runs on this device: a WebAssembly build of this program (about 14 KB), made when the page was built. To change the code, copy it and run it with the command below.Your run, in this browser
A plain array would have moved 11 items here, and about n²/2 on a star of n nodes, where n − 1 wait at once. Rust’s VecDeque and Dart’s ListQueue are A fixed block of slots used in a circle: a front index and a rear index move forward and wrap around to slot 0, so adding at the rear and taking the front move no item.: the front index moves, the items stay.
- being moved or written
Pseudo-code
-
enqueue(x): if count = capacity: error "full" -
a[rear] ← x; rear ← (rear + 1) mod capacity; count ← count + 1 -
dequeue(): if count = 0: error "empty" -
x ← a[front]; front ← (front + 1) mod capacity; count ← count − 1; return x
A circular queue with 4 slots. front is where the next item leaves, rear where the next one goes; both wrap around to slot 0 after slot 3.
When it finishes: 2 items queued.
Try your own input
Checkpoint
Check: Taking the front of an array
Practise: the cost of each operation
Give the cost of each operation, then work out three cases that put the costs together; every explanation names the documentation it comes from.
Practice that adapts to you
Practise this lesson
19 questions about 5 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Strings and slices that copy, measured in five languages Pro: opens with a Pro, Premium or Ultimate pass
Building a string piece by piece and slicing what is left are the other hidden linear costs, and each language
behaves differently. This part times both in all five languages and shows when Python’s fast += stops being fast.
In this part:
- In Python, adding to a string is linear until a second name holds it
- Reading input by slicing it away: quadratic in one line
- String += in Java and Go, and s = s + t in C++, measured
- JavaScript: += was fast in Node.js, and nothing promises it
5 runnable examples (C++, Go, Java, JavaScript, Python) · about 5 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Ordered maps and heaps, and what to use where a language has none Pro: opens with a Pro, Premium or Ultimate pass
Java and C++ ship an ordered map and a heap; Python, JavaScript and Go each lack at least one. This part runs the same floor lookup in 14 languages, with a sorted map where there is one, measures the usual stand-ins in Python and JavaScript, and works out when a stand-in stops being good enough.
In this part:
- The latest booking at or before t, in 14 languages
- Python’s sorted list: O(log n) to find, O(n) to insert, measured
- A 30-line heap for JavaScript, measured against a sorted array
- Choosing a stand-in, worked with numbers
3 runnable examples (JavaScript, Python) · about 9 minutes
Opening this part with your pass…Pro part. It opens with the same pass as the part above.
Hash maps at their worst: collisions reproduced, and the defences Pro: opens with a Pro, Premium or Ultimate pass
“Hash lookups are O(1)” is true on average and false for keys chosen to collide. This part builds such keys for Python, C++ and Java, times what they do, and says what each language does to defend itself.
In this part:
- Python: keys that all hash to 0, timed
- C++ and Java under the same attack, measured
- What to say about hash costs in a round
1 diagram · 3 runnable examples (C++, Java, Python) · about 5 minutes
Opening this part with your pass…Pro part. It opens with the same pass as the part above.
Interview questions
-
Fresher to mid · Warm-up
Why is list.pop(0) slow in Python, and what do you use for a queue instead?
A Python list stores its items in one array, so removing the first item shifts every remaining item one place to the left: pop(0) costs O(n), where pop() from the end costs O(1). Draining a list of n items from the front therefore makes about n²/2 moves, which is seconds at 80,000 items. For a queue, use collections.deque, whose append and popleft are O(1) at both ends, or keep the list and read it through a head index that moves forward instead of removing anything. The same trap exists in other languages: shift() in JavaScript and ArrayList.remove(0) in Java are linear too; ArrayDeque and std::deque are the queues there.
-
Fresher to mid
A breadth-first search in Python passes every sample but times out on a graph of 2 × 10⁵ nodes. Its queue is a list, and it takes the next node with pop(0). Estimate the cost of that line, fix it, and say what the same bug looks like in JavaScript and Java.
-
Mid to senior
You say your solution is O(n) because each dictionary lookup is O(1). The interviewer asks when that is false. Answer for Python and Java with an input that breaks it, and say what you would change in a contest where other people choose the inputs.
-
Mid to senior
10⁵ bookings arrive one at a time, mixed with 10⁵ queries for the latest booking at or before a time t. You must answer in Python or JavaScript, which have no ordered map. What do you use, and what does each choice cost?
Key takeaways
- Most operations cost what they look like; the dangerous ones look constant and are linear.
- Removing from the front of an array is O(n) in Python, JavaScript, Java and C++; use a deque, a head index, an ArrayDeque or re-slicing in Go.
- Membership tests on a list and insertion in the middle are O(n) in every language.
- Python, JavaScript and Go lack an ordered map, and JavaScript lacks a heap: know your stand-in before the round.
- Double n and time it: linear code doubles, quadratic code quadruples.
Terms in this lesson
- amortised cost
- The cost of an operation averaged over a long run of them. An append to an array list is usually one step, and the occasional copy into a larger array, spread over the appends that filled it, keeps n appends at O(n) together.
- expected cost
- The cost an operation has on average over typical inputs, such as O(1) for a hash-table lookup whose keys spread over the buckets. Inputs chosen to defeat it, such as keys that all land in one bucket, can cost far more.
References
- TimeComplexity (Python Wiki) (Python Software Foundation)
- collections.deque (The Python Standard Library) (Python Software Foundation)
- bisect: array bisection algorithm (The Python Standard Library) (Python Software Foundation)
- heapq: heap queue algorithm (The Python Standard Library) (Python Software Foundation)
- Built-in types: common sequence operations (notes on concatenation) (Python Software Foundation)
- PEP 8, Style Guide for Python Code: programming recommendations (in-place string concatenation) (Python Software Foundation)
- Built-in types: hashing of numeric types (Python Software Foundation)
- Data model: object.__hash__ (hash randomisation of str and bytes) (Python Software Foundation)
- ECMAScript Language Specification: Array.prototype.shift (Ecma International (TC39))
- ECMAScript Language Specification: Map objects (Ecma International (TC39))
- Getting things sorted in V8 (The V8 project)
- Java SE 25 API: ArrayList (Oracle)
- Java SE 25 API: ArrayDeque (Oracle)
- Java SE 25 API: LinkedList (Oracle)
- Java SE 25 API: HashMap (Oracle)
- JEP 180: Handle Frequent HashMap Collisions with Balanced Trees (OpenJDK)
- Java SE 25 API: TreeMap (Oracle)
- Java SE 25 API: PriorityQueue (Oracle)
- Java SE 25 API: Arrays.sort (Oracle)
- Working Draft, Programming Languages: C++ (N4950): containers library and algorithms library (ISO/IEC JTC1/SC22/WG21)
- std::hash (cppreference.com) (cppreference.com)
- Containers library (cppreference.com) (cppreference.com)
- std::deque and std::queue (cppreference.com) (cppreference.com)
- Package slices (The Go Authors)
- Package container/heap (The Go Authors)
- Package strings: Builder (The Go Authors)
- Package sort (The Go Authors)
- The Go Programming Language Specification: Slice expressions (The Go Authors)
- Go maps in action (The Go Authors)
- Queue<T>.Dequeue (.NET API): an O(1) operation (Microsoft)
- List<T>.RemoveAt and List<T>.BinarySearch (.NET API) (Microsoft)
- List<T>.RemoveAt (.NET API): an O(n) operation (Microsoft)
- The SplQueue class (PHP manual) (The PHP Group)
- array_shift (PHP manual) (The PHP Group)
- Module std::collections: performance of sequences and maps (The Rust Project)
- Struct VecDeque: a double-ended queue implemented with a growable ring buffer (The Rust Project)
- Array.removeFirst() (Swift standard library) (Apple)
- ArrayDeque (Kotlin standard library) (JetBrains (Kotlin))
- binarySearch (Kotlin standard library) (JetBrains (Kotlin))
- ListQueue class (dart:collection) (Dart (Google))
- List.removeAt (dart:core) (Dart (Google))
- SplayTreeMap.lastKeyBefore (dart:collection) (Dart (Google))
- Array#bsearch_index and binary searching (Ruby documentation) (Ruby)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress