Data Structures & Algorithms (DSA) Module 1 – Foundations: problems, correctness and complexity
Benchmarking and the doubling experiment
Time code with monotonic clocks, estimate the exponent of a running time with the doubling experiment, and avoid warm-up, JIT and measurement traps.
What you will learn
- Choose a monotonic clock for timing code in Python, JavaScript, Java, C++ and Go
- Estimate the exponent of a running time with the doubling experiment
- Avoid warm-up, JIT and dead-code pitfalls in microbenchmarks
Before you start
On this page
Counting steps tells you how an algorithm should grow; a measurement tells you what a real program on a real computer does. Measuring well is harder than it looks: the clock you pick, what else the computer is doing, a just-in-time compiler and even the order of your runs can each change the answer. This lesson shows how to time code honestly in five languages and how to read a growth rate off a few timings with the doubling experiment.
Pick a clock that only moves forwards
A computer has more than one clock. The wall clock tells the time of day; it can jump when the system clock is
corrected, so the difference between two readings may even be negative. A monotonic clock never goes backwards
and is not adjusted, which makes it the right tool for durations. Python’s documentation says so plainly about
time.time(): a later call can return a smaller value if the system clock was set back in between. Python can tell
you what each of its clocks promises:
import time
# What Python says about each of its clocks.
for name in ["perf_counter", "monotonic", "process_time", "time"]:
info = time.get_clock_info(name)
print(f"time.{name}(): monotonic={info.monotonic}, adjustable={info.adjustable}") Output
time.perf_counter(): monotonic=True, adjustable=False time.monotonic(): monotonic=True, adjustable=False time.process_time(): monotonic=True, adjustable=False time.time(): monotonic=False, adjustable=True
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 clock_flags.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
time.perf_counter()is the one to time code with: it is monotonic and has the highest resolution available. Its starting point is undefined, so only the difference between two readings means anything.time.perf_counter_ns()returns the same reading as a whole number of nanoseconds.time.process_time()counts the processor time of your program only. Time spent sleeping, or waiting while other programs use the processor, is not counted, which makes it steadier on a busy computer.time.time()is the wall clock: right for timestamps, wrong for durations.
Every language this track uses has a monotonic clock for measuring durations, and some have a benchmark tool:
- Python:
time.perf_counter_ns(), in whole nanoseconds. The standard library’s benchmark tool is thetimeitmodule, shown below. - JavaScript:
performance.now(), in milliseconds with a fractional part, counted from the start of the page or, in Node.js, of the process. Browsers deliberately coarsen it, to 100 microseconds on most pages (5 microseconds on cross-origin isolated ones), to make timing attacks harder.Date.now()is the wall clock in whole milliseconds. QuickJS, which runs JavaScript for this site’s Run buttons, has noperformanceobject, soDate.now()is all a program can use there. The language has no benchmark tool. - Java:
System.nanoTime(), alongcount of nanoseconds. The documentation says it is only for elapsed time: its origin is arbitrary and unrelated to the time of day, and it promises nanosecond precision but not nanosecond resolution. The JDK has no benchmark tool; JMH is a separate OpenJDK project. - C++:
std::chrono::steady_clock::now(); subtract two time points for a duration. The standard requires this clock never to decrease and to advance at a steady rate; the wall clock isstd::chrono::system_clock. The standard library has no benchmark tool. - Go:
time.Now(), thentime.Since(start)for atime.Duration.time.Now()carries a monotonic reading as well as the wall-clock time, andtime.Sinceuses the monotonic one, so a measured duration survives a clock change.go testruns benchmarks written withtesting.Bandb.Loop().
Let the standard library repeat the runs
A single measurement catches whatever else the computer happened to be doing. Python’s timeit module runs a
statement many times with time.perf_counter(), switches off garbage collection while it times, and with repeat
gives you several measurements. Its documentation advises looking at the smallest of them: other programs can only
add time, never take it away, so the fastest run is the closest to what the code itself costs.
Repeating a run is only fair if every run does the same work. This example shows how easily that goes wrong:
import random
import timeit
random.seed(42)
data = [random.random() for _ in range(100_000)]
# A trap: data.sort() sorts the list in place, so every run after the first sorts a list that is already sorted.
in_place = timeit.repeat("data.sort()", globals=globals(), repeat=5, number=1)
random.shuffle(data)
# Each run sorts the same shuffled list: sorted() returns a new list and leaves data as it was.
fresh = timeit.repeat("sorted(data)", globals=globals(), repeat=5, number=1)
for label, runs in [("data.sort(), in place", in_place), ("sorted(data), a new list", fresh)]:
print(f"{label}:")
print(" " + " ".join(f"{seconds * 1000:.1f}" for seconds in runs) + " ms")
print(f" fastest: {min(runs) * 1000:.1f} ms") Output
data.sort(), in place: 14.5 0.4 0.5 0.4 0.3 ms fastest: 0.3 ms sorted(data), a new list: 17.6 17.4 17.7 18.4 29.3 ms fastest: 17.4 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 timeit_repeat.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
data.sort() sorts the list in place, so only its first run sorts shuffled data; every later run is handed a list
that is already sorted. Python’s sort is adaptive: its documented cost is O(n log n), but sorted input takes only
O(n) comparisons. The “fastest” run is therefore many times faster than real sorting, and wrong. sorted(data)
returns a new list and leaves data shuffled, so all five runs do the same work, and only the computer’s other work
makes their times differ. Whenever a measured piece of code changes its input, build a fresh input for every run,
outside the timed part.
The doubling experiment
Suppose the running time grows like for some constant and an unknown exponent . Doubling the input then multiplies the time by a fixed factor:
Time the code at n, 2n, 4n, 8n and look at the ratio of each time to the one before. A ratio near 2 means linear growth (b ≈ 1); a little above 2, such as 2.1 to 2.2, suggests n log n; near 4 means quadratic (b ≈ 2); and near 8 means cubic (b ≈ 3). The constant , which depends on the computer and the language, cancels out, so the experiment works on any machine.
This helper does the experiment in Python. It builds a fresh input for every run outside the timing, keeps the fastest of three runs, prints each ratio with its exponent and, at the end, the slope of a straight line through all the points on a log-log scale, which is steadier than any single ratio. It compares two ways of emptying a queue:
import math
import time
from collections import deque
def best_time(work, make_input, n, clock, repeats=3):
"""Seconds taken by the fastest of `repeats` runs of work(make_input(n)); making the input is not timed."""
best = float("inf")
for _ in range(repeats):
data = make_input(n)
start = clock()
work(data)
best = min(best, clock() - start)
return best
def doubling(name, work, make_input, sizes, clock=time.perf_counter):
"""Time work for each size; print T(n), the ratio T(n) / T(n/2) and its exponent b = log2(ratio)."""
print(name)
print(f"{'n':>8} {'ms':>9} {'ratio':>6} {'b':>5}")
times = []
for n in sizes:
times.append(best_time(work, make_input, n, clock))
line = f"{n:>8,} {times[-1] * 1000:>9.2f}"
if len(times) > 1:
ratio = times[-1] / times[-2]
line += f" {ratio:>6.2f} {math.log2(ratio):>5.2f}"
print(line)
# The slope of log T against log n over all the sizes: steadier than any single ratio.
xs = [math.log2(n) for n in sizes]
ys = [math.log2(t) for t in times]
mean_x, mean_y = sum(xs) / len(xs), sum(ys) / len(ys)
slope = sum((x - mean_x) * (y - mean_y) for x, y in zip(xs, ys)) / sum((x - mean_x) ** 2 for x in xs)
print(f"estimated exponent b = {slope:.2f}")
def empty_with_pop0(items):
while items:
items.pop(0)
def empty_with_popleft(items):
while items:
items.popleft()
SIZES = [10_000, 20_000, 40_000, 80_000]
# CPU time rather than wall-clock time: the computer that recorded this output was busy with other work.
doubling("list.pop(0) until empty", empty_with_pop0, lambda n: list(range(n)), SIZES, clock=time.process_time)
doubling("deque.popleft() until empty", empty_with_popleft, lambda n: deque(range(n)), SIZES, clock=time.process_time) Output
list.pop(0) until empty
n ms ratio b
10,000 9.29
20,000 45.59 4.91 2.29
40,000 191.55 4.20 2.07
80,000 769.48 4.02 2.01
estimated exponent b = 2.12
deque.popleft() until empty
n ms ratio b
10,000 0.23
20,000 0.46 2.01 1.01
40,000 0.94 2.04 1.03
80,000 1.97 2.09 1.06
estimated exponent b = 1.03
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 doubling.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
list.pop(0) gives ratios around 4 and an exponent near 2: emptying a list from the front is quadratic, because
every pop(0) moves all the remaining items one place to the left, as the documentation of collections.deque
warns. deque.popleft() gives ratios around 2 and an exponent near 1: it removes the front item without moving the
rest. Single ratios wobble, most of all between the smallest sizes, which are the most sensitive to the processor’s
caches and to whatever else the computer is doing; read the trend and the fitted exponent rather than one ratio. The
output on this page was recorded with time.process_time(), which leaves out the time the program spent waiting for
the processor, because the recording computer was busy with other work. On your own computer the default clock,
time.perf_counter(), is the usual choice.
The same helper in JavaScript compares two ways of counting the distinct values in a list:
// performance.now() where it exists (Node.js, browsers); QuickJS has only Date.now(), in whole milliseconds.
const now = typeof performance === 'object' ? () => performance.now() : () => Date.now();
/** Milliseconds taken by the fastest of `repeats` runs of work(makeInput(n)); making the input is not timed. */
function bestTime(work, makeInput, n, repeats = 7) {
let best = Infinity;
for (let r = 0; r < repeats; r++) {
const data = makeInput(n);
const start = now();
work(data);
best = Math.min(best, now() - start);
}
return best;
}
const pad = (text, width) => String(text).padStart(width);
const withCommas = (n) => String(n).replace(/\B(?=(\d{3})+(?!\d))/g, ',');
/** Times work for each size; prints T(n), the ratio T(n) / T(n/2) and its exponent b = log2(ratio). */
function doubling(name, work, makeInput, sizes) {
console.log(name);
console.log(`${pad('n', 8)} ${pad('ms', 9)} ${pad('ratio', 6)} ${pad('b', 5)}`);
const times = [];
for (const n of sizes) {
times.push(Math.max(bestTime(work, makeInput, n), 0.001));
let line = `${pad(withCommas(n), 8)} ${pad(times.at(-1).toFixed(2), 9)}`;
if (times.length > 1) {
const ratio = times.at(-1) / times.at(-2);
line += ` ${pad(ratio.toFixed(2), 6)} ${pad(Math.log2(ratio).toFixed(2), 5)}`;
}
console.log(line);
}
// The slope of log T against log n over all the sizes: steadier than any single ratio.
const xs = sizes.map(Math.log2);
const ys = times.map(Math.log2);
const meanX = xs.reduce((a, b) => a + b) / xs.length;
const meanY = ys.reduce((a, b) => a + b) / ys.length;
let above = 0;
let below = 0;
for (let i = 0; i < xs.length; i++) {
above += (xs[i] - meanX) * (ys[i] - meanY);
below += (xs[i] - meanX) ** 2;
}
console.log(`estimated exponent b = ${(above / below).toFixed(2)}`);
}
// n different numbers in a scrambled order: 7919 is odd, so i * 7919 % n meets every value once when n is a power of 2.
const scrambled = (n) => Array.from({ length: n }, (_, i) => (i * 7919) % n);
function countDistinctWithArray(values) {
const seen = [];
for (const v of values) if (!seen.includes(v)) seen.push(v);
return seen.length;
}
function countDistinctWithSet(values) {
const seen = new Set();
for (const v of values) seen.add(v);
return seen.size;
}
doubling('distinct values with Array.prototype.includes()', countDistinctWithArray, scrambled, [2048, 4096, 8192, 16384]);
doubling('distinct values with a Set', countDistinctWithSet, scrambled, [16384, 32768, 65536, 131072]); Output
distinct values with Array.prototype.includes()
n ms ratio b
2,048 0.65
4,096 2.24 3.47 1.80
8,192 8.99 4.01 2.00
16,384 40.31 4.49 2.17
estimated exponent b = 1.99
distinct values with a Set
n ms ratio b
16,384 0.71
32,768 1.65 2.32 1.21
65,536 3.73 2.26 1.17
131,072 9.75 2.62 1.39
estimated exponent b = 1.25
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 doubling.mjs
Runs on this device, in your browser. The first run downloads JavaScript (about 0.6 MB), which is kept for the next runs.
Your run, in this browser
includes() looks through the array element by element, so checking every new value against all the earlier ones
is quadratic: its exponent comes out close to 2. The language standard requires a Set only to find a value in
less than linear time on average; the measurement shows that one pass with a Set grows close to linearly. Its
exponent comes out above 1 but well below 2: a step count treats every lookup as equally cheap, but a bigger table
no longer fits in the processor’s fastest caches, so each lookup tends to cost a little more. The two workloads use
different sizes, so that every measurement takes long enough to time.
A careful timing inside the doubling experiment
Text description of the diagram
The diagram shows a box named "A careful timing" holding five numbered steps, an arrow to a sixth step below it, and an arrow labelled "next size" from the sixth step back to the box.
- Build the input. This is not part of the timing.
- Warm up: run the code a few times without timing it, so that a just-in-time compiler has done its work.
- Time several runs with a monotonic clock.
- Keep the fastest run, or the median of the runs.
- Use the result, by checking it or printing it, so that the work cannot be skipped.
- Double the input size n and measure again. The ratio T(2n) / T(n) and its base-2 logarithm, b, estimate the exponent of the running time. Then the careful timing starts again for the next size.
Warm-up, just-in-time compilers and work that disappears
Engines with a just-in-time (JIT) compiler change speed while your program runs. V8, the engine in Node.js and Chrome, starts every function in its Ignition interpreter. A function that runs often is compiled to machine code: first quickly by Sparkplug, a compiler that does not optimise, and later by an optimising compiler, once V8 has seen what kinds of values the function handles. This program times the same function eight times:
// performance.now() where it exists (Node.js, browsers); QuickJS has only Date.now(), in whole milliseconds.
const now = typeof performance === 'object' ? () => performance.now() : () => Date.now();
const values = Array.from({ length: 200_000 }, (_, i) => (i * 7919) % 1000);
function sumOfSquares(items) {
let total = 0;
for (let i = 0; i < items.length; i++) total += items[i] * items[i];
return total;
}
const engine = typeof process === 'object' ? `Node.js ${process.versions.node} (V8 ${process.versions.v8})` : 'not Node.js';
console.log(`engine: ${engine}`);
let checksum = 0;
for (let round = 1; round <= 8; round++) {
const start = now();
checksum += sumOfSquares(values);
console.log(`round ${round}: ${(now() - start).toFixed(2)} ms`);
}
// Using the result keeps an optimising compiler from deciding the work is not needed.
console.log(`checksum ${checksum}`); Output
engine: Node.js 24.21.0 (V8 13.6.233.17-node.53) round 1: 2.41 ms round 2: 1.16 ms round 3: 0.44 ms round 4: 0.44 ms round 5: 0.47 ms round 6: 0.45 ms round 7: 0.45 ms round 8: 0.51 ms checksum 532533600000
This output changes from run to run: the times depend on the computer, the runtime and what else the computer is doing
Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node warmup.mjs
Runs on this device, in your browser. The first run downloads JavaScript (about 0.6 MB), which is kept for the next runs.
Your run, in this browser
Version note
The Run button runs this file in QuickJS, an interpreter without a JIT compiler, so it prints not Node.js as the
engine and has nothing to warm up: its rounds do not speed up as V8’s do, and each takes many times longer than V8
needs once it has compiled the function.
The first round in V8 is several times slower than the later ones: it started in the interpreter while V8 gathered information, and later rounds ran compiled code. Java’s virtual machine works the same way, starting in an interpreter and compiling the parts that run often, which is one reason the OpenJDK project offers JMH, a harness for building and running Java benchmarks. Three habits keep JIT engines honest:
- Warm up. Run the code a few times before you start the clock, or drop the first rounds.
- Use the result. An optimising compiler may leave out work whose result nobody reads. Every program in this
lesson prints or checks what it computed, like the checksum above. In Go, write benchmarks with
b.Loop(): its documentation says that the arguments and results of function calls inside the loop are kept alive, so the compiler cannot optimise the loop body away. - Keep the timed part small and fixed. Build inputs before the clock starts and time only the code you mean to measure.
Report what you measured
A timing describes one program on one computer with one runtime. When you share a measurement, say which processor, operating system and runtime version produced it, and how you summarised the runs (fastest, median or mean). Each output on this page names the runtime and platform it was recorded with, so you can compare it with what you see when you press Run or run a file on your own computer.
Logarithm & Antilog Calculator Turn a measured ratio into an exponent: b = log2(ratio).Key takeaways
- Time durations with a monotonic clock:
time.perf_counter_ns(),performance.now(),System.nanoTime(),std::chrono::steady_clockortime.Since; never with the wall clock. - Repeat every measurement, keep the fastest run (or the median), and make sure every run does the same work on a freshly built input.
- In the doubling experiment the ratio T(2n) / T(n) is about 2ᵇ: around 2 for linear, a little above 2 for n log n, 4 for quadratic and 8 for cubic. Read the trend, not one ratio.
- JIT engines such as V8 and the Java virtual machine speed up as they run: warm up first, use every result, and report the hardware and runtime with your numbers.
Exercise
Exercise · Medium · Python, JavaScript
Classify growth rates with the doubling ratio
Timings change from run to run, so this exercise does the doubling experiment with step counts instead. Each test passes a function steps(n) that runs an algorithm on an input of size n and returns how many steps it took, the same number on every computer. Write two functions (in JavaScript, doublingRatio and classify):
doubling_ratio(steps, n)returnssteps(2 * n) / steps(n), how much the count grows when the input size doubles.classify(steps)takes the doubling ratio atn = 1024and returns a growth rate by this rule:- below 1.02:
"constant" - from 1.02, below 1.5:
"log n" - from 1.5, below 2.05:
"linear" - from 2.05, below 3:
"n log n" - from 3, below 6:
"quadratic" - 6 or more:
"cubic"
The limits sit between the ratios each rate gives at this size: about 1.1 for log n, 2 for n, 2.2 for n log n, 4 for n² and 8 for n³. Lower-order terms move a ratio a little (5n + 300 gives about 1.94), but not across a limit.
Python · Starter code · growth.py
def doubling_ratio(steps, n):
"""steps(2 * n) / steps(n): how much the step count grows when the input size doubles."""
return 1.0
def classify(steps):
"""The growth rate of steps, from its doubling ratio at n = 1024 and the limits in the exercise."""
return "linear" The sample tests · test_growth.py
from growth import classify, doubling_ratio
def halvings(n):
"""Steps of a binary search over n items: halve until nothing is left."""
steps = 0
while n > 0:
n //= 2
steps += 1
return steps
def one_pass(n):
"""Steps of one pass over n items."""
steps = 0
for _ in range(n):
steps += 1
return steps
def merge_sort_comparisons(n):
"""Comparisons a merge sort makes on n scrambled values."""
count = 0
def sort(items):
nonlocal count
if len(items) <= 1:
return items
middle = len(items) // 2
left, right = sort(items[:middle]), sort(items[middle:])
merged, i, j = [], 0, 0
while i < len(left) and j < len(right):
count += 1
if left[i] <= right[j]:
merged.append(left[i])
i += 1
else:
merged.append(right[j])
j += 1
return merged + left[i:] + right[j:]
sort([(k * 7919) % n for k in range(n)])
return count
def every_pair(n):
"""Steps of looking at every pair of n items."""
steps = 0
for i in range(n):
for j in range(i + 1, n):
steps += 1
return steps
def test_doubling_ratio():
"""divides the count at 2n by the count at n"""
assert doubling_ratio(lambda n: 3 * n, 10) == 2.0
assert doubling_ratio(lambda n: n * n, 5) == 4.0
assert doubling_ratio(halvings, 1024) == 12 / 11
def test_constant():
"""a count that does not grow is constant"""
assert classify(lambda n: 7) == "constant"
def test_log_n():
"""binary search grows like log n"""
assert classify(halvings) == "log n"
def test_linear():
"""one pass, with or without extra fixed work, is linear"""
assert classify(one_pass) == "linear"
assert classify(lambda n: 5 * n + 300) == "linear"
def test_n_log_n():
"""merge sort's comparisons grow like n log n"""
assert classify(merge_sort_comparisons) == "n log n"
def test_quadratic():
"""every pair, with or without a linear term, is quadratic"""
assert classify(every_pair) == "quadratic"
assert classify(lambda n: n * n + 50 * n) == "quadratic"
def test_cubic():
"""n cubed is cubic"""
assert classify(lambda n: n**3) == "cubic" JavaScript · Starter code · growth.mjs
/** steps(2 * n) / steps(n): how much the step count grows when the input size doubles. */
export function doublingRatio(steps, n) {
return 1;
}
/** The growth rate of steps, from its doubling ratio at n = 1024 and the limits in the exercise. */
export function classify(steps) {
return 'linear';
} The sample tests · growth.test.mjs
import { test, assert } from 'toolverse:test';
import { classify, doublingRatio } from './growth.mjs';
/** Steps of a binary search over n items: halve until nothing is left. */
function halvings(n) {
let steps = 0;
while (n > 0) {
n = Math.floor(n / 2);
steps++;
}
return steps;
}
/** Steps of one pass over n items. */
function onePass(n) {
let steps = 0;
for (let i = 0; i < n; i++) steps++;
return steps;
}
/** Comparisons a merge sort makes on n scrambled values. */
function mergeSortComparisons(n) {
let count = 0;
const sort = (items) => {
if (items.length <= 1) return items;
const middle = Math.floor(items.length / 2);
const left = sort(items.slice(0, middle));
const right = sort(items.slice(middle));
const merged = [];
let i = 0;
let j = 0;
while (i < left.length && j < right.length) {
count++;
if (left[i] <= right[j]) merged.push(left[i++]);
else merged.push(right[j++]);
}
return merged.concat(left.slice(i), right.slice(j));
};
sort(Array.from({ length: n }, (_, k) => (k * 7919) % n));
return count;
}
/** Steps of looking at every pair of n items. */
function everyPair(n) {
let steps = 0;
for (let i = 0; i < n; i++) for (let j = i + 1; j < n; j++) steps++;
return steps;
}
test('divides the count at 2n by the count at n', () => {
assert.equal(doublingRatio((n) => 3 * n, 10), 2);
assert.equal(doublingRatio((n) => n * n, 5), 4);
assert.equal(doublingRatio(halvings, 1024), 12 / 11);
});
test('a count that does not grow is constant', () => assert.equal(classify(() => 7), 'constant'));
test('binary search grows like log n', () => assert.equal(classify(halvings), 'log n'));
test('one pass, with or without extra fixed work, is linear', () => {
assert.equal(classify(onePass), 'linear');
assert.equal(classify((n) => 5 * n + 300), 'linear');
});
test("merge sort's comparisons grow like n log n", () => assert.equal(classify(mergeSortComparisons), 'n log n'));
test('every pair, with or without a linear term, is quadratic', () => {
assert.equal(classify(everyPair), 'quadratic');
assert.equal(classify((n) => n * n + 50 * n), 'quadratic');
});
test('n cubed is cubic', () => assert.equal(classify((n) => n ** 3), 'cubic')); A hint
doubling_ratio is one line: call steps twice and divide. In classify, compute the ratio once, then test the limits from the smallest up and return as soon as the ratio is below one of them; whatever is left is "cubic".
Results of the sample tests
| Test | Result | Details |
|---|
What your code printed
The sample tests run on this device, in your browser (Pyodide, QuickJS): nothing is sent to mysmartcopilot.com. The first run of each language downloads it: Python (about 13.5 MB) or JavaScript (about 0.6 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.
Check yourself
6 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.
References
- time: Time access and conversions (Python Software Foundation)
- timeit: Measure execution time of small code snippets (Python Software Foundation)
- collections: deque objects (Python Software Foundation)
- Time complexity of operations on built-in types (Python Software Foundation)
- Performance measurement APIs (perf_hooks) (OpenJS Foundation)
- Performance: now() method (MDN Web Docs (Mozilla))
- ECMAScript 2026 Language Specification (Array.prototype.includes, Set objects) (Ecma International)
- Class System: nanoTime() (Java SE 25) (Oracle)
- Java Virtual Machine Technology Overview (Java SE 25) (Oracle)
- Java Microbenchmark Harness (JMH) (OpenJDK)
- C++ working draft N4950: class steady_clock [time.clock.steady] (ISO/IEC JTC1/SC22/WG21 (HTML rendering of the working draft))
- C++ working draft N4950: class system_clock [time.clock.system] (ISO/IEC JTC1/SC22/WG21 (HTML rendering of the working draft))
- Package time: monotonic clocks (The Go Authors)
- Package testing: benchmarks and B.Loop (The Go Authors)
- Launching Ignition and TurboFan (The V8 project)
- Sparkplug: a non-optimizing JavaScript compiler (The V8 project)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress