Learn JS Series (#21) - Destructuring Parameters: Named Arguments the JS Way
Published on HivePostify by @scipio · Thu Sep 03 2026
Learn JS Series (#21) - Destructuring Parameters: Named Arguments the JS Way
What will I learn - You will learn array and object destructuring, and how to pull values out cleanly; - how to destructure function parameters to get readable "named arguments"; - how to combine destructuring with default values for optional settings; - how to rename destructured properties and provide fallbacks; - why an options object beats a long list of positional parameters.
Requirements - A working modern computer running macOS, Windows or Ubuntu; - An installed Node.js (20+) distribution, or just a modern browser console; - Episodes 1-20 read, especially objects, arrays, and default/rest/spread.
Difficulty - Beginner
Curriculum (of the Learn JS Series): - [Learn JS Series (#1) - What Is JavaScript, Why It Runs Everywhere, and How to Run It](https://hive.blog/hive-196387/@scipio/learn-js-series-1-what-is-javascript-why-it-runs-everywhere-and-how-to-run-it) - [Learn JS Series (#2) - Variables and Bindings](https://hive.blog/hive-196387/@scipio/learn-js-series-2-variables-and-bindings) - [Learn JS Series (#3) - The Primitive Types: number, string, boolean, null, undefined, symbol, bigint](https://hive.blog/hive-196387/@scipio/learn-js-series-3-the-primitive-types-number-string-boolean-null-undefined-symbol-bigint) - [Learn JS Series (#4) - Operators and Expressions: Arithmetic, Comparison, Logical, and Short-Circuiting](https://hive.blog/hive-196387/@scipio/learn-js-series-4-operators-and-expressions-arithmetic-comparison-logical-and-short-circuiting) - [Learn JS Series (#5) - Strings: Template Literals, Unicode, and the Methods You Actually Use](https://hive.blog/hive-196387/@scipio/learn-js-series-5-strings-template-literals-unicode-and-the-methods-you-actually-use) - [Learn JS Series (#6) - Numbers: IEEE 754, Why 0.1 + 0.2 Is Not 0.3, and How to Cope](https://hive.blog/hive-196387/@scipio/learn-js-series-6-numbers-ieee-754-why-01-02-is-not-03-and-how-to-cope) - [Learn JS Series (#7) - Control Flow: if/else, switch, and the Ternary Expression](https://hive.blog/hive-196387/@scipio/learn-js-series-7-control-flow-ifelse-switch-and-the-ternary-expression) - [Learn JS Series (#8) - Loops: for, while, for...of, for...in, and When to Use Which](https://hive.blog/hive-196387/@scipio/learn-js-series-8-loops-for-while-forof-forin-and-when-to-use-which) - [Learn JS Series (#9) - Functions: Declarations, Parameters, Return Values, and Hoisting](https://hive.blog/hive-196387/@scipio/learn-js-series-9-functions-declarations-parameters-return-values-and-hoisting) - [Learn JS Series (#10) - Scope and the Temporal Dead Zone: How JavaScript Finds Your Variables](https://hive.blog/hive-196387/@scipio/learn-js-series-10-scope-and-the-temporal-dead-zone-how-javascript-finds-your-variables) - [Learn JS Series (#11) - Arrays: The Workhorse Data Structure and Its Core Methods](https://hive.blog/hive-196387/@scipio/learn-js-series-11-arrays-the-workhorse-data-structure-and-its-core-methods) - [Learn JS Series (#12) - Objects: Key-Value Data, Dot vs Bracket Access, and Nesting](https://hive.blog/hive-196387/@scipio/learn-js-series-12-objects-key-value-data-dot-vs-bracket-access-and-nesting) - [Learn JS Series (#13) - Truthiness, Equality, and Coercion: == vs === Done Properly](https://hive.blog/hive-196387/@scipio/learn-js-series-13-truthiness-equality-and-coercion-vs-done-properly) - [Learn JS Series (#14) - Mini Project: A Command-Line Tip Calculator](https://hive.blog/hive-196387/@scipio/learn-js-series-14-mini-project-a-command-line-tip-calculator) - [Learn JS Series (#15) - First-Class Functions: Passing, Returning, and Storing Functions](https://hive.blog/hive-196387/@scipio/learn-js-series-15-first-class-functions-passing-returning-and-storing-functions) - [Learn JS Series (#16) - Arrow Functions vs function: Syntax, this, and When Each Wins](https://hive.blog/hive-196387/@scipio/learn-js-series-16-arrow-functions-vs-function-syntax-this-and-when-each-wins) - [Learn JS Series (#17) - Closures: The Single Most Important Idea in JavaScript](https://hive.blog/hive-196387/@scipio/learn-js-series-17-closures-the-single-most-important-idea-in-javascript) - [Learn JS Series (#18) - Higher-Order Functions: Functions That Take or Return Functions](https://hive.blog/hive-196387/@scipio/learn-js-series-18-higher-order-functions-functions-that-take-or-return-functions) - [Learn JS Series (#19) - Callbacks and the Callback Pattern (Before We Reach Promises)](https://hive.blog/hive-196387/@scipio/learn-js-series-19-callbacks-and-the-callback-pattern-before-we-reach-promises) - [Learn JS Series (#20) - Default, Rest, and Spread: Flexible Function Signatures](https://hive.blog/hive-196387/@scipio/learn-js-series-20-default-rest-and-spread-flexible-function-signatures) - [Learn JS Series (#21) - Destructuring Parameters: Named Arguments the JS Way](https://hive.blog/hive-196387/@scipio/learn-js-series-21-destructuring-parameters-named-arguments-the-js-way) (this post)
Learn JS Series (#21) - Destructuring Parameters: Named Arguments the JS Way
Solutions to Episode 20 Exercises
Exercise 1 - an average of any count:
js function average(...nums) { if (nums.length === 0) return 0; return nums.reduce((a, b) => a + b, 0) / nums.length; } console.log(average(2, 4, 6)); // 4 console.log(average()); // 0
The insight: the rest parameter gives a real array, so length and reduce are right there, and we guard the empty case to avoid dividing by zero (which would hand you back NaN, not an error).
Exercise 2 - merging objects:
js function merge(...objects) { return objects.reduce((acc, obj) => ({ ...acc, ...obj }), {}); } console.log(merge({ a: 1 }, { b: 2 }, { a: 9 })); // { a: 9, b: 2 }
The insight: spreading each object in turn, later keys override earlier ones, so a ends up 9. Notice the parentheses around ({ ...acc, ...obj }) -- without them the arrow body would be read as a block, not an object literal.
Exercise 3 - min and max via spread:
js const nums = [3, 1, 4, 1, 5, 9]; console.log(Math.min(...nums)); // 1 console.log(Math.max(...nums)); // 9
The insight: ... spreads the array into separate arguments for Math.min/Math.max; the same ... in a parameter list would instead gather arguments into an array. Same three dots, opposite direction -- which is exactly the symmetry we hammered on last episode.
Now let's put destructuring to work, because it is the tool that makes function calls dramatically more readable, and it pairs beautifully with the defaults you just learned.
Destructuring basics
Destructuring is a syntax for unpacking values out of arrays and objects into individual variables, in one clean line. We already saw glimpses of it (episodes 11 and 12 both leaned on it), but here it is properly, front and center. Array destructuring pulls values out by position:
js const point = [10, 20, 30]; const [x, y, z] = point; console.log(x, y, z); // 10 20 30
const [first, , third] = point; // skip the middle with an empty slot console.log(first, third); // 10 30
That empty slot in [first, , third] is not a typo -- it is a deliberate "skip this position" hole. Handy when a function or API hands you a tuple and you only care about some of its elements.
Object destructuring pulls properties out by name (order does not matter here, the names do):
js const user = { name: "scipio", level: 7, city: "amsterdam" }; const { name, level } = user; console.log(name, level); // "scipio" 7
The variable names must match the property names, because for objects it is the key that identifies what you want, not a position. This is already handy on its own, but its real power shows up the moment you move it into a function's parameter list. That is where destructuring stops being a convenience and starts being a genuine design tool.
Destructuring array parameters
You can destructure directly in a function's parameter list. For a function that receives an array (like a coordinate pair), this names the parts immediately, right there in the signature:
js function distanceFromOrigin([x, y]) { return Math.sqrt(x x + y y); } console.log(distanceFromOrigin([3, 4])); // 5
The parameter [x, y] says, in effect, "I expect an array, and I want its first two elements bound to x and y". No arr[0], no arr[1], no mental bookkeeping. This pairs especially well with methods that hand you pairs, like iterating over Object.entries, where each element is a [key, value] array:
js const scores = { math: 90, art: 78 }; Object.entries(scores).forEach(([subject, score]) => { console.log(${subject}: ${score}); }); // math: 90 // art: 78
Each [subject, score] destructures the pair right in the callback parameter -- no pair[0]/pair[1] noise cluttering the body. Having said that, array destructuring in parameters is a smaller win than the object version, simply because positional arrays carry the same "which slot means what?" burden that positional arguments do. So let's get to the pattern that genuinely changes how you write functions.
Destructuring object parameters: named arguments
Here is the pattern that transforms real code. When a function needs several inputs, especially optional ones, passing them positionally is fragile: the caller must remember the exact order, and skipping a middle one is awkward (you have to pass undefined as a placeholder, as we saw last episode). The fix is to accept a single options object and destructure it right in the parameter list. Now every argument is named at the call site:
js function createUser({ name, age, role }) { return ${name} (${age}), role: ${role}; } console.log(createUser({ name: "scipio", role: "admin", age: 30 })); // "scipio (30), role: admin"
Look carefully at that call: { name: "scipio", role: "admin", age: 30 }. The order is irrelevant -- I deliberately wrote role before age to prove the point -- and each value is labelled by the key sitting next to it. Compare that to the positional alternative, createUser("scipio", 30, "admin"), where a reader has no earthly idea what 30 or "admin" are supposed to mean without going and reading the function definition. Multiply that across a codebase and the difference is enormous.
The named arguments style buys you three concrete things. First, calls are self-documenting -- you read the call and you know what each value is for. Second, order stops mattering, so nobody ever swaps two same-typed arguments by accident (the classic createRect(width, height) versus createRect(height, width) bug simply cannot happen). Third, and this is the quiet hero, you can add a new option later without breaking a single existing call, because old calls just do not mention the new key. Positional signatures do not give you that for free -- add a fourth positional parameter and every call site has to reckon with it.
Defaults with destructured parameters
Destructuring combines with the defaults from episode 20 to make optional settings genuinely clean. You give each destructured property its own fallback, so callers only pass what they actually want to override:
js function connect({ host, port = 8080, secure = false } = {}) { return ${secure ? "https" : "http"}://${host}:${port}; } console.log(connect({ host: "example.com" })); // "http://example.com:8080" console.log(connect({ host: "example.com", secure: true }));// "https://example.com:8080"
There are actually two layers of defaulting at work here, and it is worth pulling them apart because newcomers often see only one. The inner defaults -- port = 8080 and secure = false -- kick in per property when that specific key is missing or undefined in the object you passed. The outer = {} at the very end is a different thing entirely: it defaults the whole parameter to an empty object when you call connect() with no argument at all.
That outer = {} is not optional politeness -- it is load-bearing. Without it, calling connect() throws a TypeError, because JavaScript would try to destructure host, port, and secure out of undefined, and you cannot read properties off undefined. With the = {} guard, a bare connect() first substitutes an empty object, then the inner defaults fill in port and secure, and only host comes back undefined. Remember this one -- forgetting the = {} on a destructured options parameter is one of the most common little crashes you will write in your first month, and now you will recognise it instantly:
js function badConnect({ host, port = 8080 }) { // no = {} guard return ${host}:${port}; } // badConnect(); // TypeError: Cannot destructure property 'host' of 'undefined' console.log(badConnect({ host: "example.com" })); // "example.com:8080" (fine WITH an arg)
So the rule of thumb is simple and mechanical: any time you destructure an object in a parameter list and you want the function to be callable with no arguments, end the pattern with = {}. It costs four characters and saves a crash.
Renaming and nested destructuring
Two more tricks you will reach for constantly. First, you can rename a property as you destructure it, using the originalName: newName form. This is invaluable when the source property name is cryptic, or when it would clash with a variable you already have in scope:
js const response = { d: "2026-08-13", v: 42 }; const { d: date, v: value } = response; console.log(date, value); // "2026-08-13" 42
Read d: date as "take the property d, and bind it to a local variable called date". Nota bene: this trips people up because it looks like the object-literal syntax for the opposite direction. In an object literal { date: d } means "make a key date from variable d"; in a destructuring pattern { d: date } means "read key d into variable date". Same colon, mirror-image meaning depending on which side of the = you are on -- keep that straight and renaming becomes second nature.
Second, you can destructure nested structures in one go, reaching down into inner objects:
js const order = { id: 1, customer: { name: "scipio", address: { city: "amsterdam" } }, }; const { customer: { name, address: { city } } } = order; console.log(name, city); // "scipio" "amsterdam"
Notice something subtle in that pattern: customer: { ... } does NOT create a customer variable. The customer: part is purely a path -- it tells JavaScript where to dig, and only the innermost names (name, city) actually become variables. If you also wanted the customer object itself, you would have to list it separately. And a word of caution born from experience: nested destructuring gets hard to read fast if you go too deep. One or two levels is crisp and clear; five levels is a puzzle nobody wants to decode at 2am. Use it for pulling a couple of nested values, not as a party trick.
Tags: #programming