Functions, Collections and Program StructureIntroductionAs JavaScript programs grow beyond a few lines, placing all instructions in one sequence becomes difficult to understand, test, and maintain. Functions allow developers to name and reuse behaviour; collections provide structured ways to store related data; and modules divide a program into manageable files. Together, these features form the foundation of maintainable client-side and server-side applications. This chapter examines JavaScript functions, parameter passing, return values, scope, closures, arrow functions, strings, arrays, objects, destructuring, common array methods, and modules. The emphasis is on practical programming decisions: choosing an appropriate data structure, avoiding unintended mutation, designing reusable functions, and organizing code so that its responsibilities are clear. Learning ObjectivesBy the end of this chapter, you should be able to: - Define and invoke function declarations, expressions, and arrow functions.
- Use parameters, default values, rest parameters, and return values.
- Explain lexical scope, block scope, closures, and common scope errors.
- Manipulate strings, arrays, and objects safely and effectively.
- Apply
map(), filter(), reduce(), find(), some(), and every(). - Use object and array destructuring, spread syntax, and computed properties.
- Divide application logic into ES modules using
export and import. - Select appropriate structures and design reusable, readable program logic.
1. Functions as Reusable BehaviourA function is a named or anonymous unit of instructions that can receive input, perform an operation, and optionally produce an output. Functions reduce duplication and give a program meaningful abstractions. Function declarationsfunction calculateTotal(price, quantity) {
return price * quantity;
}
const total = calculateTotal(12.5, 4); // 50The function has two parameters, price and quantity. When it is called, the supplied values are arguments. return ends the function and sends a value to the caller. A function without an explicit return produces undefined. Function declarations are hoisted: JavaScript makes the declaration available before its position in the source file during the creation of the execution context. Nevertheless, placing declarations before their use is usually clearer. Function expressionsA function can be stored in a variable: const formatName = function (first, last) {
return `${first} ${last}`.trim();
};Function expressions are not usable before the assignment has executed when the variable is declared with const or let. They are useful when a function is treated as data, for example when passed as a callback. Parameters and argumentsParameters may have default values: function greet(name = "Guest") {
return `Hello, ${name}`;
}A rest parameter gathers remaining arguments into an array. It must be the final parameter: function sum(...numbers) {
return numbers.reduce((total, number) => total + number, 0);
}JavaScript passes primitive values such as numbers, strings, booleans, null, undefined, bigint, and symbols by value. Objects, arrays, and functions are values that contain references to mutable data. Reassigning a parameter does not replace the caller's variable, but mutating an object through that parameter can affect the original object. function rename(user) {
user.name = "Updated"; // mutates the object
}
function replaceUser(user) {
user = { name: "New user" }; // changes only the local parameter
}This distinction is important when designing APIs. A function should document whether it mutates its inputs. In many applications, returning a new object or array is safer and easier to reason about. Return values and early returnsA function should normally perform one clearly defined task. Early returns can reduce nesting and make validation explicit: function getDiscount(price, member) {
if (price <= 0) return 0;
if (!member) return 0;
return price * 0.1;
}Returning a value does not display it. A caller must store, print, render, or otherwise use the result. 2. Arrow FunctionsArrow functions provide concise syntax and have important this behaviour: const square = number => number * number;
const add = (a, b) => a + b;
const describe = item => {
const label = item.name.toUpperCase();
return label;
};An expression-body arrow function returns the expression implicitly. A block-body arrow function requires an explicit return. Parentheses are optional for one parameter, but they are required for zero or multiple parameters. To return an object literal implicitly, wrap it in parentheses: const makeUser = (name, role) => ({ name, role });Arrow functions do not create their own this, arguments, or super. They capture this from the surrounding lexical context. This makes them useful as callbacks: class Timer {
start() {
setTimeout(() => {
console.log(this); // the Timer instance
}, 1000);
}
}An arrow function is not automatically better than a traditional function. Use a traditional method or function when dynamic this, a constructor, or the arguments object is required. 3. Scope, Hoisting, and ClosuresScope determines where an identifier can be accessed. JavaScript has global scope, function scope, and block scope. var is function-scoped and can be redeclared; it is generally avoided in modern code.let and const are block-scoped.- A
const binding cannot be reassigned, although an object or array held by that binding may still be mutable.
const settings = { theme: "light" };
settings.theme = "dark"; // allowed
// settings = {}; // not allowedJavaScript uses lexical scope: a function can access variables from the location where it was defined, not from where it is called. The scope chain searches the current scope and then enclosing scopes. ClosuresA closure is a function together with access to variables in its surrounding lexical environment. The inner function may continue to use those variables after the outer function has finished: function createCounter() {
let count = 0;
return () => {
count += 1;
return count;
};
}
const next = createCounter();
next(); // 1
next(); // 2The variable count is private to the closure. This supports encapsulation, factories, memoization, and event handlers. The retained environment also has a cost: unnecessary closures that remain referenced can keep data in memory. Event listeners and timers should be removed when no longer needed. A common historical problem involved var in loops because it did not create block scope. Modern let creates a new binding for each loop iteration: for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}
// 0, 1, 24. Strings and Text ProcessingStrings are immutable sequences of UTF-16 code units. String methods return new strings rather than changing the original value. const title = " JavaScript Functions ";
const clean = title.trim().toLowerCase();
const words = clean.split(" ");Useful operations include: | Operation | Example | Purpose |
|---|
trim() | text.trim() | Removes leading and trailing whitespace | includes() | text.includes("JS") | Tests for a substring | startsWith() | url.startsWith("https") | Tests a prefix | slice() | text.slice(0, 5) | Extracts part of a string | replace() | text.replace("old", "new") | Replaces a match | split() | text.split(",") | Creates an array | join() | items.join(", ") | Creates a string from an array |
Template literals use backticks and ${...} interpolation. Interpolated values should still be escaped or handled safely when inserted into HTML. Assigning untrusted text with innerHTML can create cross-site scripting vulnerabilities; use textContent for plain text unless HTML has been deliberately sanitized. 5. Arrays and ObjectsAn array is an ordered collection indexed from zero. It may contain mixed types, although consistent element types make programs easier to understand. An object is a collection of key-value properties and is often used to represent an entity. const student = {
id: 101,
name: "Amina",
skills: ["HTML", "CSS", "JavaScript"]
};
student.name;
student["name"];Use dot notation for known, valid identifier-like property names and bracket notation for dynamic keys: const field = "email";
const profile = { email: "[email protected]" };
console.log(profile[field]);Objects and arrays are mutable. The spread syntax creates a shallow copy: const updatedStudent = { ...student, name: "Amina Khan" };
const newSkills = [...student.skills, "Testing"];A shallow copy does not clone nested objects. For deeply nested data, use a deliberate strategy such as structured cloning where supported, or design data updates to avoid unnecessary deep mutation. DestructuringDestructuring extracts values into variables: const { name, id: studentId } = student;
const [firstSkill, secondSkill] = student.skills;It can use defaults and rest syntax: const { name = "Unknown", ...details } = student;
const [first, ...remaining] = student.skills;Destructuring is especially useful for function parameters: function displayStudent({ name, skills = [] }) {
return `${name}: ${skills.join(", ")}`;
}Optional chaining and nullish coalescingOptional chaining prevents an error when an intermediate value is null or undefined: const city = user.address?.city; Nullish coalescing supplies a fallback only for null or undefined, unlike ||, which also treats values such as 0, false, and an empty string as false: const pageSize = settings.pageSize ?? 20; 6. Common Array MethodsArray methods express common transformations more clearly than manual loops. const products = [
{ name: "Keyboard", price: 40, active: true },
{ name: "Mouse", price: 25, active: false },
{ name: "Monitor", price: 180, active: true }
];
const activeNames = products
.filter(product => product.active)
.map(product => product.name);Important methods include: forEach(callback): performs an action for every element; it returns undefined.map(callback): returns a new array containing one transformed value per element.filter(callback): returns elements for which the callback is truthy.find(callback): returns the first matching element or undefined.findIndex(callback): returns the first matching index or -1.some(callback): returns true if at least one element matches.every(callback): returns true if all elements match; for an empty array, it returns true because no counterexample exists.reduce(callback, initialValue): combines elements into one accumulated result.sort(compareFunction): sorts in place and therefore mutates the array.
A reliable reduce() pattern supplies an initial value: const prices = [10, 20, 5];
const total = prices.reduce((sum, price) => sum + price, 0); // 35 Without a suitable initial value, the first array element becomes the accumulator, which can cause type errors or unexpected results. The callback receives the accumulator, current value, index, and array. The accumulator may be a number, string, array, or object. By default, sort() compares values as strings. Numeric sorting requires a comparator: const scores = [9, 80, 12];
scores.sort((a, b) => a - b); // [9, 12, 80] For non-mutating style, copy first: const ordered = [...scores].sort((a, b) => a - b);. 7. Program Structure and ModulesA well-structured program separates responsibilities. A user-interface module may render data, a validation module may check input, and a service module may communicate with an API. Each module should expose a small, intentional public interface. Named and default exportsmath.js:
export function average(values) {
if (values.length === 0) return 0;
return values.reduce((sum, value) => sum + value, 0) / values.length;
}
export const TAX_RATE = 0.2;report.js:
import { average, TAX_RATE } from "./math.js";
export default function createReport(values) {
return {
average: average(values),
taxRate: TAX_RATE
};
}Named exports are useful when a file provides several related capabilities. A default export represents the principal value of a module. Imports should use explicit paths, and browser modules are normally loaded with <script type="module" src="main.js"></script>. Modules have their own scope, so variables are not automatically global. They are also loaded asynchronously by browsers and are subject to same-origin and server configuration rules. In professional applications, a bundler or build tool may combine modules, optimize assets, and provide development support, but the underlying import and export concepts remain the same. Avoid circular dependencies when possible. If module A imports B and B imports A, initialization order can produce incomplete bindings or difficult-to-diagnose behaviour. A shared third module or clearer dependency direction is often better. 8. Worked Application: Course Progress DashboardThe following illustrative case demonstrates functions, collections, immutability, destructuring, and modular design. Background and problemA training platform stores course records. It must display each learner's completed courses, calculate progress, and identify learners who need support. The initial code duplicates calculations in several event handlers and directly mutates records received from an API. AnalysisThe data consists of an array of learner objects. Each learner contains an array of course objects. Progress is derived data and should be calculated by pure functions rather than stored redundantly. Approach// progress.js
export const completionRate = courses => {
if (courses.length === 0) return 0;
const completed = courses.filter(course => course.completed).length;
return completed / courses.length;
};
export const addProgress = learner => ({
...learner,
progress: completionRate(learner.courses)
});
export const learnersNeedingSupport = (learners, threshold = 0.5) =>
learners
.map(addProgress)
.filter(({ progress }) => progress < threshold);// dashboard.js
import { addProgress, learnersNeedingSupport } from "./progress.js";
const learners = [
{
name: "Lina",
courses: [
{ title: "HTML", completed: true },
{ title: "CSS", completed: true },
{ title: "JavaScript", completed: false }
]
},
{
name: "Omar",
courses: [
{ title: "HTML", completed: false },
{ title: "CSS", completed: false }
]
}
];
const progressRecords = learners.map(addProgress);
const supportList = learnersNeedingSupport(learners);
for (const { name, progress } of progressRecords) {
console.log(`${name}: ${Math.round(progress * 100)}%`);
}OutcomeLina receives a progress value of 2 ÷ 3, approximately 66.7%, while Omar receives 0 ÷ 2, or 0%. With a threshold of 0.5, only Omar appears on the support list. The functions do not change the original learner records; each enriched learner is a new object. Learning pointsThe case illustrates separation of calculation from presentation, default parameters, array pipelines, destructuring, named exports, and a non-mutating update strategy. In a production system, input validation, tests, loading states, error handling, accessibility, and authorization would also be required. 9. Quality, Security, and Professional PracticeReadable program structure is a technical quality issue, not merely a matter of style. Useful practices include: - Give functions one coherent responsibility.
- Use descriptive names and keep functions small enough to test.
- Prefer
const; use let only when reassignment is necessary. - Avoid hidden mutation, especially of data received from another module or API.
- Validate external input before processing it.
- Use
textContent for untrusted text and avoid unsafe HTML construction. - Handle empty arrays, missing properties, invalid numbers, and failed requests.
- Write unit tests for pure functions such as transformations and validators.
- Document exported functions, including expected inputs, outputs, and mutation behaviour.
- Keep module dependencies simple and directional.
AI-assisted coding tools can generate plausible functions, but generated code must be reviewed. Developers remain responsible for checking edge cases, security, licensing constraints, accessibility, and whether the code actually matches the application's data model. An AI suggestion that uses innerHTML, mutates shared state, or assumes trusted input may be unsuitable even if it appears to work in a demonstration. 10. Limitations and Common ErrorsSeveral mistakes recur in beginner and professional code: - Confusing
map() with forEach(): map() creates a result array; forEach() does not. - Forgetting that
sort() mutates its receiver and compares as strings by default. - Calling a function instead of passing it as a callback: use
button.addEventListener("click", handleClick), not handleClick(). - Expecting
const to make nested objects immutable. - Using
|| when 0 or false are valid values; use ?? when appropriate. - Returning an object from an arrow function without parentheses.
- Relying on global variables, which creates hidden coupling.
- Ignoring closure retention in long-lived event handlers.
- Creating circular module dependencies.
Understanding these limitations helps developers select the right construct rather than applying a fashionable syntax mechanically. Chapter SummaryFunctions package reusable behaviour and may accept parameters, return values, and callback functions. Function declarations, expressions, and arrow functions differ in syntax and execution behaviour; arrow functions capture lexical this. Scope controls identifier visibility, while closures preserve access to surrounding variables and support encapsulation. Strings are immutable and provide methods for searching, extracting, splitting, and formatting text. Arrays represent ordered collections and can be transformed with map(), filter(), reduce(), find(), some(), and every(). Objects model related properties, while destructuring, spread syntax, optional chaining, and nullish coalescing make data access and copying more expressive. Since copying is often shallow, developers must understand nested mutation. ES modules provide file-level scope and explicit dependencies through export and import. Combining pure functions, predictable data transformations, careful validation, and well-designed modules produces software that is easier to test, secure, and maintain. Key TermsFunction: A reusable unit of executable behaviour. Parameter: A named input declared by a function. Argument: A value supplied when a function is called. Return value: The result sent from a function to its caller. Callback: A function passed to another function for later execution. Arrow function: A concise function syntax with lexical this. Scope: The region in which an identifier can be accessed. Block scope: Scope created by a pair of braces and used by let and const. Closure: A function together with access to its lexical environment. String: An immutable sequence of text characters represented by JavaScript string values. Array: An ordered, index-based collection. Object: A collection of key-value properties. Destructuring: Syntax for extracting values from arrays or objects. Spread syntax: Syntax that expands iterable elements or object properties. Mutation: A change made to an existing object or array. Pure function: A function whose result depends only on its inputs and which has no observable side effects. Module: A file with its own scope and explicitly exported or imported values. Named export: An explicitly named value exported from a module. Default export: The principal exported value of a module. Accumulator: The running result used by reduce(). Review QuestionsShort Answer Questions- What is the difference between a parameter and an argument?
- Why does an arrow function require an explicit
return when its body uses braces? - What is a closure, and how can it provide private state?
- What is the difference between
map() and forEach()? - Why might a developer use
?? instead of ||?
Long Answer Questions- Explain JavaScript scope, including global, function, and block scope, and discuss why
let and const are generally preferred to var. - Compare function declarations, function expressions, and arrow functions. Include their syntax, hoisting considerations, and
this behaviour. - Explain how objects and arrays are copied with spread syntax, including the limitations of shallow copying.
- Describe how
map(), filter(), and reduce() can be combined to process a collection. Give a practical example. - Explain how ES modules improve program structure and identify risks associated with circular dependencies.
Analytical / Application Questions- Design a function that receives an array of product objects and returns the names of active products costing less than 100. State whether your function mutates the input and justify your choice.
- A click handler reports the same final loop index for every button. Diagnose the likely scope problem and propose a modern JavaScript solution.
- Refactor a single large dashboard script into at least three modules. Define the responsibility and exported functions of each module, and explain how data should flow between them.
Multiple Choice Questions with AnswersQuestion 1Which statement about a function's return value is correct? A. Every function must return a string. B. A function without an explicit return produces undefined. C. The return statement displays the result automatically. D. A return statement can only return numbers. Correct Answer: B. A function without an explicit return produces undefined. Question 2What does this expression produce: const result = [1, 2, 3].map(n => n * 2);? A. The original array is changed to [2, 4, 6]. B. The value is undefined. C. A new array [2, 4, 6] is produced. D. A single number 6 is produced. Correct Answer: C. A new array [2, 4, 6] is produced. Question 3Which feature allows a returned function to retain access to a variable declared in its outer function? A. Destructuring B. Closure C. Template literal D. Module default export Correct Answer: B. Closure Question 4Which statement about sort() is correct? A. It always returns a new, non-mutated array. B. It compares numbers numerically by default. C. It mutates the array and compares values as strings by default. D. It can only sort arrays of strings. Correct Answer: C. It mutates the array and compares values as strings by default. Question 5What does user.address?.city do if user.address is undefined? A. It throws a syntax error. B. It returns undefined instead of attempting the property access. C. It creates an empty address object. D. It returns the string "undefined". Correct Answer: B. It returns undefined instead of attempting the property access. Question 6Which syntax correctly creates a new object with all properties of user and an updated role? A. { user, role: "admin" } B. { ...user, role: "admin" } C. [...user, role: "admin"] D. user + { role: "admin" } Correct Answer: B. { ...user, role: "admin" } Question 7Which array method returns the first element satisfying a condition, or undefined if none does? A. filter() B. some() C. find() D. every() Correct Answer: C. find() Question 8What is the main purpose of export and import in JavaScript? A. To convert strings into arrays B. To define and use values across module files C. To make all variables global D. To prevent functions from returning values Correct Answer: B. To define and use values across module files Question 9What does const total = [10, 20, 5].reduce((sum, value) => sum + value, 0); produce? A. 5 B. 20 C. 30 D. 35 Correct Answer: D. 35 Question 10Which statement accurately describes an arrow function? A. It always creates its own dynamic this. B. It cannot be assigned to a variable. C. It captures this lexically from its surrounding context. D. It must contain a block body. Correct Answer: C. It captures this lexically from its surrounding context.
|