Guides

JavaScript Reverse Array: reverse() vs toReversed()

Compare mutating reverse() with copying toReversed(), including object references, sparse arrays, compatibility options, and runnable JavaScript tests.

By Text Reverser TeamNovember 1, 20267 min read

When you need to reverse an array in JavaScript, the important choice is not the direction. It is whether the operation may change the array you already have. reverse() changes the existing array in place. toReversed() creates a new array and leaves the source order alone. That distinction prevents bugs when the same list is stored in more than one variable, rendered in a UI, or used again later.

This guide explains both methods with practical examples, compatibility alternatives, sparse-array behavior, object references, and cases where reversing is the wrong operation. The MDN reference for reverse() and the MDN reference for toReversed() describe the core contracts used here.

The quick answer

Use reverse() when you deliberately want to reorder one existing array:

const colors = ["red", "blue", "green"];
colors.reverse();

console.log(colors); // ["green", "blue", "red"]

Use toReversed() when you need a reversed result while preserving the original:

const colors = ["red", "blue", "green"];
const reversedColors = colors.toReversed();

console.log(reversedColors); // ["green", "blue", "red"]
console.log(colors);         // ["red", "blue", "green"]

The method names may look like a stylistic difference, but the ownership behavior is different. If other code relies on the current order, derive a copy rather than changing its source. Each example in this guide is independent; run snippets separately rather than redeclaring their const variables together in one scope.

How reverse() behaves

reverse() mutates the array on which it is called and returns a reference to that same array. It does not return an independent result. This is true even when the variable is declared with const:

const items = ["first", "second", "third"];
const result = items.reverse();

console.log(items);            // ["third", "second", "first"]
console.log(result === items); // true

const prevents assigning a different value to items; it does not make the array immutable. Calling items.reverse() is allowed because the array contents can change. This often surprises people who interpret const as meaning that every value reachable through the variable is unchangeable.

The mutation is visible through aliases. If a function receives items, or another property stores that array, reversing through one reference changes what every reference sees. That can be useful for a deliberate in-place update, but a later consumer may unexpectedly receive the opposite order.

Calling reverse() twice is not a copying technique. The first call changes the source, and the second changes it back. If you need an independent reversed array, copy first or use toReversed(). Never assume the value returned by reverse() has been detached from the original.

How toReversed() differs

toReversed() is the non-mutating counterpart. It returns a new array with elements in the opposite order, so source and result have different array identities:

const pages = ["intro", "examples", "summary"];
const backwards = pages.toReversed();

console.log(backwards);           // ["summary", "examples", "intro"]
console.log(pages);               // ["intro", "examples", "summary"]
console.log(backwards !== pages); // true

This is convenient when stored order is meaningful but a screen needs the opposite presentation. It also gives state-update code a new array reference without editing the previous value. Reversal does not discover which item is newest, though: that presentation is correct only if the source was already in the corresponding chronological order.

Check the MDN compatibility data for the browsers or embedded runtimes you support. Do not assume every deployed environment has this modern method. If it is unavailable, make a copy and use the older reverse() method:

const backwards = pages.slice().reverse();
// Or: const backwards = [...pages].reverse();

These lines use the pages array from the previous example. For ordinary dense arrays, both forms protect its order and produce the expected result. There are meaningful differences for sparse arrays, discussed below.

A copy is shallow, not deep

Reversing an array changes element positions. It does not clone objects stored as those elements. This applies to toReversed(), slice().reverse(), and [...array].reverse():

const records = [
  { label: "first" },
  { label: "second" },
];

const backwards = records.toReversed();
backwards[0].label = "changed";

console.log(records[1].label); // "changed"

The arrays have independent positions and identities, but both contain references to the same record objects. Changing a record through the reversed copy changes that record in the source too. The promise concerns the outer array’s order, not every nested value. The MDN Array documentation explains this shallow-copy behavior.

If your requirement is only to reverse order, sharing records is often desirable. If you also need independent records, copy them according to their schema. Do not treat structuredClone() as a universal fix: it has supported-value rules and does not preserve every application object’s behavior. Make deeper copying a deliberate data-model decision.

Empty arrays, one item, and duplicates

An empty array reverses to an empty array. A one-element array keeps the same element. Duplicates keep their counts and move according to their positions; reversal does not remove or deduplicate anything:

console.log([].toReversed());                // []
console.log(["only"].toReversed());          // ["only"]
console.log(["A", "B", "A"].toReversed()); // ["A", "B", "A"]

The last output looks unchanged because the sequence is symmetrical. That does not mean duplicates are treated as unique values. Reversal maps the first position to the last, the second to the second-to-last, and so on. Even for an empty or symmetrical input, toReversed() returns a different array rather than the original reference.

Sparse arrays: holes are not undefined values

A sparse array contains absent index properties, commonly called holes. An explicit undefined value still occupies an index property. That distinction matters when another operation checks which indexes exist:

const sparse = ["A", , "C", "D"];

const inPlace = sparse.slice();
inPlace.reverse();
console.log(2 in inPlace); // false: the hole moved to index 2

const copied = sparse.toReversed();
console.log(2 in copied); // true
console.log(copied[2]);   // undefined

For this ordinary sparse array, reverse() preserves the hole while moving it to its mirrored position. toReversed() reads the absent element as undefined and returns a dense array. The ECMAScript toReversed() algorithm specifies this copying behavior.

Use an existence check such as index in array when the distinction matters; JSON serialization can hide it. These examples assume ordinary arrays without inherited indexed properties or custom iterators.

A spread copy and Array.from() also turn holes in ordinary arrays into explicit undefined values. slice().reverse() preserves holes because slice() preserves them. If sparsity is part of your data model, choose intentionally rather than treating every non-mutating spelling as identical.

Reverse is not sort

Reversal changes existing positions. It does not compare values, rank numbers, or alphabetize strings. For example, [3, 1, 2].toReversed() produces [2, 1, 3], not descending or ascending numeric order.

If you need descending numbers, sorting is a separate operation:

const numbers = [3, 1, 2];
const descending = numbers.slice().sort((a, b) => b - a);

console.log(descending); // [3, 2, 1]
console.log(numbers);    // [3, 1, 2]

The numeric comparator matters: the default array sort does not mean numeric ranking. Modern runtimes also provide the copying toSorted() method. Decide whether the task is to show an existing sequence backwards or establish a new ordering before selecting either operation.

When a backward loop is better

Sometimes you do not need a reversed array. If you only want to process values from the end toward the beginning, a backward loop avoids a second array and leaves the source alone:

const items = ["red", "blue", "green"];
for (let index = items.length - 1; index >= 0; index -= 1) {
  console.log(items[index]);
}

This prints green, blue, then red. For an empty array, the starting index is -1, so nothing runs. Reverse traversal is useful for output, searching from the end, or processing a large list when materializing another array serves no purpose.

For sparse arrays, direct indexing reads an absent element as undefined; methods such as forEach() normally skip absent indexes. Choose traversal semantics as carefully as copy semantics. For an ordinary in-place reversal, the built-in method is clearer than a hand-written swap loop unless implementing the algorithm is itself your exercise.

Reverse words without reversing their letters

An array of word tokens is not a character array. To implement a simple whitespace-normalizing word-order task, split the sentence, reverse the newly created token array, and join it:

function reverseWords(text) {
  const trimmed = text.trim();
  if (!trimmed) return "";
  return trimmed.split(/\s+/u).reverse().join(" ");
}

console.log(reverseWords("  red   blue green  "));
// "green blue red"

Here mutation is safe because split() creates a private array for this operation. There is no need to copy that fresh array again, and the original string remains unchanged. The helper normalizes whitespace, leaves punctuation attached to its token, and does not promise full linguistic word segmentation.

For the non-code distinction, see reversing word order without scrambling letters. Character-level reversal has different Unicode requirements, covered in JavaScript string reversal without breaking emoji. Array reversal does not itself choose safe character boundaries.

Performance, ownership, and UI rendering

Conceptually, an in-place reversal of a normal dense array takes linear time and constant auxiliary array space. A reversed copy requires another array proportional to the input length. Engine optimizations vary; do not choose between these methods using an unmeasured claim that one is always fastest.

The useful question is whether copying protects something that must remain unchanged. If both forward and backward views are needed, derive and reuse one reversed copy. If the array is private and mutation is intentional, reverse() may be sufficient. Avoid reversing a shared cache solely to prepare one screen: another consumer could then receive presentation order instead of canonical order.

At function boundaries, document whether the argument changes. A reversed-copy helper lets callers keep using their original input; an in-place helper should make its side effect obvious. Neither contract is universally best, but both are safer than an ambiguous function that silently reorders its argument.

For UI rendering, keep the canonical list in its stored order and derive the particular view’s order when practical. This reduces surprises in pagination, refreshes, and later comparisons. For very large lists, consider whether reverse traversal can serve the view without allocating a second full array.

Run a self-contained behavior test

Save this as reverse-array.test.cjs and run node reverse-array.test.cjs. It requires Node’s built-in assertion module, not an external test framework:

const assert = require("node:assert/strict");

const source = ["red", "blue", "green"];
const changed = source.slice();
assert.strictEqual(changed.reverse(), changed);
assert.deepEqual(changed, ["green", "blue", "red"]);
assert.deepEqual(source, ["red", "blue", "green"]);
assert.deepEqual([].slice().reverse(), []);
assert.deepEqual(["only"].slice().reverse(), ["only"]);

const sparse = ["A", , "C", "D"];
assert.equal(2 in sparse.slice().reverse(), false);
assert.equal(2 in [...sparse].reverse(), true);

if (typeof source.toReversed === "function") {
  const copy = source.toReversed();
  assert.deepEqual(copy, ["green", "blue", "red"]);
  assert.notStrictEqual(copy, source);
  assert.deepEqual(source, ["red", "blue", "green"]);

  const dense = sparse.toReversed();
  assert.equal(2 in dense, true);
  assert.equal(dense[2], undefined);

  const records = [{ label: "first" }, { label: "second" }];
  const recordsCopy = records.toReversed();
  recordsCopy[0].label = "changed";
  assert.equal(records[1].label, "changed");
} else {
  console.log("toReversed() unavailable; modern-method checks skipped");
}

console.log("Array reversal checks passed");

The feature guard makes skipped modern-method checks explicit on older runtimes. It does not provide a polyfill. The methods and behaviors above were tested on Node.js v24.19.0, including mutation, distinct copy identity, shallow object references, and sparse holes.

Before shipping, extend the tests around your actual contract: must input order remain unchanged, are record objects shared, and are sparse arrays allowed? For a quick text transformation without writing code, use the Text Reverser tool. For application code, choose the operation by ownership and data structure, not by how similar the method names look.

Try the Text Reverser tool

Reverse text, words, or letters instantly — free and entirely in your browser.

Open the tool

Permalink: https://www.textreverser.me/blog/javascript-reverse-array