Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

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.

  • Beginner
  • 20 minutes
  • Examples run with Python 3.14.8, Pyodide 314.0.7, Node.js 24.21.0, Java 25, Clang 21.0.0 and Go 1.27.1
  • By MySmartCoPilot
Free core, Pro depth: 7 of 20 minutes free
Free: Most costs agree across the five languages; a few differ · Taking the front of an array moves everything behind it · Practise: the cost of each operation. With a pass: 3 in-depth parts and 3 interview answers, about 13 minutes.

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

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, bisect on 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 slices package, map, sort.Search on 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

  1. Question 1 of 2 By the table, which of the five languages ship an ordered map that answers "the largest key ≤ t" in O(log n)?

    Choose every answer that is right.

    Show the answer: Question 1 of 2

    Answer:

    • C++
    • Java

    Only Java and C++ have an ordered map in their standard libraries. Elsewhere the table's answer is a sorted list or slice with a binary search, which finds a key in O(log n) but inserts in O(n).

  2. Question 2 of 2 An array list copies itself into a larger array whenever it is full. What does "O(1) amortised" promise for n appends?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: That the n appends cost O(n) together, although one append now and then costs O(n)

    "Amortised" means that an occasional step is expensive, when the array is copied into a larger one, but n steps cost O(n) together, so the average step is O(1).

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:

Interactive · line by line Draining a list from the front with pop(0) Watch the list after each pop(0). Every item behind the front moves up one place, and moved adds them up.

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

After line 7: queue is []; moved is 6; first is 40.queuemoved6first40
  • changed in this step

Printed so far

10 [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.

Line run 7
Next line end
Loop pass –
Every step as text
  1. Start. No line has run yet, so there are no variables. Python starts with line 1.
  2. Line 1. queue = [10, 20, 30, 40] makes queue a list of 4 items.
  3. Line 2. moved = 0 sets moved to 0.
  4. Line 3. while queue: tests its condition: it is true (queue is [10, 20, 30, 40]), so the body runs.
  5. Line 4. first = queue.pop(0) sets first to 10. 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.
  6. Line 5. moved += len(queue) changes moved from 0 to 3.
  7. Line 6. print(first, queue) prints 10 [20, 30, 40].
  8. Line 3. while queue: tests its condition: it is true (queue is [20, 30, 40]), so the body runs again (pass 2).
  9. Line 4. first = queue.pop(0) sets first to 20 (it was 10).
  10. Line 5. moved += len(queue) changes moved from 3 to 5.
  11. Line 6. print(first, queue) prints 20 [30, 40].
  12. Line 3. while queue: tests its condition: it is true (queue is [30, 40]), so the body runs again (pass 3).
  13. Line 4. first = queue.pop(0) sets first to 30 (it was 20).
  14. Line 5. moved += len(queue) changes moved from 5 to 6.
  15. Line 6. print(first, queue) prints 30 [40].
  16. Line 3. while queue: tests its condition: it is true (queue is [40]), so the body runs again (pass 4).
  17. Line 4. first = queue.pop(0) sets first to 40 (it was 30). The last pop moves nothing, because nothing is left behind the front.
  18. Line 5. moved += len(queue) moved keeps its value, 6.
  19. Line 6. print(first, queue) prints 40 [].
  20. Line 3. while queue: tests its condition: it is false (queue is []), so the loop ends.
  21. Line 7. print("moved:", moved) prints moved: 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: pop(0), a deque and a head index Python · queues/drain.py
"""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

The same queue in JavaScript, where shift() moves the rest of the array down, against a head index that only counts:

Draining a queue in Node.js: shift() and a head index JavaScript · queues/drain.mjs
// 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:

One program · 14 languages A breadth-first search whose queue costs O(1) at the front O(V + E): each node joins and leaves the queue once

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

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.

Interactive · step through it A ring buffer of four slots: only front and rear move front and rear move round a fixed set of slots and wrap from the last slot to slot 0.
Circular queue of 4 slots: 0: empty, 1: empty, 2: empty, 3: empty. front 0, rear 0, 0 in use.0123frontrearwraps around
  • being moved or written

Pseudo-code

  1. enqueue(x): if count = capacity: error "full"
  2. a[rear] ← x; rear ← (rear + 1) mod capacity; count ← count + 1
  3. dequeue(): if count = 0: error "empty"
  4. 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.

Items 0
Operations 0

Checkpoint

Check: Taking the front of an array

  1. Question 1 of 2 In the traced run, four pop(0) calls drained [10, 20, 30, 40]. How many items did they move in all?

    Type a number.

    A hint

    Count the items left behind the front after each pop.

    Show the answer: Question 1 of 2

    Answer: 6 items

    The pops left 3, 2, 1 and 0 items behind the front, and each of those moved up one place: 3 + 2 + 1 + 0 = 6, the moved line of the run. For n items that is n(n − 1)/2.

  2. Question 2 of 2 A Go program keeps its queue in a slice. Which line takes the front item off without moving any other item?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: queue = queue[1:]

    queue = queue[1:] changes only the slice's start, as the specification's slice expressions say, so taking the front is O(1). The other three copy the rest of the queue each time.

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.

  1. Question 1 of 19 Python: what does items.pop(0) cost on a list of n items?

    Choose one answer.

    Show the answer: Question 1 of 19

    Answer: O(n)

    Every item after the first moves one place to the left. The Python wiki lists popping from anywhere but the end as O(n), and the lesson's run shows draining a list this way growing about four times or more with each doubling.

  2. Question 2 of 19 Python: what does collections.deque.popleft() cost?

    Choose one answer.

    Show the answer: Question 2 of 19

    Answer: O(1)

    A deque is built for both ends: the collections documentation gives O(1) appends and pops at either end. Indexing into its middle is O(n), so use it as a queue, not as an array.

  3. Question 3 of 19 JavaScript: what does array.shift() cost on a long array of n numbers in Node.js?

    Choose one answer.

    Show the answer: Question 3 of 19

    Answer: O(n), because the remaining elements move down

    The specification's algorithm for shift moves every remaining element down by one index. In the lesson's run, draining 80,000 numbers with shift() took most of a second, against a few milliseconds with a head index.

  4. Question 4 of 19 Java: what does ArrayList.get(i) cost?

    Choose one answer.

    Show the answer: Question 4 of 19

    Answer: O(1)

    The ArrayList documentation lists size, isEmpty, get, set, iterator and listIterator as constant-time operations. An ArrayList stores its items in one array.

  5. Question 5 of 19 Java: what does ArrayList.remove(0) cost on a list of n items?

    Choose one answer.

    Show the answer: Question 5 of 19

    Answer: O(n)

    The documentation says that all operations other than the constant-time ones and add run in linear time: removing the first item shifts the rest. For a queue, use ArrayDeque, whose poll is amortised constant.

  6. Question 6 of 19 Java: what does LinkedList.get(i) cost on a list of n items?

    Choose one answer.

    Show the answer: Question 6 of 19

    Answer: O(n), because it walks from the nearer end

    The LinkedList documentation says that operations that index into the list traverse it from the beginning or the end, whichever is closer. A loop that calls get(i) for every i is therefore O(n²).

  7. Question 7 of 19 Java: what does TreeMap.floorKey(t) cost on a map of n keys?

    Choose one answer.

    Show the answer: Question 7 of 19

    Answer: O(log n)

    TreeMap is a red-black tree, and its documentation guarantees log(n) time for containsKey, get, put and remove; floorKey walks the same tree from the root to a leaf.

  8. Question 8 of 19 C++: what does v.insert(v.begin(), x) cost on a std::vector of n elements?

    Choose one answer.

    Show the answer: Question 8 of 19

    Answer: O(n)

    Every existing element moves one place to make room, so the cost is linear in the number of elements after the insertion point. A std::deque adds at the front in constant time.

  9. Question 9 of 19 C++: what does m.lower_bound(t) cost on a std::map of n keys?

    Choose one answer.

    Show the answer: Question 9 of 19

    Answer: O(log n)

    std::map is an ordered associative container, and its lookups, lower_bound included, are logarithmic in the size of the map, in the worst case as well.

  10. Question 10 of 19 C++: in the worst case, what does find cost on a std::unordered_map of n keys?

    Choose one answer.

    Show the answer: Question 10 of 19

    Answer: O(n)

    The standard gives unordered containers constant time on average and linear time in the worst case, when many keys share a bucket. Keys chosen to collide put every key in one bucket and reach that worst case.

  11. Question 11 of 19 Go: what does s = s[1:] cost on a slice of n elements?

    Choose one answer.

    Show the answer: Question 11 of 19

    Answer: O(1), because the new slice shares the same array

    A slice expression makes a new slice header over the same underlying array, so nothing moves. That makes a queue with s[1:] cheap in Go, unlike pop(0) in Python, although the array stays in memory until no slice uses it.

  12. Question 12 of 19 Go: what does slices.Insert(s, 0, x) cost on a slice of n elements?

    Choose one answer.

    Show the answer: Question 12 of 19

    Answer: O(n)

    The slices documentation says Insert is O(len(s) + len(v)): every element from the index onwards is shifted up to make room.

  13. Question 13 of 19 Python: what does x in items cost when items is a list of n values, and when it is a set?

    Choose one answer.

    Show the answer: Question 13 of 19

    Answer: O(n) for the list, O(1) on average for the set

    A list is scanned from the start; a set hashes x and looks in one place, O(1) on average and O(n) in the worst case. Inside a loop over n items, the list version makes the loop O(n²).

  14. Question 14 of 19 Python: by the table, what do n calls of heapq.heappush cost in all, starting from an empty heap?

    Choose one answer.

    Show the answer: Question 14 of 19

    Answer: O(n log n)

    The table gives O(log n) for a heap push, so n pushes from an empty heap cost O(n log n) in all. When every item is there at the start, the heapq documentation offers heapify, which arranges a whole list into a heap in linear time.

  15. Question 15 of 19 Draining a Python list of 80,000 items with pop(0) moves how many items in total, roughly? Give the answer in billions.

    Type a number.

    Show the answer: Question 15 of 19

    Answer: 3.2 billion moves (anything from 3.1 to 3.3 counts)

    The k-th pop moves the n − k items behind the first, so the total is n(n − 1)/2 = 80,000 × 79,999 / 2, about 3.2 × 10⁹ moves. A deque or a head index makes 80,000 steps.

  16. Question 16 of 19 This loop keeps the first copy of each of 10⁵ IDs: seen = [], then for each x, if x not in seen: seen.append(x). What does it cost, and what one change fixes it?

    Choose one answer.

    Show the answer: Question 16 of 19

    Answer: O(n²), because each in scans the list; keeping seen as a set makes the loop O(n) on average

    The table's row "Is x in an unsorted list? O(n)" sits inside a loop over n items, so the loop is O(n²), about 5 × 10⁹ comparisons at 10⁵ distinct IDs. A set answers the same test in O(1) on average.

  17. Question 17 of 19 The block's breadth-first search runs on a star of 1,000 nodes, node 0 joined to each of the others. How many items would a plain array's front removal move in all?

    Type a number.

    A hint

    After node 0 leaves, its 999 neighbours join; then count the items still waiting behind each node that leaves.

    Show the answer: Question 17 of 19

    Answer: 498501 items

    Node 0 leaves an empty queue, so it moves nothing; then the 999 leaves join, and each one that leaves has 998, 997, … 0 behind it: 998 × 999 / 2 = 498,501 moves. That is about n²/2, against the 11 of the lesson's eight-node graph, while the deque and the head index move nothing at all.

  18. Question 18 of 19 You double n from 40,000 to 80,000, and a drain's best time goes from 0.5 s to 2.1 s. What does that suggest about its growth?

    Choose one answer.

    Show the answer: Question 18 of 19

    Answer: Quadratic, because doubling n multiplied the time by about 4

    Double n and time it: linear code doubles, quadratic code quadruples. A ratio of about 4.2 points to O(n²), the shape the lesson's pop(0) and shift() columns show.

  19. Question 19 of 19 The ECMAScript specification defines what sort does but gives it no cost. Where does a cost for sorting in Node.js come from, then?

    Choose one answer.

    Show the answer: Question 19 of 19

    Answer: From V8, the engine of Node.js, whose engineers describe the Timsort it uses

    Cite the source that sets a cost. For JavaScript's sort that is the engine: V8's engineers describe the Timsort that Node.js uses, while the specification only says what the sorted result must be.

Python Online Compiler Paste drain.py, double the sizes again and see which column grows four times each step.

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.

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

  1. 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.

  2. 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.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  3. 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.

    Model answer, Pro. First step and follow-ups

  4. 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?

    Model answer, Pro. First step and follow-ups

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

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.