JAVASCRIPT / DESTRUCTURING, ITERABLES, AND GENERATORS
Swapping variables and multiple return values
Swap two variables in one statement without a temporary, swap array slots or object fields in place, and return several values from a function.
What you will learn
- Swap two variables with [a, b] = [b, a] and no temporary variable
- Swap array elements or object properties in place with the same pattern
- Return two ordered values as an array, three or more as a named object
- End the previous statement with ; so a line starting with [ is not read as an index
Understanding Swapping variables and multiple return values
The swap idiom [a, b] = [b, a] works because a destructuring assignment runs in two distinct phases: the engine first evaluates the whole right-hand side, producing a real array that holds the current values of b and a, and only then matches that array's elements against the pattern on the left, slot by slot. That array is the temporary variable, you just never give it a name. This is why the three-line dance with let temp = a is unnecessary, and why writing to a first cannot destroy the value b is about to receive.
There is no let or const in front of the swap, and that matters: [a, b] = [b, a] assigns to variables that already exist, it does not declare anything. Any expression that can sit on the left of a plain = can fill a slot, so [arr[i], arr[j]] = [arr[j], arr[i]] swaps two array elements and [o.x, o.y] = [o.y, o.x] swaps two properties. The same snapshot rule covers rotations: [a, b, c] = [c, a, b] moves all three at once, while the hand-written a = c; b = a; c = b; would leave all three equal to the original c.
A JavaScript function returns exactly one value, so multiple return values really means packing results into one container and unpacking them at the call site. An array literal gives a positional group, return [quotient, remainder], which is terse but forces every caller to remember the order; get it backwards and nothing throws, you simply compute the wrong number. An object literal labels each result, so callers pull out remainder by name, may list fields in any order, and keep working when a third field is added later. Reach for an array when the order is self-evident, like [min, max], and for an object as soon as the names carry information.
let width = 300;
let height = 150;
[width, height] = [height, width];
console.log(width, height);
function divmod(n, d) {
return [Math.trunc(n / d), n % d];
}
const [quotient, remainder] = divmod(17, 5);
console.log(quotient, remainder);
function bounds(nums) {
return { lowest: Math.min(...nums), highest: Math.max(...nums) };
}
const { highest, lowest } = bounds([4, -2, 9, 3]);
console.log(lowest, highest);A destructuring assignment evaluates its entire right-hand side before writing to any target, which is what makes a one-line swap safe and what lets one return statement deliver several values.
Worked examples
Swapping array slots in place
Reverses an array without building a new one, using array elements as assignment targets.
const letters = ['a', 'b', 'c', 'd', 'e'];
for (let i = 0, j = letters.length - 1; i < j; i++, j--) {
[letters[i], letters[j]] = [letters[j], letters[i]];
}
console.log(letters.join(''));Example explained
Line 1The pattern's slots are letters[i] and letters[j], legal because any valid assignment target may appear there, not only a variable name.
Line 2[letters[j], letters[i]] is built from the current contents first, so overwriting letters[i] cannot corrupt the value still owed to letters[j].
Line 3The loop stops as soon as i < j fails, which leaves the middle element of an odd-length array exactly where it was.
Positional return versus named return
Shows how a caller can silently misread an array return, and why an object return cannot be misordered.
function pairArray(text) {
const at = text.indexOf('-');
return [Number(text.slice(0, at)), Number(text.slice(at + 1))];
}
function pairObject(text) {
const at = text.indexOf('-');
return { start: Number(text.slice(0, at)), end: Number(text.slice(at + 1)) };
}
const [end, start] = pairArray('10-25');
console.log('array form:', end - start);
function report(text) {
const { end, start } = pairObject(text);
console.log('object form:', end - start);
}
report('10-25');Example explained
Line 1pairArray returns positions only, so the caller's names are labels for slot 0 and slot 1, and end receives 10.
Line 2Nothing warns about the mix-up: the code runs and quietly reports -15 instead of 15.
Line 3pairObject matches by property name, so listing end before start in the pattern still binds end to 25.
Line 4That is the whole trade-off: array returns are shorter to write, object returns are harder to get wrong.
The line-start bracket trap
Demonstrates how a missing semicolon turns a swap into a property lookup on the line above.
let p = 1;
let q = 2;
const config = { size: 2 }
[p, q] = [q, p]
console.log(p, q);
console.log(Array.isArray(config));Example explained
Line 1The const line has no semicolon, so the parser joins both lines into const config = { size: 2 }[p, q] = [q, p];
Line 2[p, q] becomes a computed property key: the comma operator yields q, so the key is "2" and no swap happens.
Line 3An assignment expression evaluates to the assigned value, so config ends up holding the array [2, 1] rather than the object.
Line 4Nothing throws, which is why this bug survives; terminate the line above, or begin the swap line with a semicolon.
Important notes
Every slot on the left must be assignable: [a, obj.key, arr[0]] is fine, [a, 3] is a SyntaxError, and a const variable throws TypeError: Assignment to constant variable when the swap runs.
Swapping objects swaps references only: [x, y] = [y, x] changes which object each name points at and leaves the contents of both objects untouched.
Common mistakes
Writing let [a, b] = [b, a] to swap: in the same scope that is a SyntaxError for redeclaring a, and inside a nested block it silently creates two new variables while the outer pair keeps its old values.
Dropping the semicolon on the preceding line, so [a, b] = [b, a] is parsed as an index into that expression; usually nothing throws and the swap simply never happens.
Reversing the names when unpacking a positional return, as in const [remainder, quotient] = divmod(17, 5): no error appears, both variables just hold the other value for the rest of the program.
Try it yourself
Change, predict, then run
In a browser console declare let low = 9, high = 2; and put them in the right order with one statement that uses no temporary variable. Then write bounds(nums) that hands back both the smallest and largest number, and unpack both results in a single line at the call site.
Open the JavaScript workspaceCheck your understanding
After let a = 1, b = 2, c = 3; and then [a, b, c] = [c, a, b];, what does console.log(a, b, c) print?
- 3 3 3
- 3 1 2
- 1 2 3
- 2 3 1
Show answer
The right-hand side is evaluated first into the array [3, 1, 2], and only then are a, b and c written, so each target gets a value from the pre-assignment snapshot: 3 1 2. The tempting 3 3 3 is what the sequential a = c; b = a; c = b; would produce, because there each line reads a variable the previous line has already overwritten.