Heaviest Stone algorithm time complexity
Issue(s)

You sort descending, but then take and assign the heaviest stone to x and the second heaviest stone to y whereas in your problem you state &quot;...stones have weights x and y with x &lt;= y&quot;.
Code isn't as DRY (Don't Repeat Yourself) as it could be.

Suggestions

Provide stones a default/initial empty array value.
Sort descending and assign const y = stones[0]; and const x = stones[1]; and push new value stones.push(y - x); to match problem. (Or normal sort ascending and take from array end, more on this later)
Use array destructuring to assign to x and y since array::splice returns an array of spliced out values.
Sort once at the beginning of each iteration.
Remove extraneous array length checks, let the while-loop condition handle it.

Code
const lastStoneWeight = (stones = []) =&gt; {
  if (!stones || !stones.length) return 0;

  while (stones.length &gt; 1) {
    stones.sort((a, b) =&gt; a - b);
    const [x, y] = stones.splice(-2);
    if (x !== y) stones.push(y - x);
  }
  return stones[0] || 0;
};

If you can use Optional Chaining and Nullish Coalescing
const lastStoneWeight = (stones = []) =&gt; {
  if (!stones?.length) return 0;

  while (stones.length &gt; 1) {
    stones.sort((a, b) =&gt; a - b);
    const [x, y] = stones.splice(-2);
    if (x !== y) stones.push(y - x);
  }
  return stones[0] ?? 0;
};

Here I use the default sort (ascending) and splice off the last two elements, removes need to shift entire forward 2 indices.
Using a Heap Data Structure
Since you ask about improved performance and the going suggestion is to use a heap/priority queue. This is a very similar implementation.
const lastStoneWeightHeap = (stones = []) =&gt; {
  if (!stones?.length) return 0;

  const heap = new PriorityQueue(); // &lt;-- uses equivalent comparator as array::sort
  stones.forEach((stone) =&gt; heap.enq(stone)); // &lt;-- populate heap

  while (heap.size() &gt; 1) {
    const y = heap.deq();
    const x = heap.deq();
    if (x !== y) heap.enq(y - x);
  }
  return heap.size() ? heap.deq() : 0;
};

t1 is regular algorithm
t2 is version using heap/priority queue data structure
10 iterations x 10000 runs
    # Elements  t0 avg      t1 avg 
1   8           0.00363     0.00106 
2   16          0.01036     0.00157 
3   32          0.01781     0.00224 
4   64          0.09148     0.00432 
5   128         0.22560     0.00944 
6   256         0.56833     0.01618 
7   512         2.37584     0.06091 
8   1024        8.78741     0.12614 
9   2048        34.29092    0.29697 
10  4096        130.50169   0.63872 

Notes:

Time is in milliseconds

https://www.npmjs.com/package/priorityqueuejs
Conclusion
Using a heap data structure is orders of magnitude an improvement. With the naive implementation it is clearly at least an \$O(n^2)\$ complexity as each time the dataset size doubles (2x) the runtime roughly quadruples (~4x) whereas the implementation using a heap roughly only doubles (~2x) the runtime with each doubling of the dataset.
Performance Benchmarking
performanceBenchmark.js
const measurePerf = (fn, data, runs = 1e3) =&gt;
  [...Array(runs).keys()]
    .map(() =&gt; {
      const start = performance.now();
      fn([...data]);
      const end = performance.now();
      return end - start;
    })
    .reduce((total, current) =&gt; total + current) / runs;

const toFixed = (val, fixed) =&gt;
  Number.isFinite(val) ? Number(val).toFixed(fixed) : val;

export const benchmark = async ({
  functions = [],
  createRunData,
  iterations = 5,
  runs = 1e3,
  logIntermediateResults
}) =&gt; {
  logIntermediateResults &amp;&amp; console.log(`${iterations} x ${runs}`);

  const results = [];

  logIntermediateResults &amp;&amp;
    console.log(
      `\t# Elements\t${functions.map((_, i) =&gt; `t${i} avg`).join(&quot;\t&quot;)}`
    );

  for (let i = 0; i &lt; iterations; i++) {
    const data = createRunData(i);

    const res = await Promise.all(
      functions.map((fn) =&gt; measurePerf(fn, data, runs))
    );

    results.push(res);

    logIntermediateResults &amp;&amp;
      console.log(
        `${i + 1}\t${data.length}\t${res
          .map((t) =&gt; `${toFixed(t, 5)}`)
          .join(&quot;\t&quot;)}`
      );
  }

  return results;
};

Setup &amp; benchmark
const ITERATIONS = 10;
const RUNS = 1e4;
const SEED = 8;

const functions = [
  lastStoneWeight,
  lastStoneWeightHeap,
];

const createRunData = (i) =&gt; {
  const dataLength = SEED &lt;&lt; i;
  const stones = [...Array(dataLength).keys()].map(() =&gt;
    Math.floor(Math.random() * dataLength)
  );
  return stones;
};

benchmark({
  functions,
  createRunData,
  iterations: ITERATIONS,
  runs: RUNS,
  logIntermediateResults: true
});

Extended heap implementation benchmark
15 x 10000 
    # Elements  t0 avg 
1   8           0.00100 
2   16          0.00171 
3   32          0.00242 
4   64          0.00434 
5   128         0.00933 
6   256         0.01825 
7   512         0.05681 
8   1024        0.13715 
9   2048        0.27621 
10  4096        0.59631 
11  8192        1.24577 
12  16384       4.75092 
13  32768       6.09799 
14  65536       13.07677 
15  131072      28.88058