Arithmetic and Assignment Operators

Understanding the building blocks of JavaScript calculations and variable manipulation

Introduction to Operators

Operators are symbols that perform operations on variables and values. JavaScript provides several categories of operators:

Today, we'll focus on arithmetic and assignment operators, which form the foundation of data manipulation in JavaScript.

graph TD A[JavaScript Operators] A --> B[Arithmetic Operators] A --> C[Assignment Operators] A --> D[Comparison Operators] A --> E[Logical Operators] A --> F[Bitwise Operators] A --> G[String Operators] A --> H[Other Operators] B --> B1[+, -, *, /, %] B --> B2[**, ++, --] C --> C1[=] C --> C2[+=, -=, *=, /=] C --> C3[%=, **=, etc.] H --> H1[Conditional] H --> H2[Type] H --> H3[Nullish]

Analogy: Operators as Kitchen Tools

Think of operators like kitchen tools: just as you might use a mixer to combine ingredients, a knife to cut them, or an oven to transform them, JavaScript operators let you manipulate and transform data in various ways. Just as different kitchen tools are designed for specific tasks (you wouldn't use a blender to cook pasta), different operators are optimized for specific operations on your data.

Arithmetic Operators

Arithmetic operators perform mathematical operations between numeric operands.

Basic Arithmetic Operators

Operator Name Description Example
+ Addition Adds numbers 5 + 3 = 8
- Subtraction Subtracts numbers 5 - 3 = 2
* Multiplication Multiplies numbers 5 * 3 = 15
/ Division Divides numbers 6 / 3 = 2
% Modulus (Remainder) Returns division remainder 5 % 2 = 1
** Exponentiation Raises to power (ES2016) 2 ** 3 = 8
// Basic arithmetic
let a = 10;
let b = 3;

console.log(a + b);  // 13
console.log(a - b);  // 7
console.log(a * b);  // 30
console.log(a / b);  // 3.3333...
console.log(a % b);  // 1
console.log(a ** b); // 1000 (10^3)

The Modulus (%) Operator

The modulus operator returns the remainder after division and is particularly useful for:

// Checking if a number is even or odd
function isEven(num) {
  return num % 2 === 0;
}

console.log(isEven(4)); // true
console.log(isEven(7)); // false

// Cycling through values (e.g., for circular data structures)
function getHourHandPosition(hour) {
  // 12-hour clock wraps around using modulo
  return hour % 12;
}

console.log(getHourHandPosition(3));  // 3
console.log(getHourHandPosition(15)); // 3
console.log(getHourHandPosition(25)); // 1

Exponentiation Operator

The exponentiation operator (**) raises the first operand to the power of the second operand.

// Exponentiation examples
console.log(2 ** 3);    // 8 (2 raised to power 3)
console.log(10 ** -2);  // 0.01 (same as 1/100)
console.log(2 ** 0.5);  // 1.4142... (square root of 2)

// Before ES2016, you'd use Math.pow()
console.log(Math.pow(2, 3)); // 8

Real-World Example: Financial Calculations

Arithmetic operators are essential for financial applications:

// Calculate compound interest
function calculateCompoundInterest(principal, rate, time, n) {
  // A = P(1 + r/n)^(nt)
  const amount = principal * (1 + rate / n) ** (n * time);
  return amount;
}

// Initial investment of $1000 at 5% interest, compounded quarterly for 10 years
const investment = calculateCompoundInterest(1000, 0.05, 10, 4);
console.log(`Final amount: $${investment.toFixed(2)}`); // $1643.62

// Calculate monthly mortgage payment
function calculateMortgagePayment(principal, annualRate, years) {
  const monthlyRate = annualRate / 12;
  const payments = years * 12;
  
  // Monthly payment formula: P * r * (1 + r)^n / ((1 + r)^n - 1)
  const payment = principal * monthlyRate * (1 + monthlyRate) ** payments / 
                 ((1 + monthlyRate) ** payments - 1);
  
  return payment;
}

// $250,000 loan at 3.5% for 30 years
const monthlyPayment = calculateMortgagePayment(250000, 0.035, 30);
console.log(`Monthly payment: $${monthlyPayment.toFixed(2)}`); // $1122.61

Increment and Decrement Operators

The increment (++) and decrement (--) operators increase or decrease a value by 1. They come in two forms: prefix and postfix.

Prefix Increment/Decrement

When used as a prefix (++x, --x), the value is changed before the expression is evaluated.

let a = 5;
let b = ++a; // Increment a first, then assign to b

console.log(a); // 6
console.log(b); // 6

let c = 10;
let d = --c; // Decrement c first, then assign to d

console.log(c); // 9
console.log(d); // 9

Postfix Increment/Decrement

When used as a postfix (x++, x--), the value is changed after the expression is evaluated.

let a = 5;
let b = a++; // Assign a to b first, then increment a

console.log(a); // 6
console.log(b); // 5

let c = 10;
let d = c--; // Assign c to d first, then decrement c

console.log(c); // 9
console.log(d); // 10
sequenceDiagram participant X as Variable x participant Y as Variable y Note over X, Y: Prefix increment: y = ++x X->>X: Increment x by 1 X->>Y: Assign new value of x to y Note over X, Y: Postfix increment: y = x++ X->>Y: Assign current value of x to y X->>X: Increment x by 1

Practical Use of Increment/Decrement

// Common use in loops
for (let i = 0; i < 5; i++) {
  console.log(i); // 0, 1, 2, 3, 4
}

// Be careful with expressions
let x = 3;
let y = x++ + 2; // y = 3 + 2, then x becomes 4
console.log(x); // 4
console.log(y); // 5

let a = 3;
let b = ++a + 2; // a becomes 4, then b = 4 + 2
console.log(a); // 4
console.log(b); // 6

Analogy: Prefix vs. Postfix Operators

Think of prefix and postfix operators like taking a number from a ticket dispenser at a deli counter:

  • Prefix increment (++x) is like taking a ticket and immediately moving to the next number before anyone sees your ticket number.
  • Postfix increment (x++) is like showing your current ticket number to someone, and then taking a new ticket for yourself.

In both cases, you end up with the next number, but the difference is whether someone else sees the old number or the new one.

Unary Operators

Unary operators work with just one operand.

Unary Plus and Minus

// Unary plus (+) attempts to convert to a number
console.log(+42);      // 42 (no change)
console.log(+"42");    // 42 (string to number)
console.log(+true);    // 1 (boolean to number)
console.log(+false);   // 0 (boolean to number)
console.log(+"hello"); // NaN (cannot convert)

// Unary minus (-) negates value after conversion
console.log(-42);      // -42
console.log(-"42");    // -42 (string to number, then negate)
console.log(-true);    // -1
console.log(-false);   // -0 (JavaScript has signed zero)

Unary Plus vs. Number()

Unary plus is a shorthand for Number() conversion, but with some nuanced differences:

// Converting to number
let str = "42";
let num1 = +str;         // Using unary plus
let num2 = Number(str);  // Using Number() constructor

console.log(num1);       // 42
console.log(num2);       // 42

// Differences in error handling
let obj = { valueOf() { throw new Error("Error!"); } };

try {
  let num = +obj; // Will throw the error directly
} catch (e) {
  console.log("Caught error from unary plus");
}

try {
  let num = Number(obj); // Might have different error handling
} catch (e) {
  console.log("Caught error from Number()");
}

Real-World Example: Input Sanitization

Unary operators are useful when sanitizing numeric input from users:

// Processing form input fields
function processQuantity(input) {
  // Convert to number using unary plus
  const quantity = +input;
  
  // Check if it's a valid number
  if (isNaN(quantity)) {
    return { valid: false, error: "Please enter a valid number" };
  }
  
  // Check if it's a positive integer
  if (quantity <= 0 || !Number.isInteger(quantity)) {
    return { valid: false, error: "Quantity must be a positive whole number" };
  }
  
  // Valid quantity
  return { valid: true, value: quantity };
}

// Test cases
console.log(processQuantity("5"));       // { valid: true, value: 5 }
console.log(processQuantity("3.14"));    // { valid: false, error: "Quantity must be a positive whole number" }
console.log(processQuantity("-10"));     // { valid: false, error: "Quantity must be a positive whole number" }
console.log(processQuantity("five"));    // { valid: false, error: "Please enter a valid number" }
console.log(processQuantity(""));        // { valid: false, error: "Please enter a valid number" }

Operator Precedence and Associativity

Just like in mathematics, JavaScript has rules for the order in which operators are evaluated.

Operator Precedence

Operators with higher precedence are evaluated first. Here's a simplified precedence table (from highest to lowest):

  1. Grouping ()
  2. Member access ., computed member access [], new (with arguments), function call ()
  3. Increment/decrement ++, -- (postfix)
  4. Logical NOT !, unary +, unary -, increment/decrement ++, -- (prefix)
  5. Exponentiation **
  6. Multiplication *, division /, remainder %
  7. Addition +, subtraction -
  8. Comparison >, >=, <, <=
  9. Equality ==, !=, ===, !==
  10. Logical AND &&
  11. Logical OR ||
  12. Conditional (ternary) ?:
  13. Assignment =, +=, -=, etc.
// Operator precedence examples
let result1 = 5 + 3 * 2;     // 11 (not 16) because * has higher precedence than +
let result2 = (5 + 3) * 2;    // 16 because parentheses have highest precedence
let result3 = 10 - 2 - 3;     // 5 because subtraction applies left to right
let result4 = 2 ** 3 ** 2;    // 512 (not 64) because ** is right-associative (2^9)
let result5 = 2 * (3 + 4) / 5; // 2.8

Associativity

When operators have the same precedence, associativity determines the order of evaluation:

// Left-associative examples
let a = 10 / 5 / 2;   // (10 / 5) / 2 = 1

// Right-associative examples
let x, y, z;
x = y = z = 5;       // z = 5, then y = 5, then x = 5

let power = 2 ** 3 ** 2;  // 2 ** (3 ** 2) = 2 ** 9 = 512

Analogy: Operator Precedence as Assembly Line

Think of operator precedence like an assembly line with multiple stations, where each station represents an operator. The stations with highest priority (precedence) get to work on the raw materials first, and then pass their results down the line. Just as putting parts together in the wrong order can result in a defective product, evaluating operators in the wrong order can lead to incorrect calculations.

Assignment Operators

Assignment operators assign values to JavaScript variables.

Basic Assignment

// Simple assignment
let x = 10;
let name = "Alice";
let isActive = true;

// Multiple assignment
let a, b, c;
a = b = c = 5; // All three variables = 5

Compound Assignment Operators

These operators combine an arithmetic operation with assignment.

Operator Equivalent to Description
+= x = x + y Addition assignment
-= x = x - y Subtraction assignment
*= x = x * y Multiplication assignment
/= x = x / y Division assignment
%= x = x % y Remainder assignment
**= x = x ** y Exponentiation assignment
// Compound assignment examples
let x = 10;

x += 5;     // x = x + 5     (x becomes 15)
x -= 3;     // x = x - 3     (x becomes 12)
x *= 2;     // x = x * 2     (x becomes 24)
x /= 4;     // x = x / 4     (x becomes 6)
x %= 4;     // x = x % 4     (x becomes 2)
x **= 3;    // x = x ** 3    (x becomes 8)

Assignment with String Concatenation

// String concatenation with +=
let greeting = "Hello";
greeting += " World"; // greeting = greeting + " World"
console.log(greeting); // "Hello World"

let message = "Count: ";
message += 5; // message = message + 5 (converts 5 to string)
console.log(message); // "Count: 5"

Real-World Example: Shopping Cart

Assignment operators are used extensively in dynamic applications:

// Shopping cart implementation
function ShoppingCart() {
  this.items = [];
  this.total = 0;
  
  // Add item to cart
  this.addItem = function(name, price, quantity = 1) {
    this.items.push({ name, price, quantity });
    this.total += price * quantity; // Use compound assignment
    return this;
  };
  
  // Update item quantity
  this.updateQuantity = function(index, newQuantity) {
    if (index >= 0 && index < this.items.length) {
      const item = this.items[index];
      const oldSubtotal = item.price * item.quantity;
      item.quantity = newQuantity;
      const newSubtotal = item.price * item.quantity;
      
      // Update total using compound assignment
      this.total += (newSubtotal - oldSubtotal);
      
      return true;
    }
    return false;
  };
  
  // Apply discount
  this.applyDiscount = function(percentOff) {
    if (percentOff > 0 && percentOff <= 100) {
      this.total *= (1 - percentOff / 100); // Compound assignment
      return true;
    }
    return false;
  };
  
  // Get cart summary
  this.getSummary = function() {
    let summary = `Shopping Cart (${this.items.length} items)\n`;
    summary += "-".repeat(30) + "\n";
    
    this.items.forEach((item, index) => {
      summary += `${index + 1}. ${item.name} x ${item.quantity} - $${(item.price * item.quantity).toFixed(2)}\n`;
    });
    
    summary += "-".repeat(30) + "\n";
    summary += `Total: $${this.total.toFixed(2)}`;
    
    return summary;
  };
}

// Usage example
const cart = new ShoppingCart();
cart.addItem("Laptop", 999.99)
   .addItem("Mouse", 24.99, 2)
   .addItem("Keyboard", 59.99);

console.log(cart.getSummary());

cart.updateQuantity(0, 2); // Update laptop quantity
cart.applyDiscount(10);    // Apply 10% discount

console.log(cart.getSummary());

Destructuring Assignment

Introduced in ES6, destructuring assignment is a special syntax that allows you to extract values from arrays or objects into distinct variables.

Array Destructuring

// Basic array destructuring
const numbers = [1, 2, 3, 4, 5];
const [first, second, third] = numbers;

console.log(first);  // 1
console.log(second); // 2
console.log(third);  // 3

// Skipping elements
const colors = ['red', 'green', 'blue', 'yellow'];
const [primary, , tertiary] = colors;

console.log(primary);  // "red"
console.log(tertiary); // "blue"

// Rest pattern
const [head, ...tail] = numbers;
console.log(head); // 1
console.log(tail); // [2, 3, 4, 5]

// Default values
const incomplete = [1, 2];
const [a, b, c = 3, d = 4] = incomplete;
console.log(a, b, c, d); // 1 2 3 4

// Swapping variables
let x = 10, y = 20;
[x, y] = [y, x];
console.log(x, y); // 20 10

Object Destructuring

// Basic object destructuring
const person = {
  name: 'Alice',
  age: 30,
  location: {
    city: 'New York',
    country: 'USA'
  }
};

const { name, age } = person;
console.log(name); // "Alice"
console.log(age);  // 30

// Different variable names
const { name: fullName, age: years } = person;
console.log(fullName); // "Alice"
console.log(years);    // 30

// Nested destructuring
const { location: { city, country } } = person;
console.log(city);    // "New York"
console.log(country); // "USA"

// Default values
const incomplete = { id: 123 };
const { id, username = 'guest' } = incomplete;
console.log(id);       // 123
console.log(username); // "guest"

// Rest pattern in objects
const { name: personName, ...details } = person;
console.log(personName); // "Alice"
console.log(details);    // { age: 30, location: { city: "New York", country: "USA" } }

Practical Applications of Destructuring

// Function parameter destructuring
function displayUserProfile({ name, age, location: { city } = {} } = {}) {
  console.log(`Name: ${name || 'Unknown'}`);
  console.log(`Age: ${age || 'Not specified'}`);
  console.log(`City: ${city || 'Unknown'}`);
}

displayUserProfile(person);
displayUserProfile({ name: 'Bob' }); // Missing properties use defaults
displayUserProfile(); // All defaults when no argument provided

// Returning multiple values
function getMinMax(numbers) {
  return {
    min: Math.min(...numbers),
    max: Math.max(...numbers)
  };
}

const { min, max } = getMinMax([3, 1, 4, 1, 5, 9]);
console.log(min, max); // 1 9

// Destructuring in loops
const people = [
  { id: 1, name: 'Alice', age: 30 },
  { id: 2, name: 'Bob', age: 25 },
  { id: 3, name: 'Charlie', age: 35 }
];

for (const { name, age } of people) {
  console.log(`${name} is ${age} years old`);
}

Analogy: Destructuring as Unpacking a Package

Think of destructuring assignment like unpacking a package where you know exactly what you want:

  • An array is like a package with items arranged in a specific order. Array destructuring is like saying "I'll take the first item, the second item, and so on."
  • An object is like a labeled package where items have names. Object destructuring is like saying "Give me the item labeled 'name' and the item labeled 'age'."

Just as unpacking only what you need saves space in your home, destructuring only the properties you need makes your code cleaner and more focused.

Real-World Example: API Response Handling

Destructuring is particularly useful when working with APIs:

// Fetching data from an API and using destructuring
async function fetchUserData(userId) {
  try {
    const response = await fetch(`https://api.example.com/users/${userId}`);
    const data = await response.json();
    
    // Destructure and format the response
    const { 
      id,
      name: { first, last },
      email,
      roles = ['user'], // Default value if roles is missing
      settings: { theme = 'light', notifications = true } = {} // Default if settings is missing
    } = data;
    
    // Create formatted user object
    const formattedUser = {
      userId: id,
      fullName: `${first} ${last}`,
      email,
      permissions: {
        isAdmin: roles.includes('admin'),
        isEditor: roles.includes('editor')
      },
      preferences: {
        theme,
        notifications
      }
    };
    
    return formattedUser;
  } catch (error) {
    console.error('Error fetching user data:', error);
    return null;
  }
}

// Using the function
const user = await fetchUserData(123);
// Now we can easily access user.fullName, user.permissions.isAdmin, etc.

Operator Quirks and Edge Cases

JavaScript operators have some behaviors that might surprise developers coming from other languages.

Division by Zero

// Division by zero doesn't throw an error in JavaScript
console.log(5 / 0);  // Infinity
console.log(-5 / 0); // -Infinity
console.log(0 / 0);  // NaN

The + Operator and Type Coercion

// + operator is overloaded for string concatenation
console.log(5 + 5);     // 10 (number addition)
console.log("5" + 5);   // "55" (string concatenation)
console.log(5 + "5");   // "55" (string concatenation)

// Other operators prefer numeric conversion
console.log("5" - 2);   // 3 (converted to number)
console.log("5" * 2);   // 10 (converted to number)
console.log("5" / 2);   // 2.5 (converted to number)

Comparing with NaN

// NaN is not equal to anything, including itself
console.log(NaN == NaN);  // false
console.log(NaN === NaN); // false

// Checking for NaN
console.log(isNaN(NaN));       // true
console.log(isNaN("hello"));   // true (converts to NaN first)
console.log(Number.isNaN(NaN)); // true
console.log(Number.isNaN("hello")); // false (no conversion)

IEEE-754 Floating Point Issues

// Floating point precision issues
console.log(0.1 + 0.2);         // 0.30000000000000004, not exactly 0.3
console.log(0.1 + 0.2 === 0.3); // false

// Workarounds for financial calculations
console.log((0.1 * 10 + 0.2 * 10) / 10); // 0.3

// Using toFixed for display
console.log((0.1 + 0.2).toFixed(2)); // "0.30"

Real-World Example: Avoiding Floating Point Issues in E-commerce

Floating point precision is critical in financial applications:

// Using integer cents instead of floating point dollars
class PreciseCart {
  constructor() {
    this.items = [];
    this.totalCents = 0;
  }
  
  addItem(name, priceDollars, quantity = 1) {
    // Convert to cents and round to avoid floating point issues
    const priceCents = Math.round(priceDollars * 100);
    const item = { name, priceCents, quantity };
    this.items.push(item);
    this.totalCents += priceCents * quantity;
    return this;
  }
  
  updateQuantity(index, newQuantity) {
    if (index >= 0 && index < this.items.length) {
      const item = this.items[index];
      const oldTotal = item.priceCents * item.quantity;
      item.quantity = newQuantity;
      const newTotal = item.priceCents * item.quantity;
      this.totalCents = this.totalCents - oldTotal + newTotal;
      return true;
    }
    return false;
  }
  
  applyDiscount(percentOff) {
    if (percentOff > 0 && percentOff <= 100) {
      // Calculate exact discount in cents
      const discountAmount = Math.round(this.totalCents * (percentOff / 100));
      this.totalCents -= discountAmount;
      return true;
    }
    return false;
  }
  
  getTotalDollars() {
    // Convert back to dollars only for display
    return (this.totalCents / 100).toFixed(2);
  }
}

// Usage
const cart = new PreciseCart();
cart.addItem("Coffee", 3.99, 2)
   .addItem("Bagel", 2.50);

console.log(`Total: $${cart.getTotalDollars()}`); // Exactly $10.48
cart.applyDiscount(15);
console.log(`Total after discount: $${cart.getTotalDollars()}`); // Exactly $8.91

Practical Exercise

Let's apply what we've learned with some practical exercises.

Exercise 1: Temperature Converter

Create a function to convert between Celsius and Fahrenheit:

// Temperature conversion functions
function celsiusToFahrenheit(celsius) {
  // Formula: F = C × 9/5 + 32
  return (celsius * 9/5) + 32;
}

function fahrenheitToCelsius(fahrenheit) {
  // Formula: C = (F - 32) × 5/9
  return (fahrenheit - 32) * 5/9;
}

// Test cases
console.log(`0°C = ${celsiusToFahrenheit(0)}°F`);     // 32°F
console.log(`100°C = ${celsiusToFahrenheit(100)}°F`); // 212°F
console.log(`32°F = ${fahrenheitToCelsius(32)}°C`);   // 0°C
console.log(`212°F = ${fahrenheitToCelsius(212)}°C`); // 100°C

// Advanced version with rounding and validation
function convertTemperature(value, fromUnit, toUnit, precision = 2) {
  // Validate input
  if (typeof value !== 'number' || isNaN(value)) {
    throw new Error('Temperature value must be a number');
  }
  
  // Normalize units for comparison
  const from = fromUnit.toUpperCase();
  const to = toUnit.toUpperCase();
  
  // Same unit, no conversion needed
  if (from === to) {
    return Number(value.toFixed(precision));
  }
  
  // Perform conversion
  let result;
  if (from === 'C' && to === 'F') {
    result = celsiusToFahrenheit(value);
  } else if (from === 'F' && to === 'C') {
    result = fahrenheitToCelsius(value);
  } else {
    throw new Error('Unsupported temperature units. Use C or F.');
  }
  
  // Round to specified precision
  return Number(result.toFixed(precision));
}

// Test the advanced function
console.log(convertTemperature(25, 'C', 'F'));   // 77.00
console.log(convertTemperature(98.6, 'F', 'C')); // 37.00

Exercise 2: Tip Calculator

Create a function to calculate tip and split a bill:

// Tip calculator
function calculateBill(subtotal, tipPercentage = 15, numberOfPeople = 1) {
  // Validate inputs
  if (subtotal <= 0) {
    throw new Error('Subtotal must be positive');
  }
  
  if (tipPercentage < 0) {
    throw new Error('Tip percentage cannot be negative');
  }
  
  if (numberOfPeople < 1 || !Number.isInteger(numberOfPeople)) {
    throw new Error('Number of people must be a positive integer');
  }
  
  // Calculate tip amount
  const tipAmount = subtotal * (tipPercentage / 100);
  
  // Calculate total bill
  const totalBill = subtotal + tipAmount;
  
  // Calculate amount per person
  const perPerson = totalBill / numberOfPeople;
  
  // Return all calculated values
  return {
    subtotal: subtotal.toFixed(2),
    tipPercentage: tipPercentage.toFixed(2),
    tipAmount: tipAmount.toFixed(2),
    total: totalBill.toFixed(2),
    numberOfPeople,
    perPerson: perPerson.toFixed(2)
  };
}

// Test cases
console.log(calculateBill(50.00, 20, 2));
console.log(calculateBill(100.00)); // Default 15% tip, 1 person

Exercise 3: Advanced Loan Calculator

Create a function to calculate monthly payments and amortization schedule for a loan:

// Loan calculator
function calculateLoan(principal, annualRate, years) {
  // Validate inputs
  if (principal <= 0 || annualRate <= 0 || years <= 0) {
    throw new Error('All values must be positive');
  }
  
  // Calculate monthly interest rate
  const monthlyRate = annualRate / 100 / 12;
  
  // Calculate number of payments
  const numberOfPayments = years * 12;
  
  // Calculate monthly payment
  // Formula: P = L[c(1 + c)^n]/[(1 + c)^n - 1]
  // where P = payment, L = loan amount, c = monthly interest rate, n = number of payments
  const x = Math.pow(1 + monthlyRate, numberOfPayments);
  const monthlyPayment = (principal * monthlyRate * x) / (x - 1);
  
  // Generate amortization schedule
  const amortizationSchedule = [];
  let balance = principal;
  let totalInterest = 0;
  
  for (let month = 1; month <= numberOfPayments; month++) {
    // Calculate interest for this month
    const interestPayment = balance * monthlyRate;
    
    // Calculate principal for this month
    const principalPayment = monthlyPayment - interestPayment;
    
    // Update the loan balance
    balance -= principalPayment;
    
    // Update total interest paid
    totalInterest += interestPayment;
    
    // Record this month's payment info
    amortizationSchedule.push({
      month,
      payment: monthlyPayment.toFixed(2),
      principal: principalPayment.toFixed(2),
      interest: interestPayment.toFixed(2),
      totalInterest: totalInterest.toFixed(2),
      balance: Math.max(0, balance).toFixed(2) // Prevent negative balance due to rounding
    });
  }
  
  // Return loan details
  return {
    loanAmount: principal.toFixed(2),
    interestRate: annualRate.toFixed(2) + '%',
    loanTermYears: years,
    numberOfPayments,
    monthlyPayment: monthlyPayment.toFixed(2),
    totalPayments: (monthlyPayment * numberOfPayments).toFixed(2),
    totalInterest: totalInterest.toFixed(2),
    amortizationSchedule
  };
}

// Example: $200,000 loan at 3.5% for 30 years
const loanDetails = calculateLoan(200000, 3.5, 30);
console.log(`Monthly payment: $${loanDetails.monthlyPayment}`);
console.log(`Total interest paid: $${loanDetails.totalInterest}`);

// Print first 3 months of the amortization schedule
console.log('First 3 months of payments:');
loanDetails.amortizationSchedule.slice(0, 3).forEach(month => {
  console.log(`Month ${month.month}: Payment=$${month.payment}, Principal=$${month.principal}, Interest=$${month.interest}, Remaining Balance=$${month.balance}`);
});

Key Takeaways

Additional Resources