DEV Community

Cover image for How to shift array position in vanilla JavaScript
Knut Melvær
Knut Melvær

Posted on

How to shift array position in vanilla JavaScript

I was helping a user over at sanity.io yesterday doing some content migration, where they wanted to take the last element of an array and put it in the first position. Every time I get a question like this, I like to see if I manage to solve it without going to any libraries. Both because I don't want to introduce more dependencies for the person with the question, but most of all, it's an opportunity for some mental exercise.

So, how to change an array like this:

["Cersei", "Joffrey", "Ilyn", "The Mountain", "The Hound", "Melisandre", "Beric", "Thoros", "Tywin", "Meryn", "Walder"]

to this?

["Walder", "Cersei", "Joffrey", "Ilyn", "The Mountain", "The Hound", "Melisandre", "Beric", "Thoros", "Tywin", "Meryn"]

My first instinct was to make a new array just targeting the last element with the list length subtracted by 1, and then spreading the rest of the array using splice. Push the green run button to run the code:

const theList = ["Cersei", "Joffrey", "Ilyn", "The Mountain", "The Hound", "Melisandre", "Beric", "Thoros", "Tywin", "Meryn", "Walder"] const newList = [theList[theList.length - 1], ...theList.splice(0, theList.length - 1)] console.log(newList)

Some of you will already have guessed why this is a bad idea, and some of you will be like me and always mix up splice and slice. Let’s see what happens with theList when we run the code above:

const theList = ["Cersei", "Joffrey", "Ilyn", "The Mountain", "The Hound", "Melisandre", "Beric", "Thoros", "Tywin", "Meryn", "Walder"] const newList = [theList[theList.length - 1], ...theList.splice(0, theList.length - 1)] // check output console.log(newList) // check output console.log(theList)

Not ideal (unless you're Arya). Replacing splice with slice leaves the original array intact, and will not mutate it (which can lead to a mountain of problems):

const theList = ["Cersei", "Joffrey", "Ilyn", "The Mountain", "The Hound", "Melisandre", "Beric", "Thoros", "Tywin", "Meryn", "Walder"] const newList = [theList[theList.length - 1], ...theList.slice(0, theList.length - 1)] console.log(newList) console.log(theList)

This leaves us with exactly what we want.

Another way to go about it is to use the Array.prototype.reduce-method, which to me feel a bit cleaner. Also, .reduce is worth investing time into learning.

const theList = ["Cersei", "Joffrey", "Ilyn", "The Mountain", "The Hound", "Melisandre", "Beric", "Thoros", "Tywin", "Meryn", "Walder"] const newList = theList.reduce( (accumulatedArray, currentItem, index, originalArray) => index < originalArray.length - 1 ? [...accumulatedArray, currentItem] : [currentItem, ...accumulatedArray], [] ) console.log(newList) console.log(theList)

This can, of course, be made into a function. Tempting as it may be, I don't recommend using an anonymous arrow (=>) function for this, as it doesn't show up with a name in the console stack trace if something wrong occurs:

const theList = ["Cersei", "Joffrey", "Ilyn", "The Mountain", "The Hound", "Melisandre", "Beric", "Thoros", "Tywin", "Meryn", "Walder"] function shiftLastToFirst (theArray) { return theArray.reduce( (accumulatedArray, currentItem, index, originalArray) => { return index < originalArray.length - 1 ? [...accumulatedArray, currentItem] : [currentItem, ...accumulatedArray] }, [] ) } shiftLastToFirst(theList)

Now, I'm certain there are many smarter ways of doing this. And the reason I'm writing this up and putting it here is that I want to learn about them.

How would you go about solving this?

Oldest comments (19)

Collapse
 
somedood profile image
Basti Ortiz • Edited

I think this will do just fine.

function shiftArray(arr) {
  // Get the last element in the array
  const lastElement = arr[arr.length - 1];

  // Prepend the `lastElement` to the start of the array
  arr.unshift(lastElement);

  // Remove the duplicate element at the end of the array
  arr.pop();

  // Hooray! We party! 🎉
  return arr;
}

// Plop in `theList`
shiftArray(theList);
Collapse
 
lexlohr profile image
Alex Lohr

Why not use a really simple solution with plain ES5?

function lastToFirst(array) {
  const result = array.slice(0);
  result.unshift(result.pop());
  return result;
}
Collapse
 
somedood profile image
Basti Ortiz

I mean it would be ES5 if const was var. 😂

Anyway, I like how concise it is. Three lines of code sure does look more satisfying than whatever I did, despite their similarities.

Collapse
 
xtrasmal profile image
Xander

Yeah that is it.
This article is lol though. I feel like the article author is trolling.
Especially when I read: "Every time I get a question like this, I like to see if I manage to solve it without going to any libraries".

Collapse
 
kmelve profile image
Knut Melvær

It wasn't trolling! 😁

It's more like “I got serious about JavaScript when the first features of ES6 and every problem seem like a nail”. And that's exactly why I asked for other approaches and learned something.

…and some meta-point about how feeling super JS savvy because I know .reduce, and being humbled because I don't know some super obvious ES5 features that have been around for a long while.

But thanks for the input!

Thread Thread
 
xtrasmal profile image
Xander

haha ok, but anyways I did not want to offend or anything. You took the scenic route :D
And that delivers value for future endeavours. Happy holidays to you and your family.

Collapse
 
michaeljota profile image
Michael De Abreu

This was literally my first thought when I was reading the problem.

Collapse
 
link2twenty profile image
Andrew Bone

I agree this is readable.

If you handed this to a new developer and asked them what it did they might be lost for a little while.

const lastToFirst = _ => [_.pop(), ..._];
Collapse
 
lexlohr profile image
Alex Lohr

Also, that would change the original array, which is not the intended functionality.

Thread Thread
 
link2twenty profile image
Andrew Bone • Edited

Good point, you'd have to do something like

const lastToFirst = _ => {const $ = _.slice(); return [$.pop(), ...$]};

which is even more convoluted.

Thread Thread
 
lexlohr profile image
Alex Lohr

How about const lastToFirst = _ => ($ => [$.pop(), ...$])(_.slice()) as even more convoluted example?

Thread Thread
 
link2twenty profile image
Andrew Bone

Yikes...

I kinda love it.

Collapse
 
bgadrian profile image
Adrian B.G.

Probably that array is not going to be accessed using the index, it looks like an unordered collection.

If this is true you need a linked list not an array, making the move operation 2N more efficient and resulting in a simpler code.

Collapse
 
thejoezack profile image
Joe Zack

Great point! Array Data Structures aren't very efficient at arbitrarily inserting or plucking (even though JS provides convenient shift/deshift methods.)

With this small of a dataset it doesn't matter, but it's a great point. :)

Collapse
 
thomasjunkos profile image
Thomas Junkツ • Edited

I would go for

const lastToFirst = (arr) => [arr.pop(), ...arr]

or

ltf= function(arr){ return [arr.pop()].concat(arr) }
Collapse
 
thejoezack profile image
Joe Zack

ES6 FTW!

Collapse
 
kennethtruyers profile image
Kenneth Truyers

I very much like the plain ES5 solution by @lexlohr . Here's another, more generic version that lets you rotate an array by a variable number of positions:

const rotateArray = (array, count) => {
  const arr = [...array];

  count = (count || 0) % arr.length;
  if (count < 0)  count += arr.length;

  var removed = arr.splice(0, count);
  arr.push.apply(arr, removed);

  return arr;
}
Collapse
 
wolfhoundjesse profile image
Jesse M. Holmes

Everyone is talking about your code, and I'm stuck appreciating this beautiful gem:

Not ideal (unless you're Arya).

Collapse
 
kmelve profile image
Knut Melvær

Thank you for noticing!

Some comments may only be visible to logged-in visitors. Sign in to view all comments.