Weird array behavior in JavaScript

I’m trying to write a tool for visualizing the bubble sort algorithm.

The array arr holding all the values, that is to be sorted, is a global variable. The user clicks the "Fill" button btnFill to fill the array with unsorted, random numbers using the fillArray function. The function returns an array of objects: {val: x, col: "y"} where x is the value and col is a color class used for CSS.

When the array is filled, if the user clicks the "sort" button btnSort, the bubbleSort function is invoked where I guess my problem is:

This function saves a copy of the array arr on every iteration of the inner loop. This is an idea that I had since I was having a lot of issues using setInterval to animate – I could just save snapshots of the array during every stage of the sorting, and then pass it to the draw function so that there wouldn’t be any JavaScript weirdness combining loops and intervals.

The function finishes running and is expected (by me at least) to return an array filled with copies of the arr array from every loop iteration.

The actual behavior which I have tried to understand for hours is that the snapShot array is filled with arrays of a sorted arr. All elements are the same – sorted arrays. Doesn’t make sense to me since when debugging it comes out as sorted in console.log when it enters the sorting loop for the first time.

I appreciate any help greatly, thank you!

–EDIT–
I’m aware that the snapShot array gets sent as an array of arrays and will not be drawn to the screen but the problem is somewhere before that. That part was changed for debugging reasons

const btnFill = document.querySelector("#btnFill");
const btnSort = document.querySelector("#btnSort");
const container = document.querySelector(".container");

let arr = [];

btnFill.addEventListener("click", function () {
  arr = fillArray();
  draw(arr);
});

btnSort.addEventListener("click", function () {
  let sorted = bubbleSort();
  draw(sorted);
});

function bubbleSort() {
  let snapShots = [];
  for (let i = 0; i < arr.length - 1; i++) {
    for (let j = 0; j < arr.length - 1 - i; j++) {
      if (arr[j].val > arr[j + 1].val) {
        let tmp = arr[j].val;
        arr[j].val = arr[j + 1].val;
        arr[j + 1].val = tmp;
        snapShots.push([...arr]);
      }
    }
  }
  return snapShots;
}

function fillArray() {
  const tmp = [];
  const len = 50;
  for (let i = 0; i < len; i++) {
    tmp[i] = { val: Math.floor(Math.random() * 100 + 1), col: "red" };
  }
  return tmp;
}

function draw(array) {
  container.innerHTML = "";
  for (let i = 0; i < array.length; i++) {
    const tmp = document.createElement("div");
    tmp.style.height = array[i].val + "%";
    tmp.classList.add("bar", array[i].col);
    container.appendChild(tmp);
  }
}

14 thoughts on “Weird array behavior in JavaScript”

  1. The reason that it’s not working like you want it is because you are doing shallow copies of the array, but the objects in each position are the same references.

    This means that this part of the code:

        let tmp = arr[j].val;
        arr[j].val = arr[j + 1].val;
        arr[j + 1].val = tmp;
    

    Is changing the object that every snapshot element is pointing in the same position. And that’s why you get every array sorted, because you are creating new arrays with the same references to the objects.

    To get what you want you can do a deep copy instead, which also creates new references of the nested values.

    function bubbleSort() {
      let snapShots = [];
      for (let i = 0; i < arr.length - 1; i++) {
        for (let j = 0; j < arr.length - 1 - i; j++) {
          if (arr[j].val > arr[j + 1].val) {
            let tmp = arr[j].val;
            arr[j].val = arr[j + 1].val;
            arr[j + 1].val = tmp;
            snapShots.push(JSON.parse(JSON.stringify(arr)));
          }
        }
      }
      return snapShots;
    }
    

    Other solution (and simpler) is to change the position of the object in the array instead of changing the property val of the object:

    function bubbleSort() {
      let snapShots = [];
      for (let i = 0; i < arr.length - 1; i++) {
        for (let j = 0; j < arr.length - 1 - i; j++) {
          if (arr[j].val > arr[j + 1].val) {
            let tmp = arr[j];
            arr[j] = arr[j + 1];
            arr[j + 1] = tmp;
            snapShots.push([...arr]);
          }
        }
      }
      return snapShots;
    }
    
    Reply

Leave a Comment