Module 01: Getting Started with JavaScript
Learning Focus: JavaScript fundamentals, arrays, objects, functions, and real-world problem solving (Canadian tax calculator)
Table of Contents
- Module Overview
- Core Concepts
- Code Walkthrough
- Testing Strategy
- Bugs Fixed
- 2021 vs 2025 Comparison
- Key Takeaways
Module Overview
What We're Building
This module covers JavaScript fundamentals through practical exercises:
- Tax Calculator: Calculate Canadian federal income tax
- Array Operations: Manipulation, filtering, summing
- Dictionary/Object Handling: Key-value pair operations
- Function Patterns: Pure functions, callbacks, higher-order functions
- Calculator: Basic arithmetic operations
Why These Topics Matter
Moving beyond "hello world," we tackle real-world problems:
- Tax calculation teaches conditional logic and bracket systems
- Arrays teach iteration patterns (map, filter, reduce)
- Objects teach data modeling
- Functions teach code organization and reusability
Core Concepts
1. Canadian Tax Brackets (2021)
Canada uses a progressive tax system with multiple brackets:
const TAX_BRACKETS = [
{ max: 49020, rate: 0.15 }, // 15% on first $49,020
{ max: 98040, rate: 0.205 }, // 20.5% on next chunk
{ max: 151978, rate: 0.26 }, // 26% on next chunk
{ max: 216511, rate: 0.29 }, // 29% on next chunk
{ max: Infinity, rate: 0.33 } // 33% on remainder
];
How Progressive Taxation Works:
Income: $60,000
Bracket 1: $49,020 Γ 15% = $7,353.00
Bracket 2: $10,980 Γ 20.5% = $2,250.90
Total = $9,603.90
You DON'T pay 20.5% on the entire $60,000βonly on the amount over $49,020.
2. Array Methods (The Holy Trinity)
const numbers = [1, 2, 3, 4, 5];
// MAP: Transform each element
const doubled = numbers.map(n => n * 2);
// Result: [2, 4, 6, 8, 10]
// FILTER: Keep elements that pass test
const evens = numbers.filter(n => n % 2 === 0);
// Result: [2, 4]
// REDUCE: Combine elements into single value
const sum = numbers.reduce((total, n) => total + n, 0);
// Result: 15
Why these matter:
- Map: Transform data (e.g., convert prices to USD)
- Filter: Search/query (e.g., active users only)
- Reduce: Aggregation (e.g., total sales)
3. Pure Functions
// β IMPURE: Modifies external state
let total = 0;
function addToTotal(n) {
total += n; // Side effect!
return total;
}
// β
PURE: No side effects, same input = same output
function add(a, b) {
return a + b; // Predictable!
}
Pure functions are:
- Easier to test
- Easier to debug
- Easier to reason about
- Safe to parallelize
Code Walkthrough
File Structure
01-getting-started/
βββ src/
β βββ scripts/
β β βββ array.js # Array operations
β β βββ array.test.js # Array tests
β β βββ calculator.js # Basic math
β β βββ calculator.test.js
β β βββ dict.js # Object/dictionary ops
β β βββ dict.test.js
β β βββ functions.js # Function exercises
β β βββ functions.test.js
β β βββ syntax.js # JS syntax basics
β β βββ syntax.test.js
β β βββ tax.js # β Tax calculator
β β βββ tax.test.js # β Tax tests
β βββ index.html
βββ package.json
tax.js - The Tax Calculator (Most Complex)
const TAX = [
[49020, 0.15],
[98040, 0.205],
[151978, 0.26],
[216511, 0.29],
[Infinity, 0.33]
];
const taxCalc = {
// Calculate tax for a specific bracket
calcTaxForBracket: (previousMax, currentMax, rate, income) => {
if (income <= previousMax) {
return 0; // Income doesn't reach this bracket
}
// How much income falls in this bracket?
const taxableInThisBracket = Math.min(income, currentMax) - previousMax;
return taxableInThisBracket * rate;
},
// Calculate total tax across all brackets
calcTax: (income) => {
let totalTax = 0;
let previousMax = 0;
// Iterate through each bracket
for (let i = 0; i < TAX.length; i++) {
const [currentMax, rate] = TAX[i];
const taxForBracket = taxCalc.calcTaxForBracket(
previousMax,
currentMax,
rate,
income
);
totalTax += taxForBracket;
previousMax = currentMax;
// Stop if we've exceeded income
if (income <= currentMax) break;
}
return totalTax;
},
// Calculate after-tax income
calcTaxWithRemainder: (income) => {
const tax = taxCalc.calcTax(income);
return {
tax: tax,
afterTax: income - tax
};
}
};
export default taxCalc;
Key Design Decisions:
Why separate
calcTaxForBracket?- Single Responsibility Principle
- Easier to test individual brackets
- Reusable logic
Why use array destructuring?
const [currentMax, rate] = TAX[i]; // Instead of: const currentMax = TAX[i][0]; const rate = TAX[i][1];Cleaner, more readable code.
Why
Math.min(income, currentMax)?- Handles the case where income is in the middle of a bracket
- Example: Income 55,β000inbracket[49,020 - $98,040]
- Only $5,980 is taxed at 20.5%, not the full bracket range
array.js - Array Operations
const arrayFunctions = {
// Add element to array and return it
addElement: (arr, element) => {
arr.push(element);
return arr; // β οΈ BUG WAS HERE: Originally didn't return
},
// Sum all numbers in array
sumArray: (arr) => {
return arr.reduce((total, element) => {
return total + element;
}, 0);
// β οΈ BUG WAS HERE: Used global `arr` instead of parameter
},
// Remove element at specific index
removeElement: (arr, index) => {
arr.splice(index, 1);
return arr;
}
};
Bug Story: The Global Variable Mistake
Original broken code:
let arr = []; // Global variable
sumArray: (element) => {
return arr.reduce((total, num) => total + num, 0);
// β Uses global `arr`, not the parameter!
}
Why this failed:
sumArray([1, 2, 3]); // Returns 0 (global arr is empty)
sumArray([5, 5]); // Still returns 0!
The fix:
sumArray: (arr) => { // β
Use parameter name
return arr.reduce((total, element) => total + element, 0);
}
dict.js - Dictionary/Object Operations
const dictFunctions = {
// Loop 1: Return value by key
returnTheSecondtItemByKey: (obj) => {
return obj[Object.keys(obj)[1]];
},
// Loop 2: Return value when key matches condition
returnTheThirdItemByValue: (obj) => {
for (const key in obj) {
if (obj[key] === key) { // β οΈ BUG WAS HERE
return obj[key];
}
}
return "error!";
}
};
Bug Story: The Always-Returning-Error Loop
Original broken code:
returnTheThirdItemByValue: (obj) => {
for (const key in obj) {
if (obj[key] === key) {
return obj[key];
}
return "error!"; // β WRONG! Returns on first iteration
}
}
Why this failed:
- The
return "error!"was inside the loop - It would execute on the first iteration if the condition failed
- The loop never got a chance to check other keys
The fix:
returnTheThirdItemByValue: (obj) => {
for (const key in obj) {
if (obj[key] === key) {
return obj[key];
}
}
return "error!"; // β
Outside loop, runs only if nothing found
}
Testing Strategy
tax.test.js - Tax Calculator Tests
import taxCalc from "./tax.js";
test("Testing the function that calculates tax for a single bracket", () => {
// Test bracket 1: $49,020 at 15%
expect(taxCalc.calcTaxForBracket(0, 49020, 0.15, 30000))
.toBeCloseTo(4500, 1); // $30,000 Γ 15% = $4,500
// Test bracket 2: Income partially in bracket
expect(taxCalc.calcTaxForBracket(49020, 98040, 0.205, 60000))
.toBeCloseTo(2250.9, 1); // $10,980 Γ 20.5% = $2,250.90
});
test("Testing the function that calculates total tax", () => {
// Income: $60,000
// Bracket 1: $49,020 Γ 15% = $7,353.00
// Bracket 2: $10,980 Γ 20.5% = $2,250.90
// Total: $9,603.90
expect(taxCalc.calcTax(60000)).toBeCloseTo(9603.9, 1);
// Edge case: Exactly at bracket boundary
expect(taxCalc.calcTax(49020)).toBeCloseTo(7353, 1);
});
test("Testing the function that calculates after-tax income", () => {
const result = taxCalc.calcTaxWithRemainder(60000);
expect(result.tax).toBeCloseTo(9603.9, 1);
expect(result.afterTax).toBeCloseTo(50396.1, 1);
});
Why toBeCloseTo() instead of
toBe()?
JavaScript floating-point arithmetic isn't exact:
0.1 + 0.2 === 0.3 // false! (actually 0.30000000000000004)
toBeCloseTo(value, decimals) handles this:
expect(0.1 + 0.2).toBeCloseTo(0.3, 10); // β
Passes
array.test.js - Array Tests
import arrayFunctions from "./array.js";
test("Does the addElement function work?", () => {
const arr = [1, 2, 3];
const result = arrayFunctions.addElement(arr, 4);
expect(result).toEqual([1, 2, 3, 4]);
expect(result.length).toBe(4);
});
test("Does the sumArray function work?", () => {
expect(arrayFunctions.sumArray([1, 2, 3])).toBe(6);
expect(arrayFunctions.sumArray([0])).toBe(0);
expect(arrayFunctions.sumArray([-1, 1])).toBe(0);
});
Bugs Fixed
During the 2025 audit, I discovered and fixed 5 critical bugs in this module:
Bug #1: Tax Array Indexing
// β WRONG: Incorrectly accessing tax bracket data
const previousMax = TAX[i - 1][1]; // Gets RATE instead of MAX
// β
CORRECT: Access the right index
const previousMax = TAX[i - 1][0]; // Gets MAX value
Impact: Calculated wrong tax amounts for all brackets after the first.
Bug #2: Tax Income Calculation
// β WRONG: Used wrong variable names
const total_tax_income = Math.min(income, P[1]) - P[0];
// β
CORRECT: Use consistent naming
const total_tax_income = Math.min(income, TAX[i][0]) - previousMax;
Impact: Logic errors in else-if blocks caused incorrect tax calculations.
Bug #3: Dictionary Loop Returns Early
// β WRONG: Always returns "error!" on first iteration
for (const key in obj) {
if (condition) return value;
return "error!"; // Inside loop!
}
// β
CORRECT: Return after checking all keys
for (const key in obj) {
if (condition) return value;
}
return "error!"; // Outside loop
Impact: Function never checked beyond first key.
Bug #4: Array Function Doesn't Return
// β WRONG: Modifies array but doesn't return it
addElement: (arr, element) => {
arr.push(element);
// Missing return statement!
}
// β
CORRECT: Return the modified array
addElement: (arr, element) => {
arr.push(element);
return arr;
}
Impact: Chaining array operations failed.
Bug #5: sumArray Uses Wrong Variable
// β WRONG: Uses global `arr` instead of parameter
sumArray: (element) => {
return arr.reduce((total, num) => total + num);
}
// β
CORRECT: Use function parameter
sumArray: (arr) => {
return arr.reduce((total, element) => total + element, 0);
}
Impact: Always returned 0 or crashed.
2021 vs 2025 Comparison
Coding Style Evolution
2021: Verbose, Imperative
// Old style - manual loops
function sumArray(arr) {
let total = 0;
for (let i = 0; i < arr.length; i++) {
total = total + arr[i];
}
return total;
}
2025: Concise, Declarative
// Modern style - array methods
const sumArray = arr => arr.reduce((total, n) => total + n, 0);
Tax Calculator Comparison
2021 Approach:
// Lots of if-else statements
function calcTax(income) {
if (income <= 49020) {
return income * 0.15;
} else if (income <= 98040) {
return 49020 * 0.15 + (income - 49020) * 0.205;
} else if (income <= 151978) {
return 49020 * 0.15 + 49020 * 0.205 + (income - 98040) * 0.26;
}
// ... more nested conditions
}
Problems:
- Hard to maintain
- Repetitive calculations
- Difficult to add new brackets
- Error-prone
2025 Approach:
// Data-driven with iteration
const TAX = [[49020, 0.15], [98040, 0.205], ...];
function calcTax(income) {
let total = 0;
let prev = 0;
for (const [max, rate] of TAX) {
if (income <= prev) break;
total += Math.min(income, max) - prev) * rate;
prev = max;
}
return total;
}
Benefits:
- β Easy to update tax brackets (just change data)
- β Single source of truth
- β No code duplication
- β Scales to any number of brackets
Testing Philosophy
2021: Manual Console Testing
// Old way - manual verification
console.log(taxCalc.calcTax(60000)); // Eyeball the result
console.log(taxCalc.calcTax(100000)); // Check each case manually
2025: Automated Test Suite
// Modern way - automated verification
test("calculates tax correctly", () => {
expect(taxCalc.calcTax(60000)).toBeCloseTo(9603.9, 1);
expect(taxCalc.calcTax(100000)).toBeCloseTo(17991.9, 1);
expect(taxCalc.calcTax(49020)).toBeCloseTo(7353, 1);
});
Key Takeaways
Technical Skills Gained
- Progressive Systems: Understanding stepped calculations (taxes, shipping tiers)
- Array Mastery: map(), filter(), reduce() for data transformation
- Object Manipulation: Iterating, accessing, transforming key-value pairs
- Pure Functions: Writing predictable, testable code
- Edge Case Handling: Boundary values, empty arrays, negative numbers
Problem-Solving Patterns
Pattern 1: Accumulation
// Start with initial value, build up result
const sum = numbers.reduce((acc, n) => acc + n, 0);
const product = numbers.reduce((acc, n) => acc * n, 1);
Pattern 2: Transformation
// Convert each item to new form
const doubled = numbers.map(n => n * 2);
const names = users.map(user => user.name);
Pattern 3: Filtering
// Keep only items that pass test
const evens = numbers.filter(n => n % 2 === 0);
const adults = users.filter(user => user.age >= 18);
Common Pitfalls Avoided
- Off-by-one errors in array indexing
- Mutating vs returning new values
- Global state pollution
- Early returns in loops
- Floating-point comparison issues
Further Learning
Practice Exercises
Tax Calculator Extensions:
- Add provincial tax rates
- Calculate marginal vs effective tax rate
- Handle different tax years
Array Challenges:
- Find median value
- Remove duplicates
- Flatten nested arrays
Object Operations:
- Deep clone objects
- Merge multiple objects
- Transform object shape
Recommended Resources
Real-World Applications
These patterns appear everywhere:
- E-commerce: Calculate shipping costs, apply discounts
- Finance: Interest calculations, loan amortization
- Analytics: Aggregate user data, generate reports
- Forms: Validate inputs, transform data for API
Module Status: β
Complete (8/8 tests passing)
Key Bugs Fixed: 5 (tax indexing, loop logic, array
operations)
Time Investment: ~4 hours (including debugging)
Next Module: Module 02: DOM
Manipulation β