Yes, V8 has optimised string slicing to O(1). This does of course depend a lot on what else happens to all the strings, they might need to get copied later on.

The relevant implementation from the above link is:

// The Sliced String class describes strings that are substrings of another
// sequential string.  The motivation is to save time and memory when creating
// a substring.  A Sliced String is described as a pointer to the parent,
// the offset from the start of the parent string and the length.  Using
// a Sliced String therefore requires unpacking of the parent string and
// adding the offset to the start address.  A substring of a Sliced String
// are not nested since the double indirection is simplified when creating
// such a substring.
// Currently missing features are:
//  - handling externalized parent strings
//  - external strings as parent
//  - truncating sliced string to enable otherwise unneeded parent to be GC'ed.
class SlicedString: public String {
  // ...
};

Also beware of your quick test results. As you are doing nothing with the y variable, the slicing and even the whole loop might get eliminated as dead code by the optimiser. If you are doing benchmarks, do them on practical real world data.

Answer from Bergi on Stack Overflow
Top answer
1 of 2
7

(V8 developer here.)

Array.prototype.slice is O(n), where n is the number of elements in the slice.
String.prototype.slice is O(1), thanks to our implementation of SlicedStrings, which are just storing pointer, offset, length to the original string and avoid copying the characters (except when they're tiny, so that copying a handful of characters is actually cheaper and smaller than storing a reference; that's still O(1)).

The key difference is that strings are immutable, and arrays are not. When you do str1 = "Hello World"; str2 = str1.slice(2, 5);, since there is no way to modify str1's contents afterwards, str2 doesn't need to ensure that it's unaffected by any such modification.
When you do a = [1, 2, 3, 4]; b = a.slice(1, 3); a[1] = "changed"; console.log(b[0]);, then you expect to see 2, not "changed". That's why b has to be an actual copy. (In theory, a copy-on-write approach would be possible, but V8 doesn't do that for array slices.)

"Shallow copy" means that nested objects will not be copied. Example:

let nested = {property: "value"};
var a = [nested];
var b = a.slice(0, 1);
a[0].property = "new value";
console.log(a === b);          // false, `b` is a copy
console.log(a[0] === b[0]);    // true, `nested` was not copied
console.log(b[0] === nested);  // true
console.log(b[0].property);    // "new value"
2 of 2
0

Based on my read (which is to say, I could be wrong, since V8 is a complicated beast) of this array-slice.tq source code, the answer is: "it depends".

If possible (and the heuristics as to when that might happen I didn't really get to), V8 optimizes things to essentially O(1) by just returning a copy-on-write view to the original array via ExtractFastJSArray.

When that fails, V8 allocates a new array and copies object (pointers) over, which is of course O(N).

The tq source code includes lots of "gotcha" cases, since JavaScript does allow you to call Array.prototype.slice() on things that aren't really arrays.

Discussions

javascript - What's the time complexity of array.splice() in Google Chrome? - Stack Overflow
Similar things can be done by Javascript interpreters. ... Find the answer to your question by asking. Ask question ... See similar questions with these tags. ... Live from re:Invent…it’s Stack Overflow! ... 2 The splice method causes the for loop to be executed n - 2 times (n is the number of elements in an array) 0 Time complexity of slice ... More on stackoverflow.com
🌐 stackoverflow.com
javascript - Performance question: String.split and then walk on the array, or RegExp? - Stack Overflow
I'll do some work on a line separated string. Which one will be faster, to split the text via String.split first and then walk on the resultant array or directly walk the whole text via a reg exp and More on stackoverflow.com
🌐 stackoverflow.com
What is the time complexity of forEach? of slice?
So, a O(n) solution will increase linearly as the size of the input increases. I would solve that problem by getting the product of the entire array, to do this you have to touch each element in the array, so that's O(n). Then, I'd go over the array again and divide the total product by each element in the array, this is also O(n). So, this solution would be O(2n), not O(n2 ). With Big O, you drop constants, because in the big picture, they don't matter. An array of length 10 with take twice as long as array of length 5. If it was O(n2 ), length 10 would take 4x as much as a length of 5. For methods like map or reduce, they are the same as forEach. They go over a collection and perform one operation per item. For methods like slice or indexOf, we can't say exactly what the time complexity is, because it could stop at the first index or the last. It's fine to chain methods, because they're ran asynchronously, not concurrently. One method doesn't run until the previous one completes. What you need to be careful of is nesting. Two nested for loops with an indexOf inside, could get expensive for example. More on reddit.com
🌐 r/learnjavascript
6
1
June 22, 2016
JavaScript runtime complexity of Array functions - Stack Overflow
Is the runtime complexity defined by the JS standard on common Array functions like push, pop, shift, slice or splice? Esp. I'm interested in removing and inserting entries at random positions. If ... More on stackoverflow.com
🌐 stackoverflow.com
🌐
JavaScript in Plain English
javascript.plainenglish.io › understanding-time-and-space-complexity-of-common-javascript-built-in-methods-39a3285a6409
Understanding Time and Space Complexity of Common JavaScript Built-in Methods | by Kyle Le | JavaScript in Plain English
April 14, 2023 - String.prototype.slice(): O(n) time complexity and O(n) space complexity. This method extracts a section of a string and returns it as a new string. String.prototype.split(): O(n) time complexity and O(n) space complexity.
🌐
MDN Web Docs
developer.mozilla.org › en-US › docs › Web › JavaScript › Reference › Global_Objects › String › slice
String.prototype.slice() - JavaScript - MDN Web Docs
For example, str.slice(4, 8) extracts the fifth character through the eighth character (characters indexed 4, 5, 6, and 7): indexStart indexEnd ↓ ↓ | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | | T | h | e | | m | i | r | r | o | r | m i r r _______________ ↑ Result · If indexStart >= str.length, an empty string is returned.
🌐
Medium
audreyhal.medium.com › reverse-a-string-in-javascript-plus-time-complexities-8cef62f4110e
Reverse a String in JavaScript (Checking Time Complexities) | by Chioma Halim | Medium
July 20, 2021 - // Time Complexity = O(n)function reverseString(string) { if (string.length === 1) return string; let parentNode = Math.floor(string.length / 2); let leftPart = string.substring(0, parentNode) let rightPart = string.substring(parentNode); return ...
🌐
Hashnode
hashnode.com › post › time-complexity-of-substr-method-in-javascript-cjjqj3qft012i3ks1ih811u19
Time complexity of substr method in javascript? - Hashnode
July 18, 2018 - So for the following code, which ... let current = str.substr(i, k); rtn.push(current) } return rtn; } the time complexity is O(n*k), which is approximated to O(n), right?...
🌐
AlgoCademy
algocademy.com › link
Time Complexity Practice 2 in JavaScript | AlgoCademy
To solve this problem, we need to understand the time complexity of slicing and concatenation operations in JavaScript. Let's start with a naive solution and then move on to more optimized approaches. A naive solution would involve using the slice method to create subarrays and the concat method ...
🌐
MDN Web Docs
developer.mozilla.org › en-US › docs › Web › JavaScript › Reference › Global_Objects › String › substring
String.prototype.substring() - JavaScript - MDN Web Docs
slice() also treats NaN arguments as 0, but when it is given negative values it counts backwards from the end of the string to find the indexes.
Find elsewhere
🌐
Medium
medium.com › @sonu9506517825 › string-methods-in-javascript-155f216e4540
String Methods in JavaScript. 1. length : time complexity O(N) |… | by Sonu Verma | Medium
December 26, 2022 - String Methods in JavaScript 1. length : time complexity O(N) | space complexity O(N) calculate the number of element in string return count of element in string 2. at() : time complexity O(1) | …
Top answer
1 of 4
37

Worst case should be O(n) (copying all n-1 elements to new array).

A linked list would be O(1) for a single deletion.

For those interested I've made this lazily-crafted benchmark. (Please don't run on Windows XP/Vista). As you can see from this though, it looks fairly constant (i.e. O(1)), so who knows what they're doing behind the scenes to make this crazy-fast. Note that regardless, the actual splice is VERY fast.

Rerunning an extended benchmark directly in the V8 shell that suggest O(n). Note though that you need huge array sizes to get a runtime that's likely to affect your code. This should be expected as if you look at the V8 code it uses memmove to create the new array.

2 of 4
7

¡Hi!

I did an experiment myself and would like to share my findings. The experiment was very simple, we ran 100 splice operations on an array of size n, and calculate the average time each splice function took. Then we varied the size of n, to check how it behave.

This graph summarizes our findings for big numbers:

For big numbers it seems to behave linearly.

We also checked with "small" numbers (they were still quite big but not as big):

On this case it seems to be constant.

If I would have to decide for one option I would say it is O(n), because that is how it behaves for big numbers. Bear in mind though, that the linear behaviour only shows for VERY big numbers.

However, It is hard to go for a definitive answer because the array implementation in javascript dependes A LOT on how the array is declared and manipulated.

I recommend this stackoverflow discussion and this quora discussion to understand how arrays work.

I run it in node v10.15.3 and the code used is the following:

const f = async () => {
  const n = 80000000;
  const tries = 100;
  const array = [];
  for (let i = 0; i < n; i++) { // build initial array
    array.push(i);
  }
  
  let sum = 0;
  for (let i = 0; i < tries; i++) {
    const index = Math.floor(Math.random() * (n));
    const start = new Date();
    array.splice(index, 1); // UNCOMMENT FOR OPTION A
    // array.splice(index, 0, -1); // UNCOMMENT FOR OPTION B
    const time = new Date().getTime() - start.getTime();
    sum += time;
    array.push(-2); // UNCOMMENT FOR OPTION A, to keep it of size n
    // array.pop(); // UNCOMMENT FOR OPTION B, to keep it of size n

  }
  console.log('for an array of size', n, 'the average time of', tries, 'splices was:', sum / tries);
 };
f();

Note that the code has an Option B, we did the same experiment for the three argument splice function to insert an element. It worked similary.

🌐
Medium
hemanthkollanur.medium.com › optimisation-issues-with-array-slice-b075e7a6e5e7
Optimisation issues with Array.slice() | by Hemanth KV | Medium
July 17, 2023 - 2. Performance: The `Array.slice()` method has a time complexity of O(n), where n is the number of elements in the resulting slice. If you’re working with large arrays and frequently slicing them, the performance impact can be noticeable.
🌐
ITNEXT
itnext.io › decoding-big-o-notation-time-and-space-complexities-in-javascript-d31ddb8e1254
Decoding Big O Notation Time and Space Complexities in JavaScript | ITNEXT
August 3, 2023 - Time Complexity: O(n) — as it converts the string to an array, reverses the array, and joins it back to a string.
🌐
Reddit
reddit.com › r/learnjavascript › what is the time complexity of foreach? of slice?
r/learnjavascript on Reddit: What is the time complexity of forEach? of slice?
June 22, 2016 -

I randomly happened upon someones Github page where he shared some of his algorithm practice problems and solutions.

One of the problems conditions stated we are given an array of all integers. For each index of that array, find the product of every other index. Solve this in linear time, linear space

Example input: [2, 7, 1, 4] Expected output: [28, 8, 56, 14]

The weird thing was, he used 2 forEach loops to come to his solution. He used both forEach loops to iterate over the array and he had variables to store product and eventually push into a final array that he returns at the end.

This had me questioning the time complexity of forEach. I found this explanation on ecma-international.org. Basically it shows O(n) time complexity for a single forEach loop. Please correct me if I'm wrong.

So, the persons solution should be O(n2) right? I really just want confirmation from someone with more experience so that I don't keep questioning myself on this..

This got me thinking about using other built-in methods. I've had solutions where I've used multiple slice methods or enumerables like map or reduce. In these cases, what is the expected time complexity of using a single slice method or map method? Isn't it really bad once you start method chaining?

Top answer
1 of 3
185

The ECMA specification does not specify a bounding complexity, however, you can derive one from the specification's algorithms.

push is O(1), however, in practice it will encounter an O(N) copy costs at engine defined boundaries as the slot array needs to be reallocated. These boundaries are typically logarithmic.

pop is O(1) with a similar caveat to push but the O(N) copy is rarely encountered as it is often folded into garbage collection (e.g. a copying collector could only copy the used part of an array).

shift is at worst O(N) however it can, in specially cases, be implemented as O(1) at the cost of slowing down indexing so your mileage may vary.

slice is O(N) where N is end - start. Not a tremendous amount of optimization opportunity here without significantly slowing down writes to both arrays.

splice is, worst case, O(N). There are array storage techniques that divide N by a constant but they significantly slow down indexing. If an engine uses such techniques you might notice unusually slow operations as it switches between storage techniques triggered by access pattern changes.

One you didn't mention, is sort. It is, in the average case, O(N log N). However, depending on the algorithm chosen by the engine, you could get O(N^2) in some cases. For example, if the engine uses QuickSort (even with an late out to InsertionSort), it has well-known N^2 cases. This could be a source of DoS for your application. If this is a concern either limit the size of the arrays you sort (maybe merging the sub-arrays) or bail-out to HeapSort.

2 of 3
17

in very simple words

push -> O(1)

pop -> O(1)

shift -> O(N)

slice -> O(N)

splice -> O(N)

Here is a complete explanation about time complexity of Arrays in JavaScript.

🌐
Medium
medium.com › @avinashkumar151199 › slice-and-splice-in-javascript-30e81e4ea997
Slice and Splice in JavaScript?. slice()-: Slice is used to get a new… | by Avinash Kumar | Medium
August 10, 2023 - In b.slice(2, 4) means that you want to extract elements from index 2 up to, but not including, index 4. Since arrays are zero-indexed, this range includes elements at indices 2 and 3. So, the extracted portion is ['Apple', 'Pine-Apple']. splice()-: Splice is used to add/remove an element from the given array, it change the original array. The time complexity of spliceis O(n).
🌐
GeeksforGeeks
geeksforgeeks.org › javascript-string-slice
JavaScript string.slice() Method - GeeksforGeeks
May 15, 2023 - The string.slice() is an inbuilt method in javascript that is used to return a part or slice of the given input string.
🌐
Reddit
reddit.com › r/compsci › time complexity of array.splice
r/compsci on Reddit: Time Complexity of Array.Splice
December 29, 2017 -

Time complexity of array.splice is worst-case O(n) because it has to potentially copy over all the elements.

Question 1: this means array.shift() is always O(n)? Or is it optimized somehow low-level where it just adjusts starting memory address?

Question 2: People say use a linked list instead of array if you repeatedly splice because deletion is O(1). But you'd still have to search through linked list to find element (which worst-case is still linear) because it's not indexed. So how do you index a linked list, so that deletion is ACTUALLY constant time? (also, is a language's implementation, like JS, of an array ever a linked list?)

Top answer
1 of 3
2
  1. Using copy on write, the copy can be avoided in some cases right up to the point where the data mutated. AFAIK, not many languages expose or even use COW. I've mostly only ever seen it referenced in OS design for things like process forking.

  2. Academics say to always use a linked list. However, practically, Array lists are almost always preferred. The problem with linked lists is that they are not cache friendly. In the worst case, your CPU has to go out and hit main memory for every single node accessed (millions of cycles per node!).

In contrast Array lists keep all the nodes in a contiguous block of memory. CPUs, when they access memory, will pull in a block of memory into cache. For traversal of an array, the cost can easily be in the 10s of cylces per element.

Further, the cost of adding to an array list isn't as bad as you might think. These lists will often overallocate so they can avoid copying over the data when you hit the end of the array.

And in practice, middle or beginning insertion just isn't all that common. You almost always do tail insertions with lists and very often you don't do any deletions at all!

So how do you index a linked list, so that deletion is ACTUALLY constant time?

You iterate and delete/split at the same time. You still have n time, but you don't end up with 2*n time. That is pretty much the case for a linked list, when you want to frequently go through the list and remove, split, or merge the list at various points based on the current element. Again, in practice this happens almost never.

also, is a language's implementation, like JS, of an array ever a linked list?

Depends. In fact, in javascript it could easily start out as a linked list and later be transformed into an array list based on jit optimizations.

2 of 3
2

From experience in implementation (JSC but I believe it’s similar in other engines) array.shift and unshift are amortized but generally O(1). Splice is interesting that engines will try to avoid “real” splicing depending on how the outputs are used