eduKatePunggol · Practical learning guide
Find your next learning step
Choose a route through JavaScript Destructuring to understand the mechanism, check a worked example and plan the next practice.
Full chapter index · Practice and parent questions · How Studying Works
Your child may understand the information inside a JavaScript object yet hesitate when a solution extracts several values in one line. Braces suddenly appear on the left of an equals sign. A colon seems to rename something, and a default does not behave as expected. These are understandable sticking points. The most useful first move is to draw the source data, identify the value being requested, and name the variable that should receive it.
JavaScript destructuring lets a programme extract values into assignment targets using a pattern. Object patterns select properties by key; array patterns take values from an iterable in sequence. This guide explains those two mechanisms, then examines renaming, defaults, nested data, rest elements and practical validation. The Punggol tuition setting offers a calm family learning approach. The examples are fictional learning projects, without claiming that these programming topics belong to a particular school syllabus or an advertised course.
The aim is to read and explain a pattern before writing a more complicated one. A student who can predict what happens for missing data, null and a nested object has a stronger foundation than someone who can copy a compact line but cannot describe its conditions. Use the routes to start with the nearest difficulty, and keep the examples small enough to inspect without guessing.
Choose a chapter
Read the pattern · 1–5
Handle absence and defaults · 6–10
Understand rest and references · 11–15
Build and validate · 16–20
Before learning the punctuation, identify the shape of the source. An object connects property keys to values. An array provides values in an order. These are different relationships, so their destructuring patterns answer different questions. Object destructuring asks for a named property; array destructuring asks for the next value supplied by iteration.
Imagine a fictional task object with a title and a duration. The word title names a property in the source. The text stored at that property is its value. A variable receiving that text may also be called title, but the source key and local variable are separate roles even when their spelling is identical. Keeping those roles separate makes renaming much easier later.
An array of two measurements tells a different story. The first and second positions have meaning because of a convention chosen by the programme. If the convention is width followed by height, the receiving names should reflect that order. The names themselves do not persuade JavaScript to find matching values elsewhere in the array. Reversing the input reverses which value each target receives.
Ask the student to sketch the source on paper. For an object, draw keys beside values. For an array, draw ordered slots. Then ask which value the next calculation needs. This little preparation avoids learning object and array patterns as interchangeable decorative syntax.
A good initial explanation sounds like this: “I want the duration property from this object and will store its value in a local variable.” Or: “I want the first two values yielded by this sequence.” Those sentences name both the source relationship and the destination. They provide a reliable basis for checking every example that follows.
Start with one object and two straightforward properties. The declaration creates two local bindings and obtains their values from the named properties of the source object.
const task = { title: "Read a chapter", minutes: 15 };
const { title, minutes } = task;
console.log(title, minutes);
The output contains Read a chapter and fifteen. The braces on the left form a destructuring pattern; they do not create a second ordinary object containing a copy of the source. The right-hand side supplies the source value. The property names in the pattern tell JavaScript which values to obtain.
The short form title is shorthand for selecting the title property and binding its value to a local variable also named title. This repeated spelling is convenient, but it hides two jobs. Say them separately once. The programme looks up a property in task, then assigns that value to a newly declared binding.
Changing the order of these property requests does not make title receive minutes. Object patterns select by property key, rather than by the order in which properties were written in the object literal. That is a useful contrast with the array pattern introduced next. Use a two-property example and ask the learner to predict the result after reversing the pattern's order.
The source object remains present after destructuring. Extracting title does not remove task.title. If the programme later changes task.title, the already-extracted primitive string does not automatically update. Destructuring happens at a particular point in execution; it is not a live link that repeats every property lookup forever. This timing becomes especially important when a learner works with interface state or other changing data.
An array pattern uses square brackets. In this simple example, the first yielded value goes to width and the second goes to height. The variable names communicate our interpretation; the order determines the assignments.
const dimensions = [240, 120];
const [width, height] = dimensions;
console.log(width, height);
Width becomes two hundred and forty, and height becomes one hundred and twenty. If dimensions were reversed, width would receive one hundred and twenty. JavaScript does not know that a particular number is geometrically a width. The programme relies on the agreed ordering of the data.
For a first trace, draw two source slots and two target slots. Connect the first source value to the first target and the second to the second target. Then draw an object pattern beside it, where each arrow begins at a named property. Comparing the drawings clarifies why the two forms use different selection rules.
The array pattern works through iteration, not simply through an assumption that the source must be a literal array. That wider mechanism will matter for strings, sets and generators. For now, use an ordinary array so the student can focus on the order without extra unfamiliar concepts.
If the source produces fewer values than the pattern requests, an unfilled target receives undefined unless a default applies. If it produces extra values, a pattern with only two ordinary targets does not store all the extras in a hidden variable. To collect remaining values, use an explicit rest element. The pattern states the destinations; it should not be treated as a guarantee that the input contained exactly the intended number of values.
Renaming is often the first confusing object pattern. A colon separates the source property key from the local assignment target. The name before the colon identifies the property to read; the name after it identifies the variable to create or assign.
const task = { title: "Check the graph", minutes: 20 };
const { title: taskTitle, minutes: durationMinutes } = task;
console.log(taskTitle, durationMinutes);
This declaration creates taskTitle and durationMinutes. It does not create a local title variable merely because title appears before the colon. The source object still has properties called title and minutes. Their keys have not been renamed inside the object.
Read the first part as “take title and call the receiving variable taskTitle”. Avoid describing the colon as a directionless equals sign. It has a specific role within this object pattern, and that role differs from the way a colon appears in an object literal used to create data. The surrounding syntax determines the meaning.
Renaming helps when two sources use the same key. A student profile and a project record might both have a name property. Local names such as studentName and projectName preserve their roles in the calculation. Renaming also helps when a source key is not suitable as a JavaScript identifier, since a quoted property key can be mapped to a valid local name.
Practise by asking the student which names exist after the declaration. They should identify taskTitle and durationMinutes, then explain where each value came from. If they list title as well, return to the source-and-target drawing. A clear distinction here prevents later nested patterns from becoming a collection of punctuation that the learner can only imitate.
Destructuring can appear in a declaration, which creates bindings, or in an assignment, which uses existing targets. A const declaration introduces bindings that cannot later be reassigned. A let declaration introduces bindings that can be reassigned. The pattern extracts values in either case, but the declaration keyword establishes the binding rule.
let title;
let minutes;
({ title, minutes } = { title: "Revise", minutes: 10 });
console.log(title, minutes);
The surrounding parentheses are important for this object assignment used as a statement. Without them, the opening brace can be parsed as the start of a block rather than the intended object destructuring assignment. A declaration such as const { title } = task does not require those enclosing parentheses because the declaration provides the relevant grammatical context.
Students sometimes repair a syntax error by adding punctuation at random. Instead, explain what the parser needs to recognise. We already have title and minutes bindings, and the statement should assign through an object pattern. The parentheses make the expression interpretation explicit in this context.
Array destructuring assignments do not have this same opening-brace ambiguity. Nevertheless, the student should still distinguish declaring new names from assigning existing ones. Repeating const with an already-declared name in the same scope causes a declaration problem; assigning a new value to a const binding causes a reassignment problem. These are separate from whether the extraction pattern is sensible.
For practice, provide one declaration and one later update using let. Ask which step creates the names and which step replaces their values. Then change let to const and discuss why the later reassignment is no longer allowed. This teaches a real relationship between binding rules and destructuring without implying that destructuring bypasses ordinary JavaScript scope.
A destructuring default supplies a value when the obtained value is undefined. This includes a missing property that yields undefined. It does not apply simply because a value is falsy, and it does not automatically replace null.
const { minutes = 15 } = {};
const { minutes: zeroMinutes = 15 } = { minutes: 0 };
const { minutes: nullMinutes = 15 } = { minutes: null };
console.log(minutes, zeroMinutes, nullMinutes);
The resulting values are fifteen, zero and null. Zero remains zero because it is a defined value. Null remains null for the same defaulting rule. A student who expects fifteen in the last two cases may be thinking of a broader “use the fallback for anything empty-looking” policy that the pattern does not implement.
This behaviour is useful when zero is meaningful. A timer setting of zero might mean no timed practice; a false setting might intentionally disable reminders. Replacing those values merely because they are falsy would erase the user's choice. The programme should adopt a fallback policy that matches its data contract.
If both null and undefined should trigger a fallback, a separate nullish-coalescing expression may be appropriate after extraction, or the source can be normalised before destructuring. That is a design decision. Do not assume that an equals sign inside the pattern already performs nullish coalescing.
Make a four-row prediction table with a missing property, undefined, null and zero. Ask the student to fill in the resulting value under a default of fifteen. The first two use fifteen; the last two preserve their supplied values. This compact exercise exposes the defaulting rule more clearly than a large application where several other conditions influence the displayed result.
A default can be an expression, including a function call. The expression is evaluated when the corresponding obtained value is undefined. It is not evaluated merely because the destructuring declaration exists. This timing matters when the expression performs work or produces a different result each time it runs.
let fallbackCalls = 0;
function fallbackTitle() {
fallbackCalls += 1;
return "Untitled";
}
const { title: firstTitle = fallbackTitle() } = { title: "Plan" };
const { title: secondTitle = fallbackTitle() } = {};
console.log(firstTitle, secondTitle, fallbackCalls);
The first extraction uses Plan and does not call the fallback function. The second uses Untitled and calls it once. The final counter is one. This experiment makes the evaluation rule observable without relying on a complicated performance claim.
Default expressions can refer to previously established bindings in the same pattern, but relying on a later binding can encounter its temporal dead zone. For beginner work, keep defaults simple and avoid chains whose evaluation order is difficult to explain. A separate calculation line may make the relationship clearer than a clever pattern.
The practical question is whether the fallback is a harmless value or an operation with consequences. A default that sends a request, increments a counter or mutates shared state is more surprising than a default string. Destructuring does not forbid such expressions, but a clear programme should make meaningful side effects easy to notice.
Ask the student to predict both the extracted values and the number of fallback calls. Then add a source value of null. The fallback still does not run for that value. This reinforces the undefined-specific condition and shows why “the default function exists” is not the same as “the default function runs on every extraction”.
CHAPTER 8 OF 24 · Handle absence and defaults
8. Nested patterns follow nested shapes
A nested object pattern reaches into a nested value. It works when the structure at that point supports the requested property extraction. Draw the outer and inner objects separately before writing the pattern.
const student = {
profile: { displayName: "Ari", level: "Secondary" }
};
const { profile: { displayName, level } } = student;
console.log(displayName, level);
The local bindings are displayName and level. This pattern does not also create a local profile binding. Profile names the outer source property whose value becomes the source for the inner pattern. If the programme needs the whole profile object as well, extract it explicitly or use a separate line.
The nested syntax can be read from the outside inward: obtain profile from student, then obtain displayName and level from that profile value. A student's explanation should name the intermediate value even when the code does not store it in a separate variable. That makes the required shape visible.
If profile is missing, the inner pattern receives undefined and the extraction throws rather than quietly creating both inner bindings as undefined. The programme is trying to destructure an absent nested object. A default for an inner property does not by itself repair a missing outer object. Those are different levels of the structure.
Try three inputs: a complete profile, an empty profile object, and no profile property. The complete profile supplies both values. The empty profile allows the inner property reads but yields undefined for each missing property. The absent profile causes the nested extraction to fail without an appropriate outer default or another policy. Comparing these cases helps a learner see why data shape matters more than the visual complexity of the pattern.
CHAPTER 9 OF 24 · Handle absence and defaults
9. Put a default at the level that can be absent
When an optional nested object may be missing, a default can supply an object for that level. Inner defaults can then supply values for missing properties of that object. These are two separate fallback decisions.
const input = {};
const {
profile: {
displayName = "Student",
reminders = false
} = {}
} = input;
console.log(displayName, reminders);
The outer profile value is undefined, so the empty-object default provides the source for the inner pattern. The inner missing properties then use Student and false. If input.profile were an empty object, the outer default would not be needed, but the inner defaults would still apply.
If input.profile is null, the outer default does not apply because null is defined. The inner extraction attempts to destructure null and fails. This is one of the most important conditions to explain. A nested equals-empty-object pattern is not a general shield against every malformed value.
The programme must decide whether null represents an allowed absence or an invalid record. If null is allowed as absence, normalise it deliberately before the inner extraction. If it is invalid, validate it and give a clear error. Either policy can be sensible, but hiding the distinction behind a memorised nested pattern leads to unreliable behaviour.
For practice, write an expectation table before coding: missing profile, undefined profile, empty object, null profile and a complete profile. Explain which default is used in each successful case and which case requires a different policy. This exercise teaches the location of a fallback, not merely its value. A learner who understands the levels can later work with more complex data without assuming that one default at the end protects the entire structure.
Extracting a property does not prove that it has the correct type, unit or acceptable range. A minutes property could contain the number fifteen, the string fifteen, a negative number or an object. Destructuring can obtain those values; it does not decide which ones the learning project should accept.
Suppose a timer expects a finite non-negative number of minutes. The programme should check that contract after extraction or during a dedicated input-normalisation step. A default may handle a missing value, but it does not convert every supplied value into valid data. A negative supplied number remains negative unless another operation rejects or changes it.
This distinction is useful beyond coding. Reading an answer from a worksheet is not the same as verifying it. A programme that prints a neat result may still be carrying the wrong type or unit through its calculations. Students should learn to ask what each step establishes and what remains unchecked.
A simple validation routine might require typeof minutes to be number, Number.isFinite(minutes) to be true, and minutes to be at least zero. The exact rule should come from the project requirements. If fractional minutes are allowed, do not accidentally demand integers. If a string input is expected from a form, parse and validate it under an explicit policy rather than pretending the destructuring pattern converted it.
Ask the student to provide one value that passes extraction but should fail validation. That transfer question reveals whether the learner has separated syntax from data quality. Then ask for one missing value that appropriately uses a default. The contrast helps them see two distinct jobs: choosing a fallback for absence and checking the acceptability of data that is actually present.
An array rest element collects the remaining yielded values into a new array. It must appear at the end of the pattern. This is useful when the first value has a special role and the rest form a collection.
const tasks = ["read", "plan", "draft", "check"];
const [firstTask, ...laterTasks] = tasks;
console.log(firstTask, laterTasks);
FirstTask receives read. LaterTasks becomes an array containing plan, draft and check. The source tasks array remains present and is not emptied by this extraction. The rest array is a separate container, but any object values inside it would still be references to those same objects unless copied through another deliberate process.
Rest uses three dots inside the receiving pattern. Spread also uses three dots in other contexts, such as constructing a new array from iterable values. The punctuation looks similar, but the direction of the operation differs. Rest gathers remaining values into a target; spread supplies values outward into another construction or call.
If the source contains only one task, the rest array is empty. If it contains no tasks, firstTask is undefined and the rest array is still empty. These boundary cases are predictable from the rule, and they are worth testing once before relying on the pattern in a larger report.
Do not place an ordinary target after the rest element to request a special final value. The rest element must be last, so a task such as separating the first and last items needs a different design. For learning, ask the student to explain why the result is an array even when it contains only one remaining value. The operation gathers a sequence of remaining values; it does not change its container type according to the current length.
An empty position in an array pattern skips a yielded value. This can be useful when a small fixed-format result contains a field the programme does not need. The comma is part of the pattern's ordering, so count positions carefully.
const reading = ["sensor-A", "C", 28];
const [sensorName, , temperature] = reading;
console.log(sensorName, temperature);
The middle value is skipped, and temperature receives the third value. The skip does not remove the middle item from the source array. It simply leaves no ordinary binding target for that visit. If the source is an iterator, the skipped value is still consumed while reaching the later value.
This example also reveals the fragility of unexplained positional formats. The programme assumes that the first field is a sensor name, the second is a unit and the third is a temperature. If another producer changes that ordering, a syntactically valid pattern can obtain the wrong information. An object with named properties may be clearer when the data format is under your control.
Skipping the unit is especially questionable if the programme then interprets the temperature without checking whether it is Celsius or another unit. The syntax allows the skip, but the project may still need that information. Good extraction is driven by the eventual calculation, not by a desire to leave out as many variables as possible.
For practice, provide a three-field format and ask the student to write its contract in plain language before destructuring it. Then change the order of two fields and ask what breaks. This demonstrates why a pattern should reflect a known format. It also helps a learner recognise when a named object is a better representation than a compact positional array.
CHAPTER 13 OF 24 · Understand rest and references
13. Object rest collects selected remaining properties
An object rest property creates a new object containing remaining own enumerable properties that were not selected earlier in the pattern. It is useful for separating a known field from other plain-data fields, but it should not be described as a complete copy of every feature of the source.
const task = { title: "Read", minutes: 15, subject: "English" };
const { title, ...details } = task;
console.log(title, details);
Details contains minutes and subject in this plain object example. It does not contain title, because title was already selected. The source still contains all three properties. Object rest creates a new receiving object; it does not delete selected properties from the original.
The qualifiers own and enumerable matter when an object has inherited properties or special property definitions. A beginner can first understand the plain-data example, then remember that object rest is not a promise to preserve a prototype, non-enumerable properties or every descriptor. For such specialised copying requirements, choose an appropriate method deliberately.
Renaming a selected property does not change which source key is excluded from rest. If title is received into taskTitle, the source key title is still the selected property. Details does not suddenly gain a title entry merely because the local binding has a different name.
This is a useful prediction exercise. Ask the learner to list the keys of the rest object before running the code, then repeat with a renamed title binding. They should obtain the same remaining keys in both cases. Finally, ask whether changing a nested object inside details would affect the source. The next chapter explains why a new outer object does not imply that every nested value was deeply copied.
CHAPTER 14 OF 24 · Understand rest and references
14. Extraction and rest do not create a deep clone
When destructuring obtains an object value, the receiving binding refers to that object. It does not recursively copy every nested object. Object rest creates a new outer object for its collected properties, but nested reference values remain shared unless another operation copies them appropriately.
const source = { settings: { reminders: false }, title: "Plan" };
const { settings } = source;
settings.reminders = true;
console.log(source.settings.reminders);
The final value is true. Both settings and source.settings refer to the same nested object. The const declaration prevents reassignment of the settings binding; it does not make the referenced object's properties immutable. These are two separate concepts that often become tangled for beginners.
If the programme instead extracts a primitive string and later changes the source property to another string, the already-extracted variable keeps its earlier primitive value. This is not a contradiction. A primitive value is different from a shared reference to a mutable object. Draw two arrows to one nested object to make the reference case visible.
Do not recommend a generic deep-copy trick without considering the data. Different copying methods have different rules for functions, prototypes, cycles and supported value types. In a small learning project, a deliberate shallow copy of the particular nested plain object may be enough. The key is to state which layer becomes independent and which values remain shared.
A good check is to ask the student what can still change after a const destructuring declaration. They should distinguish rebinding the local name from mutating the object it references. Then ask them to propose an example where the outer object is new but the nested settings object is shared. That explanation shows an understanding of the data relationship rather than an assumption that braces always mean “copy everything”.
CHAPTER 15 OF 24 · Understand rest and references
15. Array patterns consume iterables
Array destructuring works with iterable values. Arrays are a familiar example, but a generator can also supply values one at a time. A pattern requests the values needed for its targets, and a rest element requests the remaining values until iteration finishes.
function* numbers() {
yield 10;
yield 20;
yield 30;
}
const iterator = numbers();
const [first, second] = iterator;
console.log(first, second);
console.log(iterator.next());
For this generator, first and second receive ten and twenty. Destructuring that stops early closes the generator through the iterator protocol, so the later next call reports completion rather than yielding thirty. This is a useful example to test rather than guess from the visual resemblance to array indexing.
The general iterator protocol can involve resource cleanup through an iterator's return method. A generator illustrates that behaviour clearly. Do not assume that every iterable has identical internal storage or can be restarted just because the first example used an array. To obtain a fresh generator run, call the generator function again.
A rest element on a source that never finishes would attempt to collect endlessly. That makes collecting “all the rest” inappropriate for an unbounded source. In a beginner lesson, use finite inputs and state the condition plainly: the source must finish if we expect a finite rest array.
The transfer question is simple: does an array-shaped pattern prove that the source is an array? No. It expresses an iterable extraction pattern. The receiving rest value is an array, but the source can be another iterable. This distinction helps learners read code involving sets, strings and streams without treating square brackets as a guarantee about the source's concrete type.
Object destructuring cannot extract from null or undefined as a source. A default for an individual property does not help when the whole source is absent. The programme must first decide whether absence is allowed and what it should mean.
const input = null;
const { title = "Untitled" } = input ?? {};
console.log(title);
This example treats a nullish source as an empty object, then applies a property default. That can be suitable for optional display settings. It may be unsuitable for a required student record, where an absent source should produce a clear error rather than a plausible-looking fallback report.
Using logical OR instead of nullish coalescing would also replace other falsy source values, such as zero, false and an empty string. Whether that is appropriate depends on the contract. A broad fallback can hide a wrong input type. A programme should not silently turn a malformed required record into a default object simply because it wants to avoid an exception.
The existing JavaScript optional chaining lesson explores a different job: accessing a path while handling nullish values at selected points. Destructuring binds extracted values to targets. Both tools can appear in the same programme, but neither replaces a clear policy for required data.
Ask the student to choose between optional settings and a required record. For optional settings, they might defend the empty-object fallback. For a required record, they should explain how to detect absence and report it. The important skill is reasoning about meaning before selecting the shortest expression. A programme that never throws is not automatically a programme that handles its data correctly.
CHAPTER 17 OF 24 · Build and validate
17. Function parameters make the contract visible
A function can destructure a parameter to extract the fields it uses. This can make a plain options object readable, especially when several settings have sensible defaults. An outer parameter default handles an omitted argument; inner property defaults handle missing fields.
function describeTask({ title = "Practice", minutes = 15 } = {}) {
return `${title}: ${minutes} minutes`;
}
console.log(describeTask());
console.log(describeTask({ title: "Reading", minutes: 0 }));
The first call uses the outer empty-object default and then the property defaults. The second preserves zero minutes. Calling the function with null does not use the outer parameter default, because parameter defaults also apply to undefined rather than null. The destructuring would fail for that null source unless the function has a different normalisation design.
Notice that the function's signature communicates which fields it uses, but it does not validate their types. A title object or a negative duration can still reach the template expression unless another rule checks them. The signature is a useful map, not a complete validation system.
For a beginner, a function accepting one clearly named object is often easier to extend than a long list of positional arguments. Nevertheless, a densely nested parameter pattern can become difficult to read and debug. If validation is substantial, accepting the input first and extracting values inside the body may give clearer error messages and intermediate steps.
Try three calls: no argument, an empty object and an object with one explicit field. Ask which default operates in each case. Then discuss null separately. This exercise reinforces the levels of defaulting and gives the learner a practical method for reading unfamiliar function signatures.
CHAPTER 18 OF 24 · Build and validate
18. Extract loop records without losing context
Destructuring can appear in the target of a for-of loop, extracting selected fields from each record. This is useful when every iteration receives a similarly shaped plain object. The programme should still retain any identity needed for a meaningful report.
const tasks = [
{ id: "t1", title: "Read", minutes: 10 },
{ id: "t2", title: "Check", minutes: 5 }
];
for (const { id, title, minutes } of tasks) {
console.log(id, title, minutes);
}
Each iteration creates bindings for that iteration's record. The pattern selects properties by key, so the order in which those properties were written in an individual record does not determine their destinations. Missing properties still yield undefined unless defaults apply.
Including id is intentional. A title may change or be repeated, while a stable record identifier can support a later update or diagnostic. Extraction should not discard context that the programme will need. A student who keeps only a neat display string may find it difficult to reconnect an error message to the original record.
If the records come from outside the programme, validate them under an explicit rule. Destructuring in the loop does not make every record well formed. One null record can cause the extraction to fail before the body runs. A separate validation step or a loop that first names the record may be clearer when malformed inputs need individual messages.
For practice, add a record with a missing duration and choose a policy. Then add a null record and explain why it is a different shape problem. The learner should recognise that defaulting a missing property and accepting an absent record are not interchangeable decisions. This distinction supports more trustworthy list-processing code.
A modest project can bring extraction, defaults and validation together. The following function accepts a plain task-like record, supplies a missing duration, checks the values it will use, and returns a new plain object for the later display step.
function normaliseTask(input) {
if (input === null || typeof input !== "object" || Array.isArray(input)) {
throw new TypeError("Task must be an object");
}
const { title, minutes = 15 } = input;
if (typeof title !== "string" || title.trim() === "") {
throw new TypeError("Task title must be a non-empty string");
}
if (typeof minutes !== "number" || !Number.isFinite(minutes) || minutes < 0) {
throw new TypeError("Minutes must be a finite non-negative number");
}
return { title: title.trim(), minutes };
}
console.log(normaliseTask({ title: " Reading ", minutes: 0 }));
The result contains Reading without the surrounding spaces and preserves zero minutes. A missing duration uses fifteen. A null duration fails the numeric validation instead of being silently replaced by the destructuring default. Each outcome follows a visible policy.
This function is intended for a small plain-data exercise. It is not a complete security boundary for arbitrary JavaScript objects with getters, prototypes or proxies. The project contract should state where its inputs come from. For ordinary parsed task data, the narrow checks demonstrate how to separate extraction from acceptance.
The returned object contains only the fields the display needs. That is a deliberate construction, not an automatic rest copy of every incoming field. Ask the student why this may be easier to reason about than returning the original object. The output shape is explicit, and subsequent code can work with a smaller, checked contract.
Finally, ask which line actually uses destructuring. The validation before and after it serves other purposes. Naming each step's responsibility prevents the student from crediting a concise syntax feature with all the correctness of the surrounding function.
Tests should record the intended contract, including which inputs are rejected. For the task normaliser, a valid title with no duration should use fifteen; a duration of zero should remain zero; an empty title and a negative duration should fail. These are meaningful boundaries because they distinguish defaulting from validation.
const result = normaliseTask({ title: " Read " });
console.assert(result.title === "Read" && result.minutes === 15);
console.assert(normaliseTask({ title: "Read", minutes: 0 }).minutes === 0);
function expectTypeError(value) {
try {
normaliseTask(value);
} catch (error) {
if (error instanceof TypeError) return;
throw error;
}
throw new Error("Expected TypeError");
}
expectTypeError(null);
expectTypeError({ title: "", minutes: 5 });
expectTypeError({ title: "Read", minutes: -1 });
expectTypeError({ title: "Read", minutes: null });
Predict the result before running each case. The final rejected input is especially useful: null is a supplied duration, so the default does not apply, and the number check rejects it. That explanation connects two distinct rules rather than treating the error as an unexplained inconvenience.
For learning, console assertions can help inspect simple expectations, though a full test runner is more suitable for a maintained application. The helper above deliberately throws if an expected error never occurs. Otherwise, a test that merely catches errors could pass even when the function accepted everything.
Add one test of your own that targets a stated requirement. For example, Infinity should fail the finite-number rule. Explain why your test belongs before changing the programme. A useful test expresses a requirement that could be violated; it is not just another copy of a happy-path example.
After the tests, review the policy itself. Is fifteen the right default for this fictional project? Are fractional minutes allowed? Tests can confirm that the programme follows its chosen rules, but they cannot choose those rules on behalf of the people using it.
When a pattern fails, first identify whether the problem is syntax, source shape or an unexpected value. A missing parenthesis around an object assignment is a syntax issue. A nested extraction from null is a shape issue. A negative duration extracted successfully is a validation issue. These categories suggest different next actions.
If a renamed variable seems missing, inspect which name appears after the colon. That is the receiving target. If a default fails to replace null, recall the undefined-specific rule. If a nested object is unexpectedly modified, inspect whether two bindings refer to the same object. None of these problems is solved reliably by rearranging braces until the error disappears.
A useful debugging technique is to expand a complex pattern into several simple steps. Store the outer property in a clearly named variable, inspect it, then extract the inner properties. The expanded version can reveal exactly which value is absent or malformed. Once the logic is clear, decide whether combining the steps again improves readability.
Also check scope. A variable declared inside a function or block may not exist where the student later tries to use it. Destructuring follows ordinary binding and scope rules. It does not create global variables simply because several names are introduced together.
Keep a correction note containing the failed assumption and the correct rule. “The key is before the colon; the local target is after it” is useful. “Added another bracket” is not enough. The first note supports the next unfamiliar example, while the second only remembers a specific edit. A student learns more from explaining one repaired relationship than from collecting a long list of corrected code without reasons.
Try these questions without reopening the worked examples. An object has a points property containing zero. What does a destructuring default of ten produce? The answer is zero, because the supplied value is defined. What if points is missing? The default produces ten. What if points is null? It remains null. Explain the condition before writing code.
Next, extract the title property into a variable called taskTitle. State which local names are created and whether the source key changes. Only taskTitle is created by that part of the declaration, and the source still uses title. Then take the first value and the remaining values from a three-item array. The rest target receives a new array containing the final two values.
For a nested question, consider a source whose profile property is absent. An inner default for displayName alone cannot protect the missing profile level. An outer default for profile can handle undefined at that level, but it still does not handle null under the same default rule. A clear answer identifies both levels separately.
Finally, suppose a const binding receives a nested settings object. Can a property of that object change? Yes, unless another mechanism prevents mutation. Const protects the binding from reassignment, not every property of the referenced value. Draw the references if the distinction remains uncertain.
Choose one answer you found difficult and create a new example with different values. Predict it, run it, and explain any mismatch. This is the most useful part of the practice set. The goal is not to collect correct guesses; it is to make the rule available when the data shape or value changes. A learner who can transfer the explanation has a dependable foundation for the next topic.
CHAPTER 23 OF 24 · Debug and practise
23. Help a learner without crowding the session
A family learning session after a busy Punggol school day can stay focused on one small extraction. Begin with a two-property object and ask the student to identify the value needed by a simple report. Let them write the receiving name before introducing renaming, defaults or nesting. The first success should be easy to inspect and explain.
When the student can read that pattern, change one detail. Rename the receiving variable, remove a property, or supply null. Ask what should happen and why. This controlled variation is more useful than adding five unfamiliar features at once, because the learner can connect the changed outcome to the changed condition.
Parents can ask three practical questions: “Which source value are you reading?”, “Which name receives it?”, and “What happens if that value is absent?” These questions do not require a parent to memorise JavaScript grammar. They direct attention to the relationships that a tutor or student can verify in code.
If help is needed, bring the exact input and the expected output as well as the pattern. A screenshot of an error alone may omit the data shape that caused it. Describing whether the difficulty concerns renaming, defaults, nested absence or mutation makes the conversation more focused and productive.
Use How Studying Works to connect explanation, independent practice and correction. End with one new input the student can handle without looking back. A calm session can finish after that evidence of understanding; it does not need to grow into an exhausting tour of every JavaScript feature that happens to use braces. The next session can begin with a quick retrieval question and build from the one idea that remained uncertain.
CHAPTER 24 OF 24 · Debug and practise
24. Check documentation and decide what to learn next
The MDN destructuring reference provides examples of object and iterable patterns, defaults, rest and assignment contexts. The ECMAScript language specification defines the underlying language behaviour in a more formal style. Use the reference for a precise question, then test the relevant small case rather than trying to memorise every example on the page.
For related mechanisms, see the MDN iterator protocol guide and the MDN const reference. These help explain two common transfer difficulties: an array pattern can consume an iterable that is not an array, and a const binding can still refer to a mutable object.
A learner is ready to move on when they can distinguish property selection from ordered iteration, read a renamed target, predict undefined-specific defaults, and identify the level at which a nested source may be absent. They should also explain that extraction is separate from validation and that rest is not a deep clone. These are concrete skills to demonstrate on unfamiliar inputs.
Choose the next task from the remaining need. If data shape is unclear, practise drawing objects and arrays. If optional data is unclear, compare the optional-chaining lesson with explicit input validation. If the mechanism is secure, build a small task report with a normalisation function and a few justified tests.
Destructuring becomes comfortable when the punctuation represents relationships the student can already describe. Read the source shape, identify the target, state the fallback condition and check the contract. Those four habits make compact JavaScript easier to trust, explain and maintain.

