Introduction to Web Technologies (HTML, CSS, JS)

Chapter 10
JavaScript Data Structures and Modular Code

Diploma in Full Stack Programming
Introduction to Web Technologies (HTML, CSS, JS)   •   Diploma

JavaScript Data Structures and Modular Code

Introduction

Modern web applications depend on more than the ability to display HTML and style it with CSS. They must represent information, transform it, preserve application state, communicate with services, and organise increasingly large codebases. JavaScript provides a set of built-in data structures and language features that support these tasks.

This chapter examines strings, arrays, objects, dates, sets, maps, destructuring, spread syntax, array methods, classes, modules, and functional programming concepts. The emphasis is not merely on syntax, but on selecting suitable structures, writing predictable transformations, and designing reusable code.

The examples use modern JavaScript syntax. Unless stated otherwise, they are suitable for current browsers and Node.js environments that support ECMAScript modules.

Learning Objectives

By the end of this chapter, you should be able to:

  • Select appropriate JavaScript data structures for common application problems.
  • Manipulate strings, arrays, objects, dates, sets, and maps safely.
  • Use destructuring, spread syntax, and array methods to write expressive code.
  • Distinguish mutation from immutable transformation.
  • Design classes for objects with related data and behaviour.
  • Create and consume ES modules.
  • Apply functional programming concepts such as pure functions, composition, and higher-order functions.
  • Organise a small web application into maintainable, reusable modules.
  • Identify common limitations, security concerns, and maintainability risks.

1. Values, Types, and Data Representation

JavaScript variables hold values. The primitive types include strings, numbers, bigints, booleans, undefined, null, and symbols. Objects—including arrays, dates, maps, sets, and class instances—are reference values.

This distinction matters. A primitive value is copied by value:

let firstName = "Amina";
let anotherName = firstName;
anotherName = "Jon";
console.log(firstName); // Amina

Objects are accessed through references:

const original = { active: true };
const alias = original;
alias.active = false;
console.log(original.active); // false

Using const prevents reassignment of the variable, but it does not make an object immutable. This is an important source of accidental state changes in applications.

Choosing a data structure

RequirementSuitable structure
Ordered collectionArray
Named properties describing one entityObject or class
Unique valuesSet
Key–value lookup with arbitrary key typesMap
Calendar or timestamp operationsDate, often with explicit time-zone handling
Text processingString

A structure should be selected according to the operation the program performs. For example, a Set communicates that duplicates are not meaningful, while an array communicates order and supports positional access.

2. Strings and Text Processing

A string is an immutable sequence of characters. Operations create new strings rather than changing the original string.

const title = "Introduction to JavaScript";

console.log(title.length);                 // 27
console.log(title.toUpperCase());          // INTRODUCTION TO JAVASCRIPT
console.log(title.includes("JavaScript")); // true
console.log(title.startsWith("Intro"));    // true
console.log(title.slice(0, 12));            // Introduction

Template literals provide interpolation and multiline text:

const learner = "Ravi";
const message = `Welcome, ${learner}!`;

Useful methods include trim, split, replace, replaceAll, padStart, and toLowerCase. Regular expressions can support validation and searching, but they should not be used as a substitute for a complete parser when the input language is complex.

A common practical operation is normalisation:

function normaliseEmail(email) {
  return email.trim().toLowerCase();
}

Normalisation improves comparison, but it must reflect the domain. Lowercasing an email address is common for application-level comparison, yet email standards and provider behaviour can involve details that a simple function does not model completely.

Never insert untrusted strings into HTML using innerHTML without appropriate sanitisation. Prefer textContent when displaying plain user-provided text.

3. Arrays and Array Methods

An array is an ordered, zero-indexed collection. It may contain values of different types, although collections with a consistent element type are easier to understand and maintain.

const prices = [12.5, 8, 19.75];
console.log(prices[0]);       // 12.5
console.log(prices.length);   // 3

Mutation and non-mutating operations

Methods such as push, pop, shift, unshift, splice, and sort can mutate an array. Methods such as map, filter, slice, and concat return new arrays.

const numbers = [3, 1, 2];
const sorted = [...numbers].sort((a, b) => a - b);

console.log(numbers); // [3, 1, 2]
console.log(sorted);  // [1, 2, 3]

The copied array is important because sort() mutates its receiver. The comparison function is also necessary: without it, values are converted to strings and sorted lexicographically.

Core transformation methods

map transforms every element and returns an array of equal length:

const taxRates = [0.05, 0.1, 0.2];
const percentages = taxRates.map(rate => rate * 100);

filter retains elements that satisfy a condition:

const adults = users.filter(user => user.age >= 18);

find returns the first matching element or undefined:

const selected = users.find(user => user.id === 42);

some tests whether at least one element satisfies a condition, while every tests whether all elements satisfy it:

const hasOverdue = invoices.some(invoice => invoice.overdue);
const allPaid = invoices.every(invoice => invoice.status === "paid");

reduce combines a collection into one result. The initial accumulator should normally be supplied explicitly:

const total = prices.reduce((sum, price) => sum + price, 0);

The accumulator begins at 0, and each price is added to it. Supplying an initial value also makes the empty-array case well defined.

forEach is useful for side effects, but it does not create a transformed result and cannot be interrupted with break. A for...of loop is often clearer when control flow or asynchronous work is involved.

Chaining methods

Methods can be composed into a readable pipeline:

const premiumNames = users
  .filter(user => user.plan === "premium")
  .map(user => user.name)
  .sort();

Each step should have a clear purpose. Excessive chaining can become difficult to debug; in such cases, named intermediate variables improve clarity.

4. Objects and Property Management

Objects represent related properties. Property names may be written with dot notation or bracket notation:

const product = {
  id: 17,
  name: "Keyboard",
  stock: 12
};

console.log(product.name);
console.log(product["stock"]);

Bracket notation is essential when the property name is stored in a variable:

const field = "name";
console.log(product[field]);

Object methods include Object.keys, Object.values, and Object.entries:

for (const [key, value] of Object.entries(product)) {
  console.log(`${key}: ${value}`);
}

Objects are appropriate for records with named fields. They are not always the best choice for arbitrary key–value collections; Map provides clearer semantics for that purpose.

Destructuring

Destructuring extracts values from arrays or properties from objects:

const [first, second] = ["HTML", "CSS"];
const { name, stock } = product;

Aliases and defaults are possible:

const { name: productName, category = "general" } = product;

Function parameters can be destructured to make the required fields visible:

function describeProduct({ name, stock }) {
  return `${name}: ${stock} available`;
}

Spread syntax

Object spread creates a shallow copy or combines properties:

const updatedProduct = { ...product, stock: product.stock - 1 };

Later properties override earlier properties. Spread is shallow, so nested objects remain shared references:

const state = { preferences: { theme: "dark" } };
const copy = { ...state };
copy.preferences.theme = "light";
// state.preferences.theme is also now "light"

For nested updates, copy every affected level:

const nextState = {
  ...state,
  preferences: { ...state.preferences, theme: "light" }
};

5. Dates and Time

The JavaScript Date object represents a point in time internally as a number of milliseconds from the Unix epoch, 1 January 1970 UTC. Date handling is difficult because a displayed calendar date depends on time zone, locale, daylight-saving rules, and the desired business meaning.

const now = new Date();
const launch = new Date("2026-04-15T09:00:00Z");
console.log(launch.toISOString());

Use ISO 8601 strings with an explicit offset or Z when exchanging timestamps. Avoid ambiguous formats such as 04/05/2026, whose meaning differs across locales.

Methods such as getUTCFullYear and getUTCMonth operate in UTC; local methods operate in the host environment’s local time zone. Months returned by getMonth() are zero-based, so January is 0 and December is 11.

For serious scheduling, recurring events, or multiple time zones, use a well-maintained date-time library or the modern Temporal API where supported or available through an approved polyfill. Store instants consistently and convert them for presentation at the user interface boundary.

6. Sets and Maps

A Set stores unique values:

const tags = new Set(["web", "javascript", "web"]);
tags.add("css");
console.log(tags.size); // 3
console.log(tags.has("web")); // true

Sets are useful for deduplication and membership tests. They preserve insertion order during iteration, but they do not provide numeric indexing.

A Map stores key–value pairs and permits keys of any value type:

const cache = new Map();
cache.set("user:42", { name: "Amina" });
console.log(cache.get("user:42"));

Important methods are set, get, has, delete, and clear. A Map is often preferable to an object when keys are dynamic, when key types other than strings are needed, or when the collection’s size and iteration behaviour matter.

7. Classes and Object-Oriented Organisation

A class describes how instances are created and how they behave. It can group state with operations that belong to that state.

export class ShoppingCart {
  #items = [];

  add(product, quantity = 1) {
    if (quantity <= 0) throw new Error("Quantity must be positive");
    this.#items.push({ product, quantity });
  }

  get itemCount() {
    return this.#items.reduce((total, item) => total + item.quantity, 0);
  }

  getItems() {
    return this.#items.map(item => ({ ...item }));
  }
}

The #items field is private. The getter exposes a calculated value without presenting it as a method call. getItems returns copies of item records, reducing accidental external mutation.

Inheritance is available through extends, but composition is often easier to maintain. Instead of creating a deep hierarchy, an application can construct an object from smaller collaborators. Classes should have focused responsibilities and should validate their public inputs.

8. Modules and Reusable Code

Modules divide a program into files with explicit imports and exports. They create a scope, reduce accidental global variables, and make dependencies visible.

formatters.js:

export function formatCurrency(value, currency = "USD") {
  return new Intl.NumberFormat("en-US", {
    style: "currency",
    currency
  }).format(value);
}

cartService.js:

import { formatCurrency } from "./formatters.js";

export function cartSummary(cart) {
  const total = cart.getItems().reduce(
    (sum, item) => sum + item.product.price * item.quantity,
    0
  );
  return formatCurrency(total);
}

A module may use named exports or one default export. Named exports are often preferable in larger projects because the exported names are explicit and refactoring tools can track them effectively.

In browsers, modules are loaded with:

<script type="module" src="./app.js"></script>

Module paths generally require a file extension in browser environments. Modules are deferred by default, execute in strict mode, and are loaded asynchronously. A module should expose a small public interface and keep implementation details private.

9. Functional Programming Concepts

Functional programming treats computation as the evaluation and combination of functions. JavaScript is multi-paradigm, so functional techniques can be combined with object-oriented and imperative techniques.

A pure function:

  1. returns the same output for the same inputs; and
  2. produces no observable side effects.
function addTax(price, rate) {
  return price + price * rate;
}

The function does not modify external data. In contrast, a function that changes a global variable or mutates an input object has side effects.

Higher-order functions accept functions as arguments or return functions:

function greaterThan(limit) {
  return value => value > limit;
}

const isLarge = greaterThan(100);
console.log(isLarge(150)); // true

Closures allow an inner function to retain access to variables from its surrounding scope. They are useful for configuration, factories, and encapsulation.

Immutability does not mean that no data can ever change. It means that a particular value is replaced rather than modified in place. This makes state transitions easier to trace and can reduce defects in user interfaces.

Functional programming has limits. File access, network calls, logging, timers, and DOM updates are inherently side-effecting. A practical design isolates such effects at the boundaries and keeps core transformations as pure as reasonably possible.

10. Applied Case: A Modular Course Dashboard

Background

Consider an illustrative diploma-project application that displays a learner’s courses, calculates completion percentages, and labels courses as complete, active, or not started.

Problem

The initial implementation stores data in global variables, repeatedly formats dates, mutates course records during rendering, and duplicates filtering logic in several event handlers. These practices make testing and maintenance difficult.

Analysis

The data model contains learner and course records, so objects and arrays are appropriate. A course code is unique, so a Map can support direct lookup. Status calculation is a deterministic transformation and can be a pure function. Rendering and browser event handling are side effects and should remain in a user-interface module.

Approach

courseLogic.js:

export function completionPercent(completed, total) {
  if (!Number.isInteger(completed) || !Number.isInteger(total)) {
    throw new TypeError("Counts must be integers");
  }
  if (total <= 0 || completed < 0 || completed > total) {
    throw new RangeError("Invalid completion counts");
  }
  return Math.round((completed / total) * 100);
}

export function courseStatus(course) {
  if (course.completedLessons === course.totalLessons) return "complete";
  if (course.completedLessons > 0) return "active";
  return "not-started";
}

export function activeCourses(courses) {
  return courses
    .filter(course => courseStatus(course) === "active")
    .map(course => ({ ...course }));
}

courseStore.js:

export function createCourseStore(courses) {
  const byCode = new Map(courses.map(course => [course.code, course]));

  return {
    get(code) {
      return byCode.get(code);
    },
    all() {
      return [...byCode.values()].map(course => ({ ...course }));
    }
  };
}

app.js imports these functions, obtains data from an API, and updates the DOM using textContent. The logic module can be tested without a browser, while the store provides a clear access boundary.

Outcome

The application has fewer duplicated rules, more predictable state handling, and independently testable transformations. A change to the status definition is made in one location. The Map makes course-code lookup explicit, and copying records prevents a renderer from silently changing stored data.

Learning points

  • Data structures communicate design intent.
  • Pure functions are easier to test than functions tied to the DOM.
  • Modules establish boundaries and make dependencies visible.
  • Shallow copying is useful but must be understood when data is nested.
  • Validation belongs at public boundaries, especially when data originates from users or external services.

11. Challenges, Security, and Professional Practice

Common technical challenges

  • Unexpected mutation: sort, splice, and direct property assignment can change shared data.
  • Shallow-copy errors: spread syntax does not recursively copy nested structures.
  • Date ambiguity: local and UTC operations can produce different calendar results.
  • Invalid numeric data: values from forms are usually strings and require validation and conversion.
  • Overloaded modules: a file that handles fetching, business rules, rendering, and storage becomes difficult to test.
  • Excessive abstraction: unnecessary classes or utility layers can obscure simple logic.

Security and governance

Data structures do not make input trustworthy. Validate data received from forms, APIs, and storage. Treat JSON as data, not executable code. Avoid dynamic evaluation such as eval. Escape or safely insert user-generated content, and use secure transport and server-side authorisation for sensitive operations.

Do not store secrets in client-side JavaScript; browser code is visible to users. Be cautious with personally identifiable information in objects, logs, and browser storage. Data retention, access control, and privacy obligations may apply depending on the application and jurisdiction.

Testing and maintainability

Pure functions should be tested with normal, boundary, and invalid inputs. Examples include an empty array, a one-element array, zero lessons, maximum completion, malformed dates, and duplicate identifiers. Linters, formatters, type-checking tools, code review, and meaningful module names improve consistency. JSDoc or TypeScript can document expected object shapes when a project grows beyond a small script.

Contemporary Development

Modern JavaScript applications commonly use bundlers, package managers, automated tests, and static analysis. Frameworks may introduce reactive state systems, but the underlying ideas remain: represent data clearly, isolate effects, avoid uncontrolled mutation, and define module boundaries.

The ECMAScript language continues to evolve. Features such as private class fields, optional chaining, nullish coalescing, top-level await, and the Temporal proposal or API implementations address recurring development needs. Teams should verify browser and runtime support before adopting a feature and should document compatibility requirements.

Best-Practice Checklist

  • Choose a structure based on access and uniqueness requirements.
  • Prefer descriptive transformations with map, filter, and reduce when they improve clarity.
  • Supply initial values to reduce unless the non-empty constraint is deliberate and enforced.
  • Copy data before sorting or updating shared state.
  • Remember that object and array spread is shallow.
  • Use explicit, unambiguous timestamp formats.
  • Keep modules cohesive and exports minimal.
  • Separate pure domain logic from I/O and DOM effects.
  • Validate external input and avoid unsafe HTML insertion.
  • Test boundary cases and document assumptions.

Chapter Summary

JavaScript data structures provide different guarantees and operations. Strings represent immutable text; arrays represent ordered collections; objects represent named records; sets represent unique values; maps represent key–value associations; and dates represent time points that require careful treatment of time zones and formats. Destructuring improves access to structured values, while spread syntax supports shallow copying and controlled updates.

Array methods such as map, filter, find, some, every, and reduce express common transformations. Classes combine related state and behaviour, while ES modules create explicit boundaries between reusable units. Functional programming concepts—especially pure functions, immutability, higher-order functions, and separation of side effects—improve testability and predictability. Professional JavaScript development also requires input validation, security awareness, compatibility checking, and disciplined testing.

Key Terms

String: An immutable sequence of text characters.

Array: An ordered, zero-indexed collection.

Object: A collection of named properties and values.

Reference value: A value accessed through a reference to an object.

Mutation: A change made to an existing object or array.

Destructuring: Syntax for extracting values from arrays or object properties.

Spread syntax: Syntax using ... to expand iterable values or object properties.

Set: A collection that stores unique values.

Map: A collection of key–value pairs with keys of any value type.

Date: A JavaScript object representing a time point in milliseconds from the Unix epoch.

Class: A template for constructing objects with shared state and behaviour.

Module: A file with its own scope and explicit imports and exports.

Pure function: A function whose result depends only on its inputs and that has no observable side effects.

Higher-order function: A function that accepts or returns another function.

Closure: A function together with access to variables from its surrounding scope.

Immutability: The practice of replacing values rather than modifying shared values in place.

Side effect: An observable interaction with state or the outside world.

Shallow copy: A copy in which nested objects remain shared references.

Review Questions

Short Answer Questions

  1. What is the difference between an array and a Set?
  2. Why can using const still allow an object’s properties to change?
  3. What does destructuring do in JavaScript?
  4. Why is an initial accumulator value often supplied to reduce?
  5. What is the purpose of an ES module?

Long Answer Questions

  1. Explain the differences between objects and Maps, and give an appropriate use case for each.
  2. Describe mutation and immutability using array and object examples.
  3. Explain how dates and time zones can create defects in web applications.
  4. Discuss the responsibilities of a well-designed JavaScript class.
  5. Explain how functional programming techniques can improve the maintainability of a web application.

Analytical / Application Questions

  1. A shopping application must remove duplicate product tags, retrieve a product by SKU, and preserve the display order of products. Which data structures would you choose for each task, and why?
  2. A function sorts an array received from application state and later components observe that the original order has changed. Diagnose the problem and propose a safe implementation.
  3. Design a module structure for a course dashboard that receives API data, calculates completion status, and renders results. Identify which operations should be pure and which operations are side effects.

Multiple Choice Questions with Answers

Question 1

Which JavaScript structure is designed to store only unique values?

A. Array

B. Set

C. Object

D. Date

Correct Answer: B. Set

Question 2

What does the expression users.filter(user => user.active) return?

A. The first active user

B. A Boolean indicating whether every user is active

C. A new array containing active users

D. The original array with inactive users deleted

Correct Answer: C. A new array containing active users

Question 3

Which statement about object spread is correct?

A. It performs a deep copy of all nested objects

B. It creates a shallow copy and later properties override earlier ones

C. It prevents all future mutation of the copied object

D. It can only be used with arrays

Correct Answer: B. It creates a shallow copy and later properties override earlier ones

Question 4

Why should an explicit comparison function commonly be passed to sort() for numbers?

A. Without it, sort() always reverses the array

B. Without it, numbers are compared as strings

C. Without it, sort() returns a Set

D. Without it, the array becomes immutable

Correct Answer: B. Without it, numbers are compared as strings

Question 5

Which feature makes a module’s dependencies explicit?

A. Global variables

B. import and export statements

C. The var keyword

D. The Date constructor

Correct Answer: B. import and export statements

Question 6

What is a defining property of a pure function?

A. It must be a class method

B. It must modify its input

C. Its result depends only on its inputs and it has no observable side effects

D. It must perform a network request

Correct Answer: C. Its result depends only on its inputs and it has no observable side effects

Question 7

Which method returns the first array element satisfying a condition, or undefined if none does?

A. find

B. map

C. every

D. reduce

Correct Answer: A. find

Question 8

Why is textContent generally safer than assigning untrusted input to innerHTML?

A. It automatically sorts the input

B. It treats the input as text rather than interpreting it as HTML

C. It converts the input into a Map

D. It encrypts the input

Correct Answer: B. It treats the input as text rather than interpreting it as HTML

Question 9

What does a private class field such as #items provide?

A. A field accessible from every script automatically

B. A field restricted from direct external access through the class instance

C. A field that can contain only numbers

D. A field that is automatically persisted in a database

Correct Answer: B. A field restricted from direct external access through the class instance

Question 10

Which structure is generally most suitable for direct lookup by arbitrary keys such as product codes?

A. Map

B. Set

C. String

D. Date

Correct Answer: A. Map

Introduction to Web Technologies (HTML, CSS, JS)
Chapter 10 of 15