356

Assuming I have an array that has a size of N (where N > 0), is there a more efficient way of prepending to the array that would not require O(N + 1) steps?

In code, essentially, what I currently am doing is

function prependArray(value, oldArray) {
  var newArray = new Array(value);

  for(var i = 0; i < oldArray.length; ++i) {
    newArray.push(oldArray[i]);
  }

  return newArray;
}
Sebastian Simon
  • 18,263
  • 7
  • 55
  • 75
samccone
  • 10,746
  • 7
  • 43
  • 50
  • as much as I love linked lists and pointers I feel as though there must be a more effective way to do things using native JS data types – samccone Jun 01 '11 at 02:51
  • @samccone: Yeah disregard my comment sorry, I thought you said Java :P – GWW Jun 01 '11 at 02:52
  • 3
    Java, JavaScript, C or Python, it doesn't matter what language: the complexity tradeoff between arrays vs linked lists is the same. Linked Lists are probably quite unwieldy in JS because there is no built-in class for them (unlike Java), but if what you really want is O(1) insertion time, then you do want a linked list. – mgiuca Jun 01 '11 at 02:58
  • 1
    Is it a requirement to clone it? – Ryan Florence Jun 01 '11 at 03:00
  • 1
    If it is a requirement to clone it, then unshift is inappropriate, since it will mutate the original array and not create a copy. – mgiuca Jun 01 '11 at 03:18

11 Answers11

593

I'm not sure about more efficient in terms of big-O but certainly using the unshift method is more concise:

var a = [1, 2, 3, 4];
a.unshift(0);
// => [0, 1, 2, 3, 4]
console.log({a});

[Edit]

This jsPerf benchmark shows that unshift is decently faster in at least a couple of browsers, regardless of possibly different big-O performance if you are ok with modifying the array in-place. If you really can't mutate the original array then you would do something like the below snippet, which doesn't seem to be appreciably faster than your solution:

a.slice().unshift(0); // Use "slice" to avoid mutating "a".

[Edit 2]

For completeness, the following function can be used instead of OP's example prependArray(...) to take advantage of the Array unshift(...) method:

function prepend(value, array) {
  var newArray = array.slice();
  newArray.unshift(value);
  return newArray;
}

var x = [1, 2, 3];
var y = prepend(0, x);
// x => [1, 2, 3];
// y => [0, 1, 2, 3];
console.log({ x, y });
Teocci
  • 7,189
  • 1
  • 50
  • 48
maerics
  • 151,642
  • 46
  • 269
  • 291
  • 1
    ah bingo, must have forgotten about this one, I wonder how it stacks up against efficiency vs what I was doing – samccone Jun 01 '11 at 02:54
  • I'm sure `unshift` is still O(N), due to the storage of arrays. But yes, it would be much more efficient than the loop. – mgiuca Jun 01 '11 at 02:56
  • 272
    Who decided to call `prepend` "`unshift`"? – Scott Stafford Aug 08 '13 at 14:17
  • 16
    @ScottStafford `unshift` sounds more appropriate for such an array operation (it moves the elements... more or less physically). `prepend` would be more appropriate to linked lists, where you _literally_ prepend elements. – CamilB Aug 22 '13 at 14:30
  • 14
    unshift is the complementary function to shift. Calling it "prepend" would be the odd choice. Unshift is to shift as push is to pop. – Sir Robert Nov 01 '13 at 14:51
  • 11
    Only one issue with this solution. Doesn't unshift() return its **length**, and not **the array** as in this answer? http://www.w3schools.com/jsref/jsref_unshift.asp – iDVB Mar 08 '15 at 05:36
  • 2
    Like @iDVB said - you need to assign sliced array to be able to access it after `unshift`. And so `a.slice(0).unshift(0)` doesn't make sense. Fixed in answer. – Nux Nov 09 '15 at 19:21
  • 5
    `push` and `unshift` both contain `u`, whereas `pop` and `shift` don't have. –  Apr 06 '16 at 22:08
  • 1
    this answer is just wrong. it's doing 2 copy operations. ```src.slice(0)``` copy 1 ```src.unshift(value)``` copy every element over by 1 slot. the most efficient way to prepend without modifying the original is to copy in 1 pass like so ```var dest = new Array(src.length + 1); for(var i = 0; src.length > i; i++) dest[i+1] = src[i]``` – BenG Mar 04 '17 at 02:56
  • `array.unshift(value);` is fastest and exactly what OP needs, this prepend function with slice in it is completely unnecessary and decreases performance. – Mladen Oršolić Apr 20 '22 at 19:13
125

With ES6, you can now use the spread operator to create a new array with your new elements inserted before the original elements.

// Prepend a single item.
const a = [1, 2, 3];
console.log([0, ...a]);

// Prepend an array.
const a = [2, 3];
const b = [0, 1];
console.log([...b, ...a]);

Update 2018-08-17: Performance

I intended this answer to present an alternative syntax that I think is more memorable and concise. It should be noted that according to some benchmarks (see this other answer), this syntax is significantly slower. This is probably not going to matter unless you are doing many of these operations in a loop.

Frank Tan
  • 4,234
  • 2
  • 19
  • 29
57

If you are prepending an array to the front of another array, it is more efficient to just use concat. So:

const newArray = [1, 2, 3].concat([4, 5]);
newArray; // [1, 2, 3, 4, 5]

But this will still be O(N) in the size of oldArray. Still, it is more efficient than manually iterating over oldArray. Also, depending on the details, it may help you, because if you are going to prepend many values, it's better to put them into an array first and then concat oldArray on the end, rather than prepending each one individually.

There's no way to do better than O(N) in the size of oldArray, because arrays are stored in contiguous memory with the first element in a fixed position. If you want to insert before the first element, you need to move all the other elements. If you need a way around this, do what @GWW said and use a linked list, or a different data structure.

Joost
  • 3,169
  • 2
  • 22
  • 40
mgiuca
  • 20,958
  • 7
  • 54
  • 70
  • 2
    Oh yes, I forgot about `unshift`. But note that a) that mutates oldArray whereas `concat` doesn't (so which one is better for you depends on the situation), and b) it only inserts one element. – mgiuca Jun 01 '11 at 02:56
  • 2
    Wow, that is much slower. Well, as I said, it is making a copy of the array (and also creating a new array `[0]`), whereas unshift is mutating it inplace. But both should be O(N). Also cheers for the link to that site -- looks very handy. – mgiuca Jun 01 '11 at 03:03
  • 2
    its good for oneliner, since concat returns the array, and unshift returns the new length – Bernardo Dal Corno May 04 '18 at 17:39
35

If you would like to prepend array (a1 with an array a2) you could use the following:

var a1 = [1, 2];
var a2 = [3, 4];
Array.prototype.unshift.apply(a1, a2);
console.log(a1);
// => [3, 4, 1, 2]
Michael Robinson
  • 29,278
  • 12
  • 104
  • 130
uroslates
  • 479
  • 4
  • 2
8

In an immutable way, this is probably the best way:

const x = 1
const list = [2, 3, 4]
const newList = [x].concat(list) // [1, 2, 3, 4]
carlesba
  • 3,106
  • 3
  • 17
  • 16
7

I have some fresh tests of different methods of prepending. For small arrays (<1000 elems) the leader is for cycle coupled with a push method. For huge arrays, Unshift method becomes the leader.

But this situation is actual only for Chrome browser. In Firefox unshift has an awesome optimization and is faster in all cases.

ES6 spread is 100+ times slower in all browsers.

https://jsbench.me/cgjfc79bgx/1

John Klimov
  • 161
  • 2
  • 4
  • 2
    Great answer! But to ensure you don't lose part of it, you might reproduce your test cases here (maybe with a few sample run results) in case, eg, jsbench goes away. – ruffin Dec 26 '18 at 21:53
  • You're only prepending one value and with the spread, you're creating a big new array, that's why unshift is much 100x faster ... if you prepend a full array to a full array, then the results are very different, and the spread operator is "only" 4x slower, that's because it creates a completely new array in memory, whereas unshift works in-place. See this benchmark: https://jsbench.me/oul3x61nxy/1 – Octo Poulos Jun 02 '22 at 15:42
6

Calling unshift only returns the length of the new array. So, to add an element in the beginning and to return a new array, I did this:

let newVal = 'someValue';
let array = ['hello', 'world'];
[ newVal ].concat(array);

or simply with spread operator:

[ newVal, ...array ]

This way, the original array remains untouched.

rehman_00001
  • 1,299
  • 1
  • 15
  • 28
6

f you need to preserve the old array, slice the old one and unshift the new value(s) to the beginning of the slice.

var oldA=[4,5,6];
newA=oldA.slice(0);
newA.unshift(1,2,3)

oldA+'\n'+newA

/*  returned value:
4,5,6
1,2,3,4,5,6
*/
kennebec
  • 102,654
  • 32
  • 106
  • 127
4

There is special method:

a.unshift(value);

But if you want to prepend several elements to array it would be faster to use such a method:

var a = [1, 2, 3],
    b = [4, 5];

function prependArray(a, b) {
    var args = b;
    args.unshift(0);
    args.unshift(0);
    Array.prototype.splice.apply(a, args);
}

prependArray(a, b);
console.log(a); // -> [4, 5, 1, 2, 3]
bjornd
  • 22,397
  • 4
  • 57
  • 73
  • 2
    No... unshift will add array as the first argument: var a = [4, 5]; a.unshift([1,2,3]); console.log(a); // -> [[4, 5], 1, 2, 3] – bjornd Jun 01 '11 at 03:16
  • 4
    You are correct! You would need to use `Array.prototype.unshift.apply(a,b);` – david Jun 01 '11 at 03:28
3

Example of prepending in-place:

var A = [7,8,9]
var B = [1,2,3]

A.unshift(...B)

console.log(A) // [1,2,3,7,8,9]
Miguel Mota
  • 20,135
  • 5
  • 45
  • 64
3

I just ran a benchmark of 4 algorithms, on Chrome:

in-place:

// 1) splice method
{
    let x = [8, 4, 1, 4, 124, 1, 14, 11, 9, 100, 6, 44];
    const y = [5, 6, 99, 5, 3, 4];
    x.splice(0, 0, ...y); // 87'426 ops/s (but big variation of 35%)
    // x is [5, 6, 99, 5, 3, 4, 8, 4, 1, 4, 124, 1, 14, 11, 9, 100, 6, 44]
}

// 2) unshift method
{
    let x = [8, 4, 1, 4, 124, 1, 14, 11, 9, 100, 6, 44];
    const y = [5, 6, 99, 5, 3, 4];
    x.unshift(...y); // 69'471 ops/s
    // x is [5, 6, 99, 5, 3, 4, 8, 4, 1, 4, 124, 1, 14, 11, 9, 100, 6, 44]
}

copy:

// 3) spread operator
{
    const x = [8, 4, 1, 4, 124, 1, 14, 11, 9, 100, 6, 44];
    const y = [5, 6, 99, 5, 3, 4];
    const z = [...y, ...x]; // 17'118 ops/s
    // z is [5, 6, 99, 5, 3, 4, 8, 4, 1, 4, 124, 1, 14, 11, 9, 100, 6, 44]
}

// 4) concat method
{
    const x = [8, 4, 1, 4, 124, 1, 14, 11, 9, 100, 6, 44];
    const y = [5, 6, 99, 5, 3, 4];
    const z = y.concat(x); // 6'286 ops/s
    // z is [5, 6, 99, 5, 3, 4, 8, 4, 1, 4, 124, 1, 14, 11, 9, 100, 6, 44]
}

Summary: if you want to preprend in place, both unshift and splice are good, and if you want a copy, then the spread operator seems the best choice ... on Chrome at least.

Octo Poulos
  • 523
  • 6
  • 5