We promised the relief would arrive on the first page, so let us start there. All through Module 3 you have been dragging six parallel arrays around —titles[i], assignees[i], priorities[i], statuses[i], hours[i], dueDates[i]— and one function, describeTask, that needed eight arguments to print a single line. Every time you wanted to filter, you filtered indexes instead of tasks. Every time you added a field to the model, you touched every function. In this lesson that ends: you will learn what an object is in JavaScript, how it is created, how its properties are read and modified, and you will use that knowledge to finally write the real Nómada Tasks backlog as an array of objects, with the clean signature describeTask(task, today). It is the most important change in the course so far, because from here on the code stops simulating the data model and starts containing it.
Contents
- The problem: six arrays that have to be kept aligned
- What an object is: literals, properties and values
- Dot access and bracket access
- Adding, modifying and deleting properties
- Properties that do not exist:
undefinedand optional chaining - Checking whether a property exists
- The real backlog: an array of objects
- The payoff:
describeTask(task, today) - Nested objects: the task with its subtasks
- Computed property names
- Walking an object:
for...inandObject.keys/values/entries - Objects as a lookup dictionary
- A brief mention of
MapandSet - Common Mistakes and Tips
- Exercises
- Conclusion
- The problem: six arrays that have to be kept aligned
Before celebrating the solution, look at the problem in all its rawness. This is how the backlog stood at the end of Module 3:
const titles = ['Redesign the multipurpose room', 'Signage for the screen-printing workshop', /* ... */];
const assignees = ['Iván', 'Marta', 'Lucía', 'Marta', 'Iván', 'Iván'];
const priorities = ['high', 'medium', 'high', 'low', 'medium', 'high'];
const statuses = ['in-progress', 'pending', 'pending', 'done', 'in-progress', 'pending'];
const hours = [12, 6, 14, 3, 8, 5];
const dueDates = ['2026-09-30', '2026-10-15', '2026-10-02', '2026-09-12', '2026-11-05', '2026-09-05'];This design has three serious flaws, and it is worth naming them:
| Flaw | What happens in practice |
|---|---|
| Fragility | If you delete task 3 from titles and forget to delete it from hours, from that index onwards the whole backlog is misaligned and the program raises no error at all: it simply lies |
| Enormous signatures | describeTask(id, title, assignee, priority, status, estimatedHours, dueDate, today) — eight positional parameters you have to remember in order |
| There is no "the task" | No value in the program actually is a task. All that exists is the number 3, and the tacit agreement that index 3 means the same thing in six places |
An object solves all three at once: it groups related data under a single value, with names instead of positions.
- What an object is: literals, properties and values
An object is a collection of key → value pairs. The way to create one is the object literal: braces {} with the pairs inside.
const task = {
id: 1,
title: 'Redesign the multipurpose room',
assignee: 'Iván',
priority: 'high',
status: 'in-progress',
tags: ['space', 'design'],
estimatedHours: 12,
dueDate: '2026-09-30',
reviewer: 'Marta'
};Precise vocabulary, because you will use it throughout the module:
- Property: each complete pair, for example
title: 'Redesign the multipurpose room'. - Key (or property name): the left-hand part,
title. Internally it is always a string (or aSymbol, which we will not cover here). Even if you write{ 1: 'one' }, the real key is'1'. - Value: the right-hand part. It can be anything: a number, a string,
null, an array (tags), another object, or even a function (those are methods, and they arrive in the next lesson).
Three syntax details that save you grief:
const example = {
title: 'No quotes needed if it is a valid identifier',
'due-date': 'Quotes needed if it has hyphens, spaces or starts with a digit',
status: 'pending', // the trailing comma is allowed
};Keys carry no quotes if they are valid JavaScript identifiers (letters, digits that do not come first, _ and $). If the key has a hyphen, a space or an awkward accent, it has to be quoted. In Nómada Tasks we always use unquoted camelCase keys: estimatedHours, dueDate.
And remember what you already know from 01-05: typeof an object is 'object', and objects are reference values. Two objects with the same contents are not equal:
console.log(typeof task); // 'object'
console.log({ id: 1 } === { id: 1 }); // false ← they are two different objects
- Dot access and bracket access
There are two ways to read a property, and they are not interchangeable.
// Dot notation: the key is written literally in the code
console.log(task.title); // 'Redesign the multipurpose room'
console.log(task.estimatedHours); // 12
// Bracket notation: the key is an EXPRESSION that evaluates to a string
console.log(task['title']); // 'Redesign the multipurpose room'
const field = 'assignee';
console.log(task[field]); // 'Iván' ← impossible with the dot
console.log(task.field); // undefined ← it looks for the literal key "field"That last line is the classic mistake: task.field looks for a property called field, not for the contents of the variable field. When each notation is needed:
| Situation | Notation | Example |
|---|---|---|
| You know the name as you write the code | Dot (preferred: shorter and more readable) | task.status |
| The name is in a variable | Brackets | task[field] |
| The name is computed | Brackets | task['estimated' + 'Hours'] |
| The name is not a valid identifier | Brackets | config['dark-mode'] |
| The name is a number | Brackets | weights[3] |
A practical case where brackets are indispensable: a generic function that sorts or compares by whichever field you tell it.
function fieldValue(task, field) {
return task[field]; // with the dot it would always be the property "field"
}
console.log(fieldValue(task, 'priority')); // 'high'
console.log(fieldValue(task, 'reviewer')); // 'Marta'
- Adding, modifying and deleting properties
Objects declared with const can indeed be modified. What const prevents is reassigning the variable, not changing the contents of the object it points to (you saw this in 01-05 with primitives versus references).
const task = { id: 1, title: 'Redesign the multipurpose room', status: 'pending' };
// Modify: the key already exists
task.status = 'in-progress';
// Add: the key did not exist, it is created on the fly
task.assignee = 'Iván';
task['estimatedHours'] = 12;
console.log(task);
// { id: 1, title: 'Redesign the multipurpose room', status: 'in-progress',
// assignee: 'Iván', estimatedHours: 12 }
// Delete: the delete operator removes the whole property
delete task.estimatedHours;
console.log(task.estimatedHours); // undefined
// Reassigning the variable DOES fail
// task = { id: 2 }; // ✗ TypeError: Assignment to constant variableTwo important nuances about delete:
deleteis not the same as settingundefined. Afterdelete task.reviewer, the keyreviewerno longer exists in the object. Aftertask.reviewer = undefined, the key exists and its value isundefined. Reading with the dot you cannot tell the difference, but'reviewer' in taskcan (section 6), and so canObject.keysandJSON.stringify(lesson 04-08).- In Nómada Tasks we almost never use
delete. The model saysrevieweris "a string ornull": a missing reviewer is represented withnull, which is an explicit value, not by deleting the key. Deleting fields from the canonical model breaks the functions that expect to find them.
- Properties that do not exist:
undefined and optional chaining
undefined and optional chainingReading a property that does not exist is not an error: it returns undefined.
const task = { id: 1, title: 'Redesign the multipurpose room' };
console.log(task.assignee); // undefined ← it does not exist, but it does not fail
console.log(task.titel); // undefined ← a typo also returns undefined!That second line explains why typos in property names are so treacherous: JavaScript does not warn you, it simply hands you undefined and the failure shows up three functions later. (In 08-02 you will see how a linter and type annotations catch this before running.)
What does blow up is trying to read a property of undefined:
const task = { id: 1, title: 'Redesign the multipurpose room' };
console.log(task.review.author);
// ✗ TypeError: Cannot read properties of undefined (reading 'author')This is where the optional chaining ?. you met in 01-06 comes back. If what sits on the left is null or undefined, the whole expression stops and returns undefined instead of throwing the error:
console.log(task.review?.author); // undefined ← no TypeError
console.log(task.review?.author ?? 'not reviewed'); // 'not reviewed'Combined with ?? (nullish coalescing), you have the perfect pair for reading data that may not be there: ?. avoids the error and ?? supplies the default value. A direct example from the project, where reviewer may be null:
function reviewerName(task) {
return task.reviewer ?? 'not yet assigned';
}
console.log(reviewerName({ reviewer: 'Marta' })); // 'Marta'
console.log(reviewerName({ reviewer: null })); // 'not yet assigned'Be careful not to overuse
?.. If you writetask?.title?.lengtheverywhere, you are papering over the fact that you do not know what data you are receiving. Use it where the data is legitimately optional (reviewer, a field that does not exist yet), not as a universal safety net.
- Checking whether a property exists
There are four ways of asking "does this object have this property?", and they do not answer exactly the same question.
| Form | Returns true when… |
With { reviewer: undefined } |
With inherited properties |
|---|---|---|---|
task.reviewer !== undefined |
The value is not undefined |
false |
It sees them |
'reviewer' in task |
The key exists, whatever its value | true |
It sees them |
task.hasOwnProperty('reviewer') |
The key is the object's own | true |
No |
Object.hasOwn(task, 'reviewer') |
The key is the object's own (modern, recommended version) | true |
No |
const task = { id: 1, title: 'Carpentry workshop quote', reviewer: undefined };
console.log(task.reviewer !== undefined); // false ← the value is undefined
console.log('reviewer' in task); // true ← but the key is there
console.log(Object.hasOwn(task, 'reviewer')); // true
console.log(Object.hasOwn(task, 'priority')); // falseWhich one to use? The practical rule:
Object.hasOwn(obj, key)when you want to know whether the key is present. It is the modern form and the one you should write by default.obj.key !== undefined(or simplyif (obj.key), watching out for the falsy values from 01-07) when what matters to you is the value.inworks, but it also finds properties inherited from the prototype (Module 5), which is sometimes surprising.hasOwnPropertyis the veteran;Object.hasOwnexists precisely to replace it with fewer traps.
A real example: validating that an incoming object has every mandatory field of the model.
const REQUIRED_FIELDS = ['id', 'title', 'assignee', 'priority',
'status', 'tags', 'estimatedHours', 'dueDate'];
function missingFields(task) {
const missing = [];
for (const field of REQUIRED_FIELDS) {
if (!Object.hasOwn(task, field)) missing.push(field);
}
return missing;
}
console.log(missingFields({ id: 7, title: 'Service the paper guillotine' }));
// [ 'assignee', 'priority', 'status', 'tags', 'estimatedHours', 'dueDate' ]Notice that reviewer is not on the list: it is optional, and its absence is represented with null.
- The real backlog: an array of objects
The moment has come. This block of code is the source of truth for the rest of the module; you will see it reappear in the next seven lessons.
'use strict';
const TODAY = '2026-09-20';
const backlog = [
{
id: 1,
title: 'Redesign the multipurpose room',
assignee: 'Iván',
priority: 'high',
status: 'in-progress',
tags: ['space', 'design'],
estimatedHours: 12,
dueDate: '2026-09-30',
reviewer: 'Marta'
},
{
id: 2,
title: 'Signage for the screen-printing workshop',
assignee: 'Marta',
priority: 'medium',
status: 'pending',
tags: ['screen-printing', 'communication'],
estimatedHours: 6,
dueDate: '2026-10-15',
reviewer: null
},
{
id: 3,
title: 'Update the bookings website',
assignee: 'Lucía',
priority: 'high',
status: 'pending',
tags: ['web', 'bookings'],
estimatedHours: 14,
dueDate: '2026-10-02',
reviewer: 'Iván'
},
{
id: 4,
title: 'Screen-printing ink inventory',
assignee: 'Marta',
priority: 'low',
status: 'done',
tags: ['screen-printing', 'storeroom'],
estimatedHours: 3,
dueDate: '2026-09-12',
reviewer: null
},
{
id: 5,
title: 'Bookbinding guide for residents',
assignee: 'Iván',
priority: 'medium',
status: 'in-progress',
tags: ['bookbinding', 'documentation'],
estimatedHours: 8,
dueDate: '2026-11-05',
reviewer: 'Lucía'
},
{
id: 6,
title: 'Carpentry workshop quote',
assignee: 'Iván',
priority: 'high',
status: 'pending',
tags: ['carpentry', 'purchasing'],
estimatedHours: 5,
dueDate: '2026-09-05',
reviewer: 'Marta'
}
];The data is exactly the same as always —48 h in total, 45 h open, Iván with three tasks and 25 h, "Carpentry workshop quote" overdue— but now each task is a single unit. See for yourself:
console.log(backlog.length); // 6
console.log(backlog[0].title); // 'Redesign the multipurpose room'
console.log(backlog[5].dueDate); // '2026-09-05'
console.log(backlog[2].tags[0]); // 'web'Read backlog[2].tags[0] slowly: from the backlog array take the element at position 2 (an object), from that object read the tags property (an array) and from that array take element 0 (a string). Access chains from left to right, one step at a time.
flowchart TD
A["backlog<br/>(array of 6 elements)"] --> B0["[0] task object"]
A --> B1["[1] task object"]
A --> B2["[2] task object"]
A --> B5["[5] task object"]
B2 --> C1["id: 3"]
B2 --> C2["title: 'Update the bookings website'"]
B2 --> C3["tags: ['web', 'bookings']"]
B2 --> C4["reviewer: 'Iván'"]
And now the check that really matters: deleting a task can no longer misalign anything.
// With parallel arrays you had to delete index 3 in SIX arrays.
// Now the whole task disappears in one go, and nothing shifts out of place.
- The payoff:
describeTask(task, today)
describeTask(task, today)Let us bring back the Module 3 functions and rewrite them with the real model.
function priorityWeight(priority) {
if (priority === 'high') return 3;
if (priority === 'medium') return 2;
if (priority === 'low') return 1;
return 0;
}
// R10: overdue = due date in the past and the task is not finished.
function isOverdue(task, today) {
return task.dueDate < today && task.status !== 'done';
}
function statusBadge(status) {
if (status === 'done') return '✓';
if (status === 'in-progress') return '▸';
return '○';
}
function describeTask(task, today) {
const badge = statusBadge(task.status);
const warning = isOverdue(task, today) ? ' ⚠ OVERDUE' : '';
const review = task.reviewer ?? 'no reviewer';
return `${badge} [${task.id}] ${task.title} · ${task.assignee} · ` +
`${task.priority} · ${task.estimatedHours} h · rev: ${review}${warning}`;
}
console.log(describeTask(backlog[0], TODAY));
// ▸ [1] Redesign the multipurpose room · Iván · high · 12 h · rev: Marta
console.log(describeTask(backlog[5], TODAY));
// ○ [6] Carpentry workshop quote · Iván · high · 5 h · rev: Marta ⚠ OVERDUE
console.log(describeTask(backlog[3], TODAY));
// ✓ [4] Screen-printing ink inventory · Marta · low · 3 h · rev: no reviewerCompare the two signatures of describeTask:
| Module 3 | Now | |
|---|---|---|
| Signature | describeTask(id, title, assignee, priority, status, estimatedHours, dueDate, today) |
describeTask(task, today) |
| Call | describeTask(ids[i], titles[i], assignees[i], priorities[i], statuses[i], hours[i], dueDates[i], TODAY) |
describeTask(backlog[i], TODAY) |
Adding the reviewer field |
Change the signature and every call | Nothing changes: the field already travels inside |
| Risk of getting the order wrong | High: seven strings and numbers in a row | None |
This is the real benefit of objects: data that belongs together travels together. And notice the side effect on isOverdue(task, today): it went from three parameters to two, and it can no longer receive one task's date with another task's status.
- Nested objects: the task with its subtasks
In 03-07 you used, reluctantly, an object with nested subtasks. Now you can read it properly:
const redesignTask = {
id: 1,
title: 'Redesign the multipurpose room',
assignee: 'Iván',
status: 'in-progress',
estimatedHours: 0,
subtasks: [
{ id: 11, title: 'Measure and draw up the floor plan', assignee: 'Iván',
status: 'done', estimatedHours: 3, subtasks: [] },
{ id: 12, title: 'Choose the furniture', assignee: 'Marta',
status: 'in-progress', estimatedHours: 0,
subtasks: [
{ id: 121, title: 'Request quotes', assignee: 'Marta',
status: 'done', estimatedHours: 2, subtasks: [] },
{ id: 122, title: 'Visit two suppliers', assignee: 'Marta',
status: 'pending', estimatedHours: 2, subtasks: [] }
]
},
{ id: 13, title: 'Paint and assemble', assignee: 'Iván',
status: 'pending', estimatedHours: 5, subtasks: [] }
]
};
console.log(redesignTask.subtasks[1].subtasks[0].title); // 'Request quotes'
console.log(redesignTask.subtasks[0].subtasks.length); // 0
console.log(redesignTask.subtasks[9]?.title); // undefined (no error)The last line shows why ?. is so useful with nested structures: subtasks[9] does not exist, and without ?. you would get a TypeError. A value can be nested as deep as you like: an object inside an array inside an object inside an array. Access is always read from left to right, one step at a time.
- Computed property names
Sometimes a property's key is not known until the program runs. The computed property name syntax lets you calculate it inside the literal itself, by putting it in brackets:
const field = 'status';
const value = 'in-progress';
const filter = { [field]: value };
console.log(filter); // { status: 'in-progress' }
// Without brackets, the key would literally be "field"
const badFilter = { field: value };
console.log(badFilter); // { field: 'in-progress' } ✗Any expression that produces a string can be used:
const person = 'Iván';
const counter = {
[`tasks_of_${person}`]: 3,
[`hours_of_${person}`]: 25
};
console.log(counter); // { tasks_of_Iván: 3, hours_of_Iván: 25 }A practical use: a function that builds a search criterion from whichever field the user picks in the future Nómada Tasks panel.
function criterion(field, value) {
return { [field]: value };
}
console.log(criterion('assignee', 'Lucía')); // { assignee: 'Lucía' }
console.log(criterion('priority', 'high')); // { priority: 'high' }
- Walking an object:
for...in and Object.keys/values/entries
for...in and Object.keys/values/entriesAn array is walked by positions; an object is walked by keys. There are two routes.
for...in walks the keys (always as strings):
const task = backlog[0];
for (const key in task) {
console.log(`${key}: ${task[key]}`);
}
// id: 1
// title: Redesign the multipurpose room
// assignee: Iván
// priority: high
// status: in-progress
// tags: space,design
// estimatedHours: 12
// dueDate: 2026-09-30
// reviewer: MartaLook at the access: inside the loop you have to use brackets, task[key], because the key is in a variable. task.key would give undefined nine times over.
The three Object methods turn the object into arrays, which you already know how to handle:
console.log(Object.keys(task));
// [ 'id', 'title', 'assignee', 'priority', 'status',
// 'tags', 'estimatedHours', 'dueDate', 'reviewer' ]
console.log(Object.values(task));
// [ 1, 'Redesign the multipurpose room', 'Iván', 'high', 'in-progress',
// [ 'space', 'design' ], 12, '2026-09-30', 'Marta' ]
console.log(Object.entries(task));
// [ [ 'id', 1 ], [ 'title', 'Redesign the multipurpose room' ], ... ]
// ↑ an array of [key, value] pairsA practical comparison:
| Form | What it gives you | Advantage | Drawback |
|---|---|---|---|
for...in |
One key per turn | Direct syntax | It also walks properties inherited from the prototype (Module 5) |
Object.keys(o) |
Array of own keys | Own keys only; you can use array methods | It creates an intermediate array |
Object.values(o) |
Array of own values | Ideal for adding up or counting | You lose the names |
Object.entries(o) |
Array of [key, value] pairs |
You get everything | Slightly denser syntax (it improves a lot with destructuring, 04-06) |
In practice, Object.entries with a for...of is the default option, and for...in is left for the odd special case:
In 04-06 you will see how to write that very same thing as for (const [key, value] of Object.entries(task)), which is far more readable.
A useful example: counting how many fields of the model are empty.
function emptyFields(task) {
const empty = [];
for (const key of Object.keys(task)) {
const value = task[key];
if (value === null || value === undefined || value === '') empty.push(key);
}
return empty;
}
console.log(emptyFields(backlog[1])); // [ 'reviewer' ]
console.log(emptyFields(backlog[0])); // []
- Objects as a lookup dictionary
There is a second use for objects, different from "grouping the fields of an entity": using them as a dictionary or lookup table, where the keys are data rather than fixed names. You already saw it in passing in 02-03 as an alternative to switch:
const WEIGHTS = { high: 3, medium: 2, low: 1 };
function priorityWeight(priority) {
return WEIGHTS[priority] ?? 0; // brackets: the key comes in a variable
}
console.log(priorityWeight('high')); // 3
console.log(priorityWeight('urgentish')); // 0 ← unknown key → undefined → ?? 0Compare the two versions of the same function:
if chain (03-01) |
Dictionary | |
|---|---|---|
| Adding a new priority | Write another if |
Add a line to WEIGHTS |
| The data lives… | Mixed in with the logic | Separately, in one single place |
| Cost of the lookup | It walks the ifs in order |
Direct access by key |
The pattern generalizes to any value → value translation:
const BADGES = { done: '✓', 'in-progress': '▸', pending: '○' };
const STATUS_NAMES = { pending: 'Not started', 'in-progress': 'Under way', done: 'Completed' };
function statusBadge(status) {
return BADGES[status] ?? '?';
}
function statusName(status) {
return STATUS_NAMES[status] ?? 'Unknown';
}
console.log(`${statusBadge('in-progress')} ${statusName('in-progress')}`); // ▸ Under wayNotice that 'in-progress' needs quotes as a key, because the hyphen is not valid in an identifier. To read it, BADGES['in-progress'] with brackets; BADGES.in-progress would be a syntax error (JavaScript would read it as a subtraction).
And a third use, the index by identifier, which saves you walking the whole backlog every time you look for a task:
const byId = {};
for (const task of backlog) {
byId[task.id] = task; // the numeric key is converted into the string '1', '2'...
}
console.log(byId[3].title); // 'Update the bookings website'
console.log(byId[99]?.title); // undefinedIn 04-05 you will build this index far more elegantly with reduce.
- A brief mention of
Map and Set
Map and SetObjects used as dictionaries have two limits: their keys can only be strings (everything else is converted to a string) and they do not keep count of how many entries they hold. For those cases JavaScript offers two dedicated structures:
Map: a dictionary where the key can be any value (a real number, an object, a function), which preserves insertion order and has.size.Set: a collection of values with no duplicates, ideal for deduplicating tags.
const uniqueTags = new Set(['screen-printing', 'purchasing', 'screen-printing']);
console.log(uniqueTags.size); // 2We will not develop them here: they appear in detail in Searching, Sorting and Aggregating Data, when you have to group and deduplicate for real. For 95% of what you will do in this course, an object literal is enough.
Common Mistakes and Tips
1. Using the dot when the key is in a variable.
const field = 'status';
console.log(task.field); // ✗ undefined
console.log(task[field]); // ✓ 'in-progress'2. Forgetting the quotes when reading a key with brackets.
console.log(task[title]); // ✗ ReferenceError: title is not defined
console.log(task['title']); // ✓With brackets, what goes inside is an expression. Without quotes, JavaScript looks for a variable with that name.
3. Believing that const freezes the object. It does not: const protects the variable, not the contents. To prevent changes to the object there is Object.freeze, which you will see in 04-08.
4. Confusing "it does not exist" with "its value is undefined". Use Object.hasOwn when the question is about the key; compare with undefined when it is about the value.
5. Chaining accesses without protection. task.subtasks[0].title blows up if subtasks is empty. With task.subtasks[0]?.title you get undefined and stay alive.
6. Putting one comma too many between properties. { id: 1,, title: 'x' } is a syntax error. The trailing comma ({ id: 1, }) is allowed and is good practice: Git diffs come out cleaner.
Professional tip. Pick a fixed order for the fields —in Nómada Tasks: id, title, assignee, priority, status, tags, estimatedHours, dueDate, reviewer— and stick to it in every object. Reading code is comparing, and comparing is far quicker when everything is in the same place.
Exercises
Exercise 1 — Workshop record card. Create an object workshop describing the Taller Nómada space with the properties name ('Taller Nómada'), seats (18), areas (an array with 'coworking', 'screen-printing', 'bookbinding', 'carpentry') and coordinator ('Marta'). Then:
- Print the second area to the console.
- Add the property
openSaturdayswith the valuetrue. - Change
seatsto 20. - Delete
coordinatorand check withObject.hasOwnthat it is gone. - Read a non-existent property
phonewithout the program failing, showing'no phone'if it does not exist.
Exercise 2 — Deadline dictionary. Write a function daysOfHeadroom(priority) that, using a lookup object (no if, no switch), returns how many days of headroom a task has according to its priority: high → 3, medium → 7, low → 15. If the priority is unknown, it must return 30. Try it with 'high', 'low' and 'none'.
Exercise 3 — Task inspector. Write a function inspect(task) that takes a task from the backlog and returns a multiline string with one line per field, in the format key: value, skipping the tags field. Then write fieldSummary(task), which returns an object with three properties: total (number of fields), empty (number of fields whose value is null) and names (an array with the field names). Test it with backlog[1].
Solutions
Exercise 1
const workshop = {
name: 'Taller Nómada',
seats: 18,
areas: ['coworking', 'screen-printing', 'bookbinding', 'carpentry'],
coordinator: 'Marta'
};
// 1. Second area: index 1, because arrays start at 0
console.log(workshop.areas[1]); // 'screen-printing'
// 2. Add a new property
workshop.openSaturdays = true;
// 3. Modify an existing one
workshop.seats = 20;
// 4. Delete and check
delete workshop.coordinator;
console.log(Object.hasOwn(workshop, 'coordinator')); // false
// 5. Non-existent property without failing
console.log(workshop.phone ?? 'no phone'); // 'no phone'
console.log(workshop);
// { name: 'Taller Nómada', seats: 20,
// areas: [ 'coworking', 'screen-printing', 'bookbinding', 'carpentry' ],
// openSaturdays: true }The frequent mistake here is point 1: workshop.areas[2] would return 'bookbinding', the third one. Remember that position 1 is the second element.
Exercise 2
const HEADROOM_IN_DAYS = { high: 3, medium: 7, low: 15 };
function daysOfHeadroom(priority) {
return HEADROOM_IN_DAYS[priority] ?? 30;
}
console.log(daysOfHeadroom('high')); // 3
console.log(daysOfHeadroom('low')); // 15
console.log(daysOfHeadroom('none')); // 30An important detail: here ?? is more correct than ||. If some day a priority had headroom 0, HEADROOM_IN_DAYS[p] || 30 would return 30 because 0 is falsy, whereas ?? only steps in for null or undefined. It is exactly the distinction you studied in 01-06.
Exercise 3
function inspect(task) {
const lines = [];
for (const key of Object.keys(task)) {
if (key === 'tags') continue;
lines.push(`${key}: ${task[key]}`);
}
return lines.join('\n');
}
function fieldSummary(task) {
const names = Object.keys(task);
let empty = 0;
for (const key of names) {
if (task[key] === null) empty++;
}
return { total: names.length, empty: empty, names: names };
}
console.log(inspect(backlog[1]));
// id: 2
// title: Signage for the screen-printing workshop
// assignee: Marta
// priority: medium
// status: pending
// estimatedHours: 6
// dueDate: 2026-10-15
// reviewer: null
console.log(fieldSummary(backlog[1]));
// { total: 9, empty: 1,
// names: [ 'id', 'title', 'assignee', 'priority', 'status',
// 'tags', 'estimatedHours', 'dueDate', 'reviewer' ] }Two observations. First: fieldSummary returns an object, which is exactly what 03-03 recommended for returning several named values; now you know how that object is built. Second: the continue inside the for...of is the same one from 02-04, and here it serves to skip the tags field without breaking the loop.
Conclusion
You have settled the debt. An object is a collection of key → value pairs written with a {} literal; its properties are read with the dot when you know the name and with brackets when the name is in a variable or gets computed. You know how to add and modify properties on the fly, how to delete them with delete (and why in this project that is almost never a good idea), and what happens when you read one that does not exist: undefined, with no error, unless you try to keep chaining —that is what ?. is for, and you now genuinely understand it. You also have the table of the four ways to check whether a key exists, with Object.hasOwn as the recommended option.
And above all you have the real backlog: six objects inside an array, with the same data as always (48 h in total, 45 open, Iván with 25 h, the carpentry quote overdue) but structured at last. With it you have rewritten isOverdue(task, today) and describeTask(task, today), and you have seen the change of scale: from eight positional parameters down to two, from six arrays that had to be kept in sync to a single list you can delete from and sort without fear. You have also seen the nested objects of the subtask tree, computed property names { [key]: value }, the four ways of walking an object (for...in, Object.keys, Object.values, Object.entries) and the use of objects as a lookup dictionary, which turns four ifs into one line.
But an object can hold more than just data. If WEIGHTS can live inside an object, why not priorityWeight? And what if the Taller Nómada board kept, alongside its list of tasks, the very operations for adding a task or changing its status? That is exactly what we will look at in Object Methods and the this Keyword: functions stored in properties, the shorthand syntax for writing them and that keyword, this, which is at once the most useful and the most misunderstood thing in JavaScript. Along the way you will settle another outstanding debt from Module 3: why arrow functions behave differently from normal ones.
JavaScript Course: From Beginner to Advanced
Module 1: Introduction to JavaScript
- What Is JavaScript?
- Setting Up Your Development Environment
- Your First JavaScript Program
- JavaScript Syntax and Basic Concepts
- Variables and Data Types
- Basic Operators
- Type Conversion and Comparisons
- The Course Project: Nómada Tasks
Module 2: Control Structures
- Conditional Statements
- Loops: for, while, do-while
- Switch Statements
- Flow Control: break, continue and Nested Loops
- Error Handling with try-catch
Module 3: Functions
- Defining and Calling Functions
- Function Expressions and Arrow Functions
- Parameters and Return Values
- Scope and Closures
- Hoisting and the Execution Context
- Higher-Order Functions
- Recursion
Module 4: Objects and Arrays
- Introduction to Objects
- Object Methods and the
thisKeyword - Arrays: Basics and Methods
- Iterating over Arrays
- Searching, Sorting and Aggregating Data: find, sort and reduce
- Array Destructuring
- Object Destructuring, Spread and Rest
- JSON and Copying Objects
Module 5: Advanced Objects and Functions
- Prototypes and Inheritance
- Classes and Object-Oriented Programming
- Encapsulation: Getters, Setters and Private Fields
- Modules: Import and Export
- Asynchronous JavaScript: Callbacks
- Promises and Async/Await
- The Event Loop and the Microtask Queue
- Iterators and Generators
Module 6: The Document Object Model (DOM)
- Introduction to the DOM
- Selecting and Manipulating DOM Elements
- Handling Events
- Propagation, Delegation and Custom Events
- Creating and Removing DOM Elements
- Rendering Lists and HTML Templates
- Handling and Validating Forms
Module 7: Browser APIs and Advanced Topics
- Local and Session Storage
- The Fetch API and AJAX
- Robust Requests: Errors, Timeouts and AbortController
- WebSockets
- Service Workers and Progressive Web Apps (PWAs)
- Essential Browser APIs
- Introduction to WebAssembly
Module 8: Testing and Debugging
- Debugging JavaScript
- Code Quality: ESLint, Prettier and Conventions
- Unit Testing with Jest
- Test Doubles: Mocks, Stubs and Spies
- Integration Testing
- End-to-End Testing with Cypress
Module 9: Performance and Optimization
- Measure Before You Optimize: DevTools and Web Vitals
- Optimizing JavaScript Performance
- Memory Management
- Efficient DOM Manipulation
- Lazy Loading and Code Splitting
Module 10: JavaScript Frameworks and Libraries
- Why Frameworks Exist
- Introduction to React
- State Management with Redux
- Vue.js Basics
- Angular Basics
- Choosing the Right Framework
