JavaScript Time Complexity for Interviews

Updated · By

The ECMAScript specification states one complexity requirement for these types: Map and Set must give access times that are sublinear on average in the number of elements. MDN adds that a sort's complexity cannot be guaranteed. For Array, Object and string methods, neither gives a Big O, so state your assumption and reason from the steps.

Where these figures come from

JavaScript has two documents to check. The ECMAScript specification defines the language, and MDN Web Docs is its standard reference. Both were read on November 5, 2026, using the specification draft dated November 3, 2026.

Neither one gives the kind of table Python and Java publish, because the specification defines what a method must do and leaves speed to each engine. Figures you see elsewhere for push or shift come from observing particular engines, and they are not promised by the language. This page keeps those two things apart.

What the documents state

OperationWhat is statedSource
Map: access to entriesSublinear on average in the number of elements. Hash tables or "other mechanisms" are allowed.ECMAScript, Map objects
Set: access to valuesSublinear on average in the number of elements. Hash tables or "other mechanisms" are allowed.ECMAScript, Set objects
Map and Set internallyCould be a hash table with O(1) lookup, a search tree with O(log N) lookup, or anything better than O(N).MDN, Map, MDN, Set
Set.prototype.hasOn average, faster than Array.prototype.includes on an array of the same size.MDN, Set
Array.prototype.sort"The time and space complexity of the sort cannot be guaranteed as it depends on the implementation."MDN, sort
Map against ObjectMap "performs better in scenarios involving frequent additions and removals". Object is "not optimized" for them.MDN, Map

So the accurate thing to say about Map.get is "sublinear on average," not a flat O(1). The specification also says its description of Maps and Sets is not meant to be a viable implementation model, so do not read speed into the way it words them.

What the specification's steps do

For these methods no document states a bound, so this table gives none. What the specification does give is the steps a method must appear to follow, and those are what you can reason from. An engine may do something faster, as long as the result is the same.

MethodWhat the steps doSource
arr.push(x)Writes the new items after the last index and updates the length. Existing elements are not touched.push
arr.pop()Reads and deletes the last element and updates the length.pop
arr.shift()Loops over every later element and moves each one down an index.shift
arr.unshift(x)Loops over every existing element and moves each one up, then writes the new items.unshift
arr.includes(x), arr.indexOf(x)Compare elements in ascending order and stop at the first match.includes, indexOf
str.indexOf(x)Tries each starting position in ascending order and compares the substring there.StringIndexOf

Objects and strings have no complexity statements anywhere in these documents, including for reading a property, deleting one, building a string with + or calling slice. So they have no row above. When you need a key-value store, MDN's comparison is the documented reason to prefer Map, especially if keys are added and removed often.

How to reason without a stated bound

You still have to choose an approach. The honest move is to separate what the language promises from what you are assuming, and to write code that does not depend on the assumption where you can. Here, the queue never moves elements, whatever the engine does with shift:

JavaScript

// A queue that never moves elements: dequeue only advances a head index.
class Queue {
  constructor() { this.items = []; this.head = 0; }
  enqueue(x) { this.items.push(x); }
  dequeue() {
    if (this.head === this.items.length) return undefined;
    const x = this.items[this.head];
    this.items[this.head++] = undefined;   // let the value be collected
    return x;
  }
  get size() { return this.items.length - this.head; }
}

// Map for counts: any key type, no inherited keys to collide with.
function countValues(values) {
  const counts = new Map();
  for (const v of values) counts.set(v, (counts.get(v) ?? 0) + 1);
  return counts;
}

// Set for membership, in place of includes() inside a loop.
function firstSeen(values) {
  const seen = new Set();
  const out = [];
  for (const v of values) {
    if (!seen.has(v)) { seen.add(v); out.push(v); }
  }
  return out;
}

If you want evidence about your own engine rather than an assumption, measure it. This doubles the input and prints the time, so you can see how the cost grows on the Node or browser version you actually run:

JavaScript

// Doubles n and times one operation on an array of that size.
function measure(label, operation) {
  for (let n = 1 << 16; n <= 1 << 20; n *= 2) {
    const arr = Array.from({ length: n }, (_, i) => i);
    const start = performance.now();
    for (let i = 0; i < 1000; i++) operation(arr);
    const ms = (performance.now() - start).toFixed(2);
    console.log(label, "n =", n, ms, "ms");
  }
}

measure("push and pop", (a) => { a.push(1); a.pop(); });
measure("unshift and shift", (a) => { a.unshift(1); a.shift(); });
measure("includes, missing", (a) => a.includes(-1));

If the time doubles when n doubles, that operation is growing with the array on that engine, today. It is a measurement, so say it as one.

How to say it out loud

What a strong candidate might say

I'll use a Map for the counts. The specification only promises sublinear access on average, so I'll assume it behaves like a hash table and call the pass roughly linear, and I'm saying that's an assumption. For the queue I'll keep a head index rather than call shift, because the spec defines shift as moving every remaining element, and I don't want to depend on an engine optimizing that away. If performance mattered here, I'd measure it on our runtime rather than assume.

Few candidates can say what the language actually promises, so this is an easy way to stand out, as long as you do not overdo it. One sentence is enough. Practice it in a coding mock interview, and see the Big O cheat sheet for the costs of the structures themselves.

What the documents do not state

None of these has a Big O in the specification or on the MDN pages read for this guide, so none has a figure here:

  • Array: indexing, push, pop, shift, unshift, splice, slice, concat, includes and indexOf.
  • Object: reading, writing and deleting a property, and Object.keys.
  • String: +, slice, indexOf, includes, split and at.
  • Map and Set: anything beyond the sublinear average, such as a worst case or the cost of iterating.

Frequently asked questions

What is the time complexity of a JavaScript Map?

The ECMAScript specification only requires access times that are sublinear on average in the number of elements. MDN notes that this could be a hash table with O(1) lookup or a search tree with O(log N) lookup. So say sublinear on average, and if you treat it as constant, say that is an assumption.

Is Array.prototype.shift O(n) in JavaScript?

The specification and MDN state no Big O for shift. The specification defines its steps as moving every later element down one index, and an engine may do something faster as long as the result is the same. In an interview, avoid depending on it: a head index never moves elements at all.

Should I use a Map or an object in JavaScript?

Use a Map for a key-value store, especially if keys are added and removed often. MDN says Map performs better in those scenarios and that Object is not optimized for them. A Map also accepts any key type and has no inherited keys to collide with. Use an object for a record with known fields.

Try a mock interview free

3 interviews a month on the Free plan, up to 20 minutes each. No card needed.