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

  1. The problem: six arrays that have to be kept aligned
  2. What an object is: literals, properties and values
  3. Dot access and bracket access
  4. Adding, modifying and deleting properties
  5. Properties that do not exist: undefined and optional chaining
  6. Checking whether a property exists
  7. The real backlog: an array of objects
  8. The payoff: describeTask(task, today)
  9. Nested objects: the task with its subtasks
  10. Computed property names
  11. Walking an object: for...in and Object.keys/values/entries
  12. Objects as a lookup dictionary
  13. A brief mention of Map and Set
  14. Common Mistakes and Tips
  15. Exercises
  16. Conclusion

  1. 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.

  1. 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 a Symbol, 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

  1. 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'

  1. 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 variable

Two important nuances about delete:

  • delete is not the same as setting undefined. After delete task.reviewer, the key reviewer no longer exists in the object. After task.reviewer = undefined, the key exists and its value is undefined. Reading with the dot you cannot tell the difference, but 'reviewer' in task can (section 6), and so can Object.keys and JSON.stringify (lesson 04-08).
  • In Nómada Tasks we almost never use delete. The model says reviewer is "a string or null": a missing reviewer is represented with null, which is an explicit value, not by deleting the key. Deleting fields from the canonical model breaks the functions that expect to find them.

  1. Properties that do not exist: undefined and optional chaining

Reading 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 write task?.title?.length everywhere, 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.

  1. 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'));  // false

Which 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 simply if (obj.key), watching out for the falsy values from 01-07) when what matters to you is the value.
  • in works, but it also finds properties inherited from the prototype (Module 5), which is sometimes surprising.
  • hasOwnProperty is the veteran; Object.hasOwn exists 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.

  1. 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.

  1. The payoff: 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 reviewer

Compare 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.

  1. 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.

  1. 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' }

  1. Walking an object: for...in and Object.keys/values/entries

An 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: Marta

Look 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] pairs

A 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:

for (const pair of Object.entries(task)) {
  console.log(`${pair[0]} → ${pair[1]}`);
}

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]));   // []

  1. 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 → ?? 0

Compare 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 way

Notice 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); // undefined

In 04-05 you will build this index far more elegantly with reduce.

  1. A brief mention of Map and Set

Objects 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);   // 2

We 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:

  1. Print the second area to the console.
  2. Add the property openSaturdays with the value true.
  3. Change seats to 20.
  4. Delete coordinator and check with Object.hasOwn that it is gone.
  5. Read a non-existent property phone without 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'));   // 30

An 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

Module 2: Control Structures

Module 3: Functions

Module 4: Objects and Arrays

Module 5: Advanced Objects and Functions

Module 6: The Document Object Model (DOM)

Module 7: Browser APIs and Advanced Topics

Module 8: Testing and Debugging

Module 9: Performance and Optimization

Module 10: JavaScript Frameworks and Libraries

Module 11: Final Project

© Copyright 2026. All rights reserved