You have been using this syntax in passing for three lessons, always with a footnote saying "this is explained in 04-06". The moment has come. const [removed] = tasks.splice(i, 1), for (const [i, task] of backlog.entries()), pair[0] and pair[1] while walking Object.entries: all of those are the same idea, and the idea is called destructuring. It consists of unpacking an array's elements into named variables, in a single line and with no indexes. It looks like a cosmetic detail and it is not: it changes the readability of indexed loops, it lets you swap variables without a helper, it makes a function's parameters read themselves, and it is the basis of patterns you will see in React and in almost any modern code. In this lesson you will learn it in full, and you will apply it to splitting an ISO dueDate and to processing the key-value pairs of the Taller Nómada workload summary.

Contents

  1. What destructuring is
  2. The basic syntax
  3. Skipping elements with commas
  4. Default values
  5. The rest with ...
  6. Swapping variables without a helper
  7. Destructuring in a function's parameters
  8. Nested arrays
  9. Destructuring the result of split()
  10. Destructuring Object.entries()
  11. Destructuring in for...of
  12. The [error, value] pattern and its limits
  13. Common Mistakes and Tips
  14. Exercises
  15. Conclusion

  1. What destructuring is

Look at these two ways of pulling an array's elements out into variables:

const entry = ['Iván', 25, 3];

// By hand, with indexes
const assigneeA = entry[0];
const hoursA = entry[1];
const tasksA = entry[2];

// With destructuring
const [assignee, hours, tasks] = entry;

console.log(assignee, hours, tasks);   // Iván 25 3

The second version does exactly the same thing in one line. The rule is simple:

When you put a bracketed pattern on the left-hand side of an assignment, JavaScript does not create an array: it unpacks the array on the right, element by element, into the variables you have named, by position.

That is the key point: by position, not by name. You choose the variable names freely; what rules is the order.

const [a, b, c] = ['Iván', 25, 3];
console.log(a);   // 'Iván'   ← the name is irrelevant, the position counts

This is the essential difference from object destructuring, which you will study in the next lesson and which works the other way round: there the name rules and the order is irrelevant.

  1. The basic syntax

const tags = ['carpentry', 'purchasing', 'urgent'];

// As many variables as you want, in order
const [first, second, third] = tags;
console.log(first);      // 'carpentry'

// You can take fewer elements than there are: the rest is ignored
const [onlyTheFirst] = tags;
console.log(onlyTheFirst);   // 'carpentry'

// If you ask for more than there are, the extras are undefined
const [x, y, z, w] = tags;
console.log(w);          // undefined

It works the same with let, with const and with already-declared variables:

let assignee;
let hours;

[assignee, hours] = ['Lucía', 14];      // no declaration
console.log(assignee, hours);           // Lucía 14

Careful with that last form. If the previous line does not end in a semicolon, JavaScript may read the brackets as an index access (it is one of the ASI cases you saw in 01-04). When you destructure onto existing variables, start the line with ; or make sure the previous one is closed.

And it works with any iterable, not just arrays: strings, Set, Map

const [firstLetter, secondLetter] = 'Nómada';
console.log(firstLetter, secondLetter);   // N ó

const [one, two] = new Set([10, 20, 30]);
console.log(one, two);                    // 10 20

  1. Skipping elements with commas

If you only care about certain positions, leave the gap empty with a comma:

const row = ['Iván', 'high', 'in-progress', 12, '2026-09-30'];

// I want the assignee and the hours: I skip three positions
const [person, , , estimatedHours] = row;
console.log(person, estimatedHours);       // Iván 12

// Only the status (third position)
const [, , status] = row;
console.log(status);                       // 'in-progress'

Count the commas carefully: [, , status] has two commas before the name, so status picks up the third position. It is a compact syntax but easy to miscount; if you need to skip more than two positions, it is usually more readable to access by index or to rethink the data as an object.

  1. Default values

When a position does not exist (or is undefined), you can give a default value with =, exactly as in a function's parameters (03-03):

const [title, assignee = 'unassigned', reviewer = null] = ['Service the paper guillotine'];

console.log(title);      // 'Service the paper guillotine'
console.log(assignee);   // 'unassigned'   ← position 1 did not exist
console.log(reviewer);   // null

The critical detail, the same as with default parameters: the default value only applies with undefined, not with null, nor with 0, nor with ''.

const [a = 'default'] = [null];
console.log(a);        // null     ← null does NOT trigger the default

const [b = 'default'] = [undefined];
console.log(b);        // 'default'

const [c = 10] = [0];
console.log(c);        // 0        ← 0 does not trigger it either

It is the same distinction that separates ?? from || (01-06), and for the same reason. If you need null to trigger the default too, destructure first and apply ?? afterwards.

Default values can be expressions, and they are only evaluated if they are needed:

function todaysDate() {
  console.log('  (computing the date...)');
  return '2026-09-20';
}

const [title1, date1 = todaysDate()] = ['Task A', '2026-10-01'];
// prints nothing: position 1 existed

const [title2, date2 = todaysDate()] = ['Task B'];
//   (computing the date...)
console.log(date2);   // '2026-09-20'

  1. The rest with ...

The rest element collects everything left unassigned into a new array:

const queue = ['Signage', 'T-shirts', 'Tote bags', 'Reprint'];

const [next, ...waiting] = queue;
console.log(next);         // 'Signage'
console.log(waiting);      // [ 'T-shirts', 'Tote bags', 'Reprint' ]

// It also works for separating head from body
const [first, second, ...rest] = queue;
console.log(rest);         // [ 'Tote bags', 'Reprint' ]

Three rules of the rest element:

  1. It must come last. const [...all, last] = queue; is a SyntaxError.
  2. It always produces an array, even if nothing is left: then it will be [], never undefined.
  3. It takes no default value: [...rest = []] is not valid.
const [onlyOne, ...empty] = ['just one'];
console.log(empty);        // []   ← an empty array, not undefined

And a very useful application, copying without the first element, which is what a queue does:

function serve(queue) {
  const [served, ...pending] = queue;
  return { served, pending };          // it does not mutate the original queue
}

const result = serve(queue);
console.log(result.served);          // 'Signage'
console.log(result.pending.length);  // 3
console.log(queue.length);           // 4  ✓ untouched

Compare it with queue.shift(), which does the same thing but by mutating the array (04-03). This version is immutable, which is the project's design line.

  1. Swapping variables without a helper

Destructuring's most celebrated trick. You used to need a temporary variable:

let a = 'Iván';
let b = 'Marta';

// The old way
let temp = a;
a = b;
b = temp;

// With destructuring: one line, no helper
[a, b] = [b, a];
console.log(a, b);      // Iván Marta   (back where they started)

It works because the right-hand side is evaluated in full before assigning: the array ['Marta', 'Iván'] is built and only then is it distributed. It also works on an array's elements:

const order = ['T1', 'T2', 'T3'];
[order[0], order[2]] = [order[2], order[0]];
console.log(order);     // [ 'T3', 'T2', 'T1' ]

And for rotating three or more values at once:

let x = 1, y = 2, z = 3;
[x, y, z] = [z, x, y];
console.log(x, y, z);   // 3 1 2

  1. Destructuring in a function's parameters

If a function receives an array, you can unpack it in the signature itself. The parameter stops being called "that array" and starts naming its parts:

// Without destructuring: you have to remember what each index means
function describeRowA(row) {
  return `${row[0]}: ${row[1]} h`;
}

// Destructuring in the signature: it reads itself
function describeRow([person, hours]) {
  return `${person}: ${hours} h`;
}

console.log(describeRow(['Iván', 25]));    // 'Iván: 25 h'

It accepts default values and a rest element, just as outside:

function summarizeQueue([next = 'nothing', ...rest] = []) {
  return `Next: ${next}. Waiting: ${rest.length}`;
}

console.log(summarizeQueue(['Signage', 'T-shirts']));   // 'Next: Signage. Waiting: 1'
console.log(summarizeQueue([]));                        // 'Next: nothing. Waiting: 0'
console.log(summarizeQueue());                          // 'Next: nothing. Waiting: 0'

That trailing = [] matters: without it, calling summarizeQueue() with no arguments would throw a TypeError, because undefined cannot be destructured. It is the same protection you will see in 04-07 with the = {} trick.

Where it shows most is in the callbacks of the methods you studied in 04-05:

const workload = { 'Iván': 25, 'Marta': 6, 'Lucía': 14 };

// Without destructuring: pair[0] and pair[1] say nothing
Object.entries(workload).forEach((pair) => console.log(`${pair[0]}: ${pair[1]} h`));

// Destructuring in the callback's parameter: readable
Object.entries(workload).forEach(([person, hours]) => console.log(`${person}: ${hours} h`));
// Iván: 25 h
// Marta: 6 h
// Lucía: 14 h

  1. Nested arrays

The pattern on the left can reproduce any structure, as deep as you need:

const week = [
  ['Monday', [8, 6]],
  ['Tuesday', [7, 5]]
];

const [[day, [morning, afternoon]]] = week;
console.log(day, morning, afternoon);       // Monday 8 6

Read it from the outside in: the outer [[...]] says "take the first element of week"; inside, [day, [morning, afternoon]] says "that element is an array of two: the first is called day, and the second is itself an array of two, morning and afternoon".

It is powerful, but it becomes unreadable very quickly. Practical rule: more than two levels of nesting and the data should probably be an object. Compare:

// Hard to read
const [[, [morningHours]]] = week;

// The same data modeled as an object: no need to count brackets
const weekObj = [{ day: 'Monday', morning: 8, afternoon: 6 }];
console.log(weekObj[0].morning);      // 8

  1. Destructuring the result of split()

Here comes the direct application to the project. split(separator) breaks a string into an array, and that array is a perfect candidate for destructuring. The canonical case: splitting an ISO dueDate.

const dueDate = '2026-09-30';

const [year, month, day] = dueDate.split('-');
console.log(year, month, day);    // 2026 09 30
console.log(typeof year);         // 'string'  ← split always returns strings

Converting to numbers on the fly with map (04-04):

const [y, m, d] = dueDate.split('-').map(Number);
console.log(y, m, d);             // 2026 9 30
console.log(typeof y);            // 'number'

With this you can now write a presentation function Marta has been asking for since Module 1: showing the date in a readable format.

const MONTHS = ['January', 'February', 'March', 'April', 'May', 'June',
                'July', 'August', 'September', 'October', 'November', 'December'];

function readableDate(iso) {
  const [year, month, day] = iso.split('-');
  return `${Number(day)} ${MONTHS[Number(month) - 1]} ${year}`;
}

console.log(readableDate('2026-09-30'));   // '30 September 2026'
console.log(readableDate('2026-09-05'));   // '5 September 2026'
console.log(readableDate('2026-11-05'));   // '5 November 2026'

Notice MONTHS[Number(month) - 1]: the -1 is because months run from 1 to 12 and array indexes from 0 to 11. It is the classic off-by-one, and here it is isolated in a single easy-to-review line.

Another frequent use, splitting a line of text into fields:

const line = 'Iván;high;12;2026-09-30';
const [assignee, priority, hours, date] = line.split(';');
console.log(`${assignee} has ${hours} h at ${priority} priority`);
// Iván has 12 h at high priority

A warning: if the string does not have all the expected separators, the leftover variables end up undefined with no error at all. When you process data coming from outside, combine destructuring with default values and a validation.

const [r = 'unknown', p = 'medium', h = '0'] = 'Lucía'.split(';');
console.log(r, p, h);      // Lucía medium 0

  1. Destructuring Object.entries()

Object.entries (04-01) returns an array of [key, value] pairs. Destructuring them is what turns a cryptic walk into a readable one.

const workload = { 'Iván': 25, 'Marta': 6, 'Lucía': 14 };

// Without destructuring
for (const pair of Object.entries(workload)) {
  console.log(`${pair[0]} is carrying ${pair[1]} h`);
}

// Destructuring: names instead of indexes
for (const [person, hours] of Object.entries(workload)) {
  console.log(`${person} is carrying ${hours} h`);
}
// Iván is carrying 25 h
// Marta is carrying 6 h
// Lucía is carrying 14 h

Combined with the methods from 04-05, the result is directly the "workload per person" block of the weekly report:

Object.entries(workload)
  .toSorted(([, hoursA], [, hoursB]) => hoursB - hoursA)     // destructuring and skipping the key
  .forEach(([person, hours], i) => console.log(`${i + 1}. ${person}: ${hours} h`));
// 1. Iván: 25 h
// 2. Lucía: 14 h
// 3. Marta: 6 h

The comparator is worth dwelling on: ([, hoursA], [, hoursB]) => hoursB - hoursA. Both parameters are pairs, and in each one we skip the key with a comma so as to keep only the value. Compared with (a, b) => b[1] - a[1], the destructured version says what is being compared, not which position it sits in.

The way back exists too: Object.fromEntries rebuilds an object from pairs.

const workloadInWorkdays = Object.fromEntries(
  Object.entries(workload).map(([person, hours]) => [person, Number((hours / 8).toFixed(1))])
);
console.log(workloadInWorkdays);    // { Iván: 3.1, Marta: 0.8, Lucía: 1.8 }

  1. Destructuring in for...of

You have already seen it in lesson 04-04 with a footnote. Now you understand exactly what happens:

for (const [index, task] of backlog.entries()) {
  console.log(`${index + 1}. ${task.title}`);
}
// 1. Redesign the multipurpose room
// 2. Signage for the screen-printing workshop
// ...

backlog.entries() produces, on each pass, a two-element array: [0, {task 1}], [1, {task 2}]… The pattern const [index, task] unpacks that pair on the spot. And since it is still a for...of, it keeps break and continue, which was the reason for preferring it over forEach:

for (const [i, task] of backlog.entries()) {
  if (task.status === 'done') continue;
  if (i >= 3) break;
  console.log(`${i}: ${task.title}`);
}

It also works when walking arrays of arrays directly:

const assignments = [['Iván', 12], ['Marta', 6], ['Lucía', 14]];

for (const [person, hours] of assignments) {
  console.log(`${person} → ${hours} h`);
}

And with Map, which produces pairs naturally (04-05):

const counts = new Map([['Iván', 3], ['Marta', 2], ['Lucía', 1]]);
for (const [person, tasks] of counts) {
  console.log(`${person}: ${tasks} task(s)`);
}

  1. The [error, value] pattern and its limits

In 03-03 you studied how to return several values from a function. One of the options was returning an array, and destructuring now makes that option comfortable: the [error, value] pattern, popular in many libraries.

/**
 * Validates a task. Returns [error, task]:
 * if all is well, error is null; if it goes wrong, task is null.
 */
function validateTask(task) {
  if (typeof task.title !== 'string' || task.title.trim() === '') {
    return ['The title is required.', null];
  }
  if (!(task.estimatedHours > 0 && task.estimatedHours <= 40)) {
    return ['Hours must be between 0 and 40.', null];             // R3
  }
  return [null, task];
}

const [error, validTask] = validateTask({ title: 'Service the paper guillotine', estimatedHours: 2 });
if (error) {
  console.log(`✗ ${error}`);
} else {
  console.log(`✓ ${validTask.title}`);
}
// ✓ Service the paper guillotine

const [error2] = validateTask({ title: '', estimatedHours: 2 });
console.log(error2);       // 'The title is required.'

Its virtue is that the caller chooses the names, which avoids collisions when you make several calls in a row:

const [errorA, taskA] = validateTask(candidateA);
const [errorB, taskB] = validateTask(candidateB);

Now the limits, because it is worth knowing when not to use it:

Returning an array [error, value] Returning an object { error, value }
Names Set by the caller Set by the function (they can be renamed, 04-07)
Order Significant: getting it wrong is a silent bug Irrelevant
Taking only the second one const [, value] = ... (awkward comma) const { value } = ...
Adding a third piece of data Breaks anyone who was counting positions Breaks nothing
Self-documentation Low: you have to read the function High

The silent bug in the second row is real and very easy to commit:

const [task, error] = validateTask({ title: 'Something', estimatedHours: 2 });
// ✗ They are the wrong way round: task is null and error holds the task object.
// There is no warning at all. The program simply does the opposite of what you think.

Practical rule: use the two-element array when the two values are inseparable and obvious (a result and its error, a coordinate, a key-value pair), and whenever the caller will want to give them names of their own. For three or more pieces of data, or when the names matter, return an object, as summarizeWorkload() did in 03-03. It is the same recommendation as in that lesson, now with both syntaxes in hand for comparison.

Common Mistakes and Tips

1. Confusing position with name. In arrays the order rules. const [title, id] = [1, 'Redesign…'] assigns title = 1. With objects (04-07) the name rules.

2. Destructuring undefined or null.

const [a] = undefined;    // ✗ TypeError: undefined is not iterable
const [b] = null;         // ✗ TypeError
const [c] = [];           // ✓ c is undefined, no error

Protect yourself with = [] in the parameters or with ?? [] on the value.

3. Miscounting the commas when skipping elements. [, , status] takes the third position. When in doubt, count the commas: there are as many skipped positions as commas before the name.

4. Putting the rest in the middle. const [...all, last] = a; is a SyntaxError. The rest always comes last.

5. Expecting the default value to apply with null. It only applies with undefined. If null must trigger the default too, apply ?? afterwards.

6. Forgetting the semicolon on the previous line when destructuring onto already-declared variables: [a, b] = [b, a] can join the line above and produce a confusing error.

7. Destructuring a Set expecting a particular order. It works because it is iterable, but the order is insertion order; do not use it to "take the two smallest".

Professional tip. Destructure when it improves reading, not for sport. const [a, , , , , f] = row; is no better than row[5]. The sign that you are forcing it is having to count commas: if your data needs names, it should probably be an object, and the next lesson will give you the exact syntax for unpacking one.

Exercises

Exercise 1 — The date, in parts. Write a function breakDownDate(iso) that takes a dueDate in the format 'yyyy-mm-dd' and returns an object { year, month, day, readable }, where year, month and day are numbers and readable has the format '30 September 2026'. Then write sameMonth(isoA, isoB), returning true if two dates fall in the same year and month. Try them with '2026-09-30', '2026-09-05' and '2026-11-05'.

Exercise 2 — Workload podium. Starting from the object const workload = { 'Iván': 25, 'Marta': 6, 'Lucía': 14 };, write podium(workload) returning an object { first, second, rest }, where first and second are strings in the format 'Iván (25 h)' and rest is an array with the same format for the others. Use Object.entries, toSorted with destructuring in the comparator, and destructuring with a rest element.

Exercise 3 — The screen-printing workshop queue, immutably. Write three functions that do not mutate the input array:

  1. serveNext(queue) → returns [served, remainingQueue].
  2. moveToFront(queue, position) → returns a new queue with the order at that position placed first.
  3. swap(queue, i, j) → returns a new queue with two positions swapped, using destructuring.

Try them with ['Signage', 'T-shirts', 'Tote bags', 'Reprint'].

Solutions

Exercise 1

const MONTHS = ['January', 'February', 'March', 'April', 'May', 'June',
                'July', 'August', 'September', 'October', 'November', 'December'];

function breakDownDate(iso) {
  const [year, month, day] = iso.split('-').map(Number);
  return { year, month, day, readable: `${day} ${MONTHS[month - 1]} ${year}` };
}

function sameMonth(isoA, isoB) {
  const [yearA, monthA] = isoA.split('-');
  const [yearB, monthB] = isoB.split('-');
  return yearA === yearB && monthA === monthB;
}

console.log(breakDownDate('2026-09-30'));
// { year: 2026, month: 9, day: 30, readable: '30 September 2026' }

console.log(breakDownDate('2026-09-05').readable);   // '5 September 2026'

console.log(sameMonth('2026-09-30', '2026-09-05'));  // true
console.log(sameMonth('2026-09-30', '2026-11-05'));  // false

Two observations. In breakDownDate, the .map(Number) converts all three parts at once, and that is what makes day come out as 5 instead of '05' —exactly what we want for the readable text. In sameMonth, by contrast, we do not convert: comparing the strings '09' with '09' works perfectly and saves the conversion. Notice too the { year, month, day, ... }: writing year instead of year: year is the property shorthand, which the next lesson will explain along with the rest of the object syntax.

Exercise 2

function podium(workload) {
  const [first, second, ...rest] = Object.entries(workload)
    .toSorted(([, hoursA], [, hoursB]) => hoursB - hoursA)
    .map(([person, hours]) => `${person} (${hours} h)`);

  return { first, second, rest };
}

console.log(podium({ 'Iván': 25, 'Marta': 6, 'Lucía': 14 }));
// { first: 'Iván (25 h)', second: 'Lucía (14 h)', rest: [ 'Marta (6 h)' ] }

console.log(podium({ 'Iván': 25 }));
// { first: 'Iván (25 h)', second: undefined, rest: [] }

The chain does three things linked together from 04-05 —convert to pairs, sort and turn into text— and the destructuring on the first line distributes the result in a single gesture. The second console.log shows the behavior in the edge case: second ends up undefined and rest ends up [] (never undefined), exactly as the rule in section 5 said. If you wanted text instead of undefined, const [first, second = '—', ...rest] would be enough.

Exercise 3

function serveNext(queue) {
  const [served, ...remainingQueue] = queue;
  return [served, remainingQueue];
}

function moveToFront(queue, position) {
  if (position < 0 || position >= queue.length) return queue.slice();
  const copy = queue.slice();
  const [chosen] = copy.splice(position, 1);
  copy.unshift(chosen);
  return copy;
}

function swap(queue, i, j) {
  const copy = queue.slice();
  [copy[i], copy[j]] = [copy[j], copy[i]];
  return copy;
}

const queue = ['Signage', 'T-shirts', 'Tote bags', 'Reprint'];

const [served, remaining] = serveNext(queue);
console.log(served);                       // 'Signage'
console.log(remaining);                    // [ 'T-shirts', 'Tote bags', 'Reprint' ]

console.log(moveToFront(queue, 3));
// [ 'Reprint', 'Signage', 'T-shirts', 'Tote bags' ]

console.log(swap(queue, 0, 3));
// [ 'Reprint', 'T-shirts', 'Tote bags', 'Signage' ]

console.log(queue);
// [ 'Signage', 'T-shirts', 'Tote bags', 'Reprint' ]  ✓ untouched in all three

All three respect the project's rule: copy before modifying. serveNext does not even need to copy, because the rest element already creates a new array. moveToFront combines slice (copy), splice with destructuring (take it out and keep what was taken) and unshift (put it at the front), all three from 04-03. And swap is the trick from section 6 applied to an array's positions; without destructuring it would need a temporary variable.

Conclusion

Array destructuring is a small piece of syntax with a big impact on readability. You know that it unpacks by position, that you can skip elements with commas, give default values that only trigger with undefined, and collect the tail with the rest element ..., which always comes last and always produces an array. You know how to swap variables without a helper, how to destructure directly in a function's parameters —including the = [] that avoids the error when calling with no arguments— and how to unpack nested structures, with the warning that beyond two levels the data was probably asking to be an object.

And you have applied it where it really shows: splitting an ISO dueDate with split('-') to build readableDate('2026-09-30')'30 September 2026'; walking the workload-per-assignee summary with for (const [person, hours] of Object.entries(workload)); writing comparators that say what they compare, ([, hoursA], [, hoursB]) => hoursB - hoursA; and understanding at last, without a footnote, what for (const [i, task] of backlog.entries()) means. You have also seen the [error, value] pattern and, above all, its limits against returning an object: the order is significant, getting it wrong gives no warning at all, and adding a third piece of data breaks anyone who was counting positions.

That limit is precisely the doorway into the next lesson. Nómada Tasks data does not come in pairs or triples: it comes as tasks with nine named fields, and unpacking those by position makes no sense at all. In Object Destructuring, Spread and Rest you will learn the variant that goes by name, with renaming and default values, you will finally write the 03-03 options object straight into a function's signature, and you will discover the ... operator in its other role —spread— which lets you copy and combine objects and, with that, write the immutable update { ...task, status: 'done' } that underpins all state work in modern frameworks.

JavaScript Course: From Beginner to Advanced

Module 1: Introduction to JavaScript

Module 2: Control Structures

Module 3: Functions

Module 4: Objects and Arrays

Module 5: Advanced Objects and Functions

Module 6: The Document Object Model (DOM)

Module 7: Browser APIs and Advanced Topics

Module 8: Testing and Debugging

Module 9: Performance and Optimization

Module 10: JavaScript Frameworks and Libraries

Module 11: Final Project

© Copyright 2026. All rights reserved