Practice Questions
1. What are two benefits of using modules to organize JavaScript code? How do modules help as applications grow in size?
Solution
Modules provide several benefits (any two of these):
- Encapsulation: Variables and functions defined in a module are private by default, preventing naming conflicts with other parts of the codebase.
- Reusability: Exported functionality can be imported and reused across multiple files.
- Maintainability: Splitting code into separate files, each with a focused purpose, makes it easier to understand, update, and debug.
As applications grow, having all code in a single file becomes unmanageable. Modules let you split code into logical units, each responsible for a specific piece of functionality.
2. How do you enable ES6 modules in a browser environment? How about in a Node.js environment?
Solution
In the browser, add the type="module" attribute to the script tag:
<script type="module" src="script.js"></script>
In Node.js, add "type": "module" to your package.json:
{
"type": "module"
}
Without these configurations, the browser treats the script as a regular (non-module) script, and Node.js uses CommonJS (require/module.exports) by default.
3. Show two different ways to export the following function and constant from a module:
const TAX_RATE = 0.07;
function calculateTax(amount) {
return amount * TAX_RATE;
}
Solution
Inline exports — place export before each declaration:
export const TAX_RATE = 0.07;
export function calculateTax(amount) {
return amount * TAX_RATE;
}
Export at the end of the file — declare everything first, then export:
const TAX_RATE = 0.07;
function calculateTax(amount) {
return amount * TAX_RATE;
}
export { TAX_RATE, calculateTax };
Both approaches are equivalent. The second makes it easy to see all exports in one place.
4. Given the following module, write the import statement(s) to use its exports. Then show how to import everything as a namespace called MathUtils.
// math-utils.js
export function add(a, b) {
return a + b;
}
export function subtract(a, b) {
return a - b;
}
export const PI = 3.14159;
Solution
Named imports:
import { add, subtract, PI } from "./math-utils.js";
console.log(add(2, 3)); // 5
console.log(PI); // 3.14159
Namespace import:
import * as MathUtils from "./math-utils.js";
console.log(MathUtils.add(2, 3)); // 5
console.log(MathUtils.PI); // 3.14159
5. What is the difference between a default export and a named export? When would you use each?
Solution
A named export requires curly braces when importing and the name must match (unless you use an alias). A module can have multiple named exports. Use named exports when a module provides several related utilities.
export function add(a, b) {
return a + b;
}
import { add } from "./math.js";
A default export is imported without curly braces, and you can give it any name. A module can have at most one default export. Use a default export when a module has one primary value (like a class or component).
export default class Account {
/* ... */
}
import Account from "./account.js"; // any name works
import BankAccount from "./account.js"; // this also works
6. Rewrite the following code so that Logger is a default export and LOG_LEVEL is a named export. Then write the import statement that brings in both.
// logger.js
const LOG_LEVEL = "debug";
class Logger {
log(message) {
console.log(`[${LOG_LEVEL}] ${message}`);
}
}
export { Logger, LOG_LEVEL };
Solution
Module with default and named export:
// logger.js
const LOG_LEVEL = "debug";
class Logger {
log(message) {
console.log(`[${LOG_LEVEL}] ${message}`);
}
}
export default Logger;
export { LOG_LEVEL };
Import statement combining both:
import Logger, { LOG_LEVEL } from "./logger.js";
The default export comes first (without braces), followed by named exports in braces.
7. Given the following module, write an import statement that renames deposit to addFunds and withdraw to removeFunds.
// account.js
export function deposit(amount) {
/* ... */
}
export function withdraw(amount) {
/* ... */
}
export function getBalance() {
/* ... */
}
Solution
Use the as keyword to create import aliases:
import {
deposit as addFunds,
withdraw as removeFunds,
getBalance,
} from "./account.js";
addFunds(100);
removeFunds(20);
console.log(getBalance());
8. A student writes the following code and is confused about why two accounts share the same balance. Explain the problem and how to fix it.
// account.js
let balance = 0;
export function deposit(amount) {
balance += amount;
}
export function getBalance() {
return balance;
}
// app.js
import { deposit, getBalance } from "./account.js";
deposit(100); // John's deposit
deposit(50); // Mary's deposit
console.log(getBalance()); // 150 — but John only deposited 100!
Solution
The problem is that a module is not a type. There is only one copy of the module’s state. The balance variable is shared by all the code that imports from account.js, so John’s deposit and Mary’s deposit both change the same variable.
To fix this, export a class so each user can have their own instance:
// account.js
export class Account {
#balance = 0;
deposit(amount) {
this.#balance += amount;
}
getBalance() {
return this.#balance;
}
}
// app.js
import { Account } from "./account.js";
const johnsAccount = new Account();
const marysAccount = new Account();
johnsAccount.deposit(100);
marysAccount.deposit(50);
console.log(johnsAccount.getBalance()); // 100
console.log(marysAccount.getBalance()); // 50
So, modules organize code into files, and classes define reusable types where each instance has its own state.
9. What is an IIFE? Write one that encapsulates a variable secret and logs it, without adding secret to the global scope.
Solution
An IIFE (Immediately Invoked Function Expression) is a function that is defined and immediately invoked. It was a common pattern for encapsulation before ES6 modules.
(function () {
const secret = "hidden value";
console.log(secret); // "hidden value"
})();
// secret is not accessible here
The IIFE has two parts:
- A function expression wrapped in parentheses:
(function () { ... }) - Immediate invocation:
()right after