JavaScript JS
From Basics to Advanced - Complete Guide
1 Introduction to JavaScript
JavaScript is the programming language of the web. It adds interactivity to websites - handling clicks, validating forms, fetching data, creating animations, and much more. Unlike HTML (structure) and CSS (style), JavaScript provides behavior.
🔑 Where JavaScript Runs
Browser: Runs in Chrome, Firefox, Safari - makes websites interactive
Server (Node.js): Runs on servers - powers backends, APIs
Mobile: React Native, Ionic - builds mobile apps
Desktop: Electron - builds desktop apps (VS Code, Slack)
// This is a single-line comment /* This is a multi-line comment. Use for longer explanations. */ // Print to browser console (F12 to open) console.log("Hello, World!"); // Print multiple values console.log("Name:", "Alice", "Age:", 25); // Show alert popup (avoid in production) alert("Welcome to JavaScript!");
Hello, World! Name: Alice Age: 25
2 Variables
Variables store data values. JavaScript has three ways to declare variables:
let, const, and var. Modern JavaScript prefers
let and const.
// LET - value can change later let age = 25; console.log("Age:", age); // Age: 25 age = 26; // ✓ Allowed - reassigning console.log("New age:", age); // New age: 26 // CONST - value cannot change (use by default!) const name = "Alice"; console.log("Name:", name); // Name: Alice // name = "Bob"; // ✗ ERROR! Cannot reassign const // CONST with objects - properties CAN change const person = { name: "Alice", age: 25 }; person.age = 26; // ✓ Allowed - modifying property console.log(person); // { name: "Alice", age: 26 } // VAR - old way, has scoping issues (avoid!) var oldVariable = "Don't use var"; // Good naming conventions let firstName = "John"; // camelCase (recommended) let is_active = true; // snake_case (less common) const MAX_SIZE = 100; // SCREAMING_CASE for constants
Age: 25
New age: 26
Name: Alice
{ name: "Alice", age: 26 }
💡 Best Practice
Use const by default. Only use let when you know the value will change.
Never use var in modern JavaScript.
3 Data Types
JavaScript has several built-in data types. Understanding them is crucial because different types behave differently and support different operations.
// STRING - text in quotes let greeting = "Hello"; // Double quotes let name = 'Alice'; // Single quotes let message = `Hi, ${name}!`; // Template literal (backticks) console.log(message); // Hi, Alice! // NUMBER - integers and decimals let age = 25; let price = 19.99; let negative = -10; console.log(age + price); // 44.99 // BOOLEAN - true or false let isLoggedIn = true; let hasPermission = false; console.log(isLoggedIn); // true // UNDEFINED - declared but no value let undefinedVar; console.log(undefinedVar); // undefined // NULL - intentionally empty let emptyValue = null; console.log(emptyValue); // null // ARRAY - ordered list of values let colors = ["red", "green", "blue"]; console.log(colors[0]); // red (index 0) console.log(colors.length); // 3 // OBJECT - key-value pairs let person = { name: "Alice", age: 25, isStudent: true }; console.log(person.name); // Alice console.log(person["age"]); // 25 // Check type with typeof console.log(typeof greeting); // string console.log(typeof age); // number console.log(typeof isLoggedIn); // boolean console.log(typeof colors); // object (arrays are objects)
Hi, Alice! 44.99 true undefined null red 3 Alice 25 string number boolean object
4 Operators
Operators perform operations on values. JavaScript has arithmetic, comparison, logical, and assignment operators.
// ARITHMETIC OPERATORS let a = 10, b = 3; console.log(a + b); // Addition: 13 console.log(a - b); // Subtraction: 7 console.log(a * b); // Multiplication: 30 console.log(a / b); // Division: 3.333... console.log(a % b); // Modulo (remainder): 1 console.log(a ** b); // Exponent: 1000 // INCREMENT / DECREMENT let x = 5; x++; // x = 6 (increment) x--; // x = 5 (decrement) // COMPARISON OPERATORS console.log(5 == "5"); // true (loose equality, converts type) console.log(5 === "5"); // false (strict equality, same type) console.log(5 != "5"); // false console.log(5 !== "5"); // true (strict not equal) console.log(5 > 3); // true console.log(5 >= 5); // true console.log(3 < 5); // true // LOGICAL OPERATORS console.log(true && false); // AND: false console.log(true || false); // OR: true console.log(!true); // NOT: false // ASSIGNMENT OPERATORS let n = 10; n += 5; // n = n + 5 → 15 n -= 3; // n = n - 3 → 12 n *= 2; // n = n * 2 → 24 n /= 4; // n = n / 4 → 6 // TERNARY OPERATOR (shorthand if/else) let age = 20; let status = age >= 18 ? "Adult" : "Minor"; console.log(status); // "Adult"
13 7 30 3.3333333333333335 1 1000 true false Adult
⚠️ Always Use === Instead of ==
The == operator converts types before comparing, leading to unexpected results like 0 == "" being true. Always use === for predictable comparisons.
5 Conditionals (If/Else)
Conditionals let your code make decisions. The code inside an if block
only runs if the condition is true.
const age = 20; const score = 85; // Simple if if (age >= 18) { console.log("You are an adult"); } // If-else if (score >= 60) { console.log("You passed!"); } else { console.log("You failed."); } // If-else if-else chain if (score >= 90) { console.log("Grade: A"); } else if (score >= 80) { console.log("Grade: B"); } else if (score >= 70) { console.log("Grade: C"); } else { console.log("Grade: F"); } // Ternary operator (shorthand if-else) const status = age >= 18 ? "Adult" : "Minor"; console.log("Status:", status); // Logical operators const hasID = true; const hasTicket = true; // AND (&&) - both must be true if (hasID && hasTicket) { console.log("Entry allowed"); } // OR (||) - at least one must be true if (age < 12 || age > 65) { console.log("Discount applies"); }
You are an adult You passed! Grade: B Status: Adult Entry allowed
5 Loops
Loops execute code repeatedly. Use for when you know how many times,
while for unknown iterations, and for...of for arrays.
// FOR loop - when you know the count console.log("=== FOR Loop ==="); for (let i = 1; i <= 5; i++) { console.log(`Count: ${i}`); } // FOR...OF loop - iterate over arrays console.log("=== FOR...OF Loop ==="); const fruits = ["Apple", "Banana", "Cherry"]; for (const fruit of fruits) { console.log(`Fruit: ${fruit}`); } // WHILE loop - when count is unknown console.log("=== WHILE Loop ==="); let countdown = 3; while (countdown > 0) { console.log(`${countdown}...`); countdown--; } console.log("Liftoff! 🚀"); // forEach - array method for iteration console.log("=== forEach ==="); fruits.forEach((fruit, index) => { console.log(`${index}: ${fruit}`); });
=== FOR Loop === Count: 1 Count: 2 Count: 3 Count: 4 Count: 5 === FOR...OF Loop === Fruit: Apple Fruit: Banana Fruit: Cherry === WHILE Loop === 3... 2... 1... Liftoff! 🚀 === forEach === 0: Apple 1: Banana 2: Cherry
6 Functions
Functions are reusable blocks of code. They can accept inputs (parameters), perform operations, and return outputs. Functions are fundamental to JavaScript.
// Basic function declaration function greet() { console.log("Hello!"); } greet(); // Call the function // Function with parameters function greetPerson(name) { console.log(`Hello, ${name}!`); } greetPerson("Alice"); greetPerson("Bob"); // Function with return value function add(a, b) { return a + b; } const sum = add(5, 3); console.log("Sum:", sum); // Default parameters function greetWithDefault(name = "Guest") { console.log(`Welcome, ${name}!`); } greetWithDefault(); // Uses default greetWithDefault("Alice"); // Uses provided value // ARROW FUNCTION (modern ES6+ syntax) const multiply = (a, b) => { return a * b; }; console.log("Multiply:", multiply(4, 5)); // Arrow function shorthand (single expression) const square = (n) => n * n; console.log("Square of 4:", square(4)); // Even shorter for single parameter const double = n => n * 2; console.log("Double of 7:", double(7));
Hello! Hello, Alice! Hello, Bob! Sum: 8 Welcome, Guest! Welcome, Alice! Multiply: 20 Square of 4: 16 Double of 7: 14
8 Arrow Functions
Arrow functions provide a shorter syntax for writing functions. They're especially useful for callbacks and one-liners.
// Traditional function function add(a, b) { return a + b; } // Arrow function equivalent const addArrow = (a, b) => { return a + b; }; // Shorter: implicit return (single expression) const addShort = (a, b) => a + b; // Single parameter: no parentheses needed const double = n => n * 2; // No parameters: empty parentheses const greet = () => "Hello!"; // Usage console.log(add(2, 3)); // 5 console.log(addArrow(2, 3)); // 5 console.log(addShort(2, 3)); // 5 console.log(double(5)); // 10 console.log(greet()); // "Hello!" // Perfect for array methods! const numbers = [1, 2, 3, 4, 5]; const doubled = numbers.map(n => n * 2); console.log(doubled); // [2, 4, 6, 8, 10] const evens = numbers.filter(n => n % 2 === 0); console.log(evens); // [2, 4]
5 5 5 10 Hello! [2, 4, 6, 8, 10] [2, 4]
💡 When to Use Arrow Functions
✓ Callbacks: array.map(x => x * 2)
✓ Short one-liners
✗ Object methods (use regular functions for this)
9 Arrays
Arrays store multiple values in a single variable. They're ordered collections where each item has an index starting from 0.
// Creating arrays const fruits = ["apple", "banana", "orange"]; const numbers = [1, 2, 3, 4, 5]; const mixed = [1, "two", true, null]; // Accessing elements (0-indexed) console.log(fruits[0]); // "apple" console.log(fruits[2]); // "orange" console.log(fruits.length); // 3 // Modifying elements fruits[1] = "mango"; console.log(fruits); // ["apple", "mango", "orange"] // Adding elements fruits.push("grape"); // Add to end fruits.unshift("kiwi"); // Add to beginning // Removing elements fruits.pop(); // Remove from end fruits.shift(); // Remove from beginning // Finding elements console.log(fruits.indexOf("mango")); // 1 (index) console.log(fruits.includes("apple")); // true // Slicing (does not modify original) const nums = [1, 2, 3, 4, 5]; console.log(nums.slice(1, 4)); // [2, 3, 4] // Joining arrays const arr1 = [1, 2]; const arr2 = [3, 4]; const combined = [...arr1, ...arr2]; // [1, 2, 3, 4] // Looping through arrays for (const fruit of fruits) { console.log(fruit); } // With index fruits.forEach((fruit, index) => { console.log(`${index}: ${fruit}`); });
apple orange 3 ["apple", "mango", "orange"] 1 true [2, 3, 4] 0: apple 1: mango 2: orange
10 Array Methods
JavaScript arrays have powerful built-in methods for transformation and filtering. These are essential for modern JavaScript development.
const numbers = [1, 2, 3, 4, 5]; // MAP - transform each element const doubled = numbers.map(n => n * 2); console.log("Doubled:", doubled); // [2, 4, 6, 8, 10] // FILTER - keep elements that pass test const evens = numbers.filter(n => n % 2 === 0); console.log("Even numbers:", evens); // [2, 4] // FIND - get first matching element const firstBig = numbers.find(n => n > 3); console.log("First > 3:", firstBig); // 4 // REDUCE - accumulate to single value const sum = numbers.reduce((total, n) => total + n, 0); console.log("Sum:", sum); // 15 // SOME - check if any element passes const hasLarge = numbers.some(n => n > 4); console.log("Has number > 4:", hasLarge); // true // EVERY - check if all elements pass const allPositive = numbers.every(n => n > 0); console.log("All positive:", allPositive); // true // CHAINING methods together const result = numbers .filter(n => n > 2) // Keep > 2: [3, 4, 5] .map(n => n * 10); // Multiply: [30, 40, 50] console.log("Chained:", result);
Doubled: [2, 4, 6, 8, 10] Even numbers: [2, 4] First > 3: 4 Sum: 15 Has number > 4: true All positive: true Chained: [30, 40, 50]
11 Objects
Objects store data as key-value pairs. They're used to represent real-world entities and are fundamental to JavaScript.
// Creating objects const person = { name: "Alice", age: 25, city: "NYC", isStudent: false, hobbies: ["coding", "reading"], // Method (function inside object) greet() { return `Hi, I'm ${this.name}!`; } }; // Accessing properties console.log(person.name); // Dot notation: "Alice" console.log(person["age"]); // Bracket notation: 25 console.log(person.greet()); // "Hi, I'm Alice!" // Modifying properties person.age = 26; person.email = "alice@email.com"; // Add new property // Deleting properties delete person.isStudent; // Check if property exists console.log("name" in person); // true // Get all keys/values console.log(Object.keys(person)); // ["name", "age", "city", ...] console.log(Object.values(person)); // ["Alice", 26, "NYC", ...] // Loop through object for (const key in person) { console.log(`${key}: ${person[key]}`); } // Nested objects const company = { name: "TechCorp", address: { street: "123 Main St", city: "Boston" } }; console.log(company.address.city); // "Boston"
Alice 25 Hi, I'm Alice! true ["name", "age", "city", "hobbies", "greet", "email"] name: Alice age: 26 city: NYC hobbies: coding,reading email: alice@email.com Boston
12 DOM Manipulation
The DOM (Document Object Model) represents the HTML structure as objects. JavaScript can read and modify the DOM to make pages interactive.
// SELECT elements const title = document.getElementById("title"); const btn = document.querySelector(".btn"); const items = document.querySelectorAll(".item"); // MODIFY content title.textContent = "New Title"; // Change text title.innerHTML = "<em>Styled</em>"; // Change HTML // MODIFY styles title.style.color = "blue"; title.style.fontSize = "24px"; // ADD/REMOVE classes title.classList.add("highlight"); title.classList.remove("old-class"); title.classList.toggle("active"); // Add if missing, remove if present // MODIFY attributes btn.setAttribute("disabled", "true"); btn.removeAttribute("disabled"); // CREATE new elements const newDiv = document.createElement("div"); newDiv.textContent = "I am new!"; newDiv.classList.add("new-item"); // ADD to page document.body.appendChild(newDiv); // Or: parentElement.insertBefore(newDiv, existingElement); // REMOVE element // newDiv.remove();
9 Events
Events are things that happen on the page - clicks, key presses, form submissions. Event listeners let JavaScript respond to these actions.
const button = document.querySelector("#myButton"); const input = document.querySelector("#myInput"); // CLICK event button.addEventListener("click", () => { console.log("Button clicked!"); }); // CLICK with event object button.addEventListener("click", (event) => { console.log("Clicked element:", event.target); event.preventDefault(); // Stop default action }); // INPUT event - fires on every keystroke input.addEventListener("input", (e) => { console.log("Current value:", e.target.value); }); // KEYDOWN event document.addEventListener("keydown", (e) => { if (e.key === "Enter") { console.log("Enter pressed!"); } }); // FORM SUBMIT const form = document.querySelector("form"); form.addEventListener("submit", (e) => { e.preventDefault(); // Stop page reload const formData = new FormData(form); console.log("Form submitted!"); }); // MOUSE events element.addEventListener("mouseenter", () => { /* hover in */ }); element.addEventListener("mouseleave", () => { /* hover out */ });
10 Async/Await (Advanced)
Asynchronous code handles operations that take time (API calls, file reading).
async/await makes async code look synchronous and easier to read.
// Simulated async function (like API call) function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } // ASYNC function with AWAIT async function fetchUserData() { console.log("Fetching data..."); // 'await' pauses until promise resolves await delay(1000); // Wait 1 second console.log("Data received!"); return { name: "Alice", age: 25 }; } // Call async function fetchUserData().then(user => { console.log("User:", user); }); // Using try/catch for errors async function fetchWithErrorHandling() { try { const response = await fetch("https://api.example.com/data"); if (!response.ok) { throw new Error("HTTP error!"); } const data = await response.json(); console.log(data); } catch (error) { console.error("Error:", error.message); } } // Parallel async operations async function fetchMultiple() { // Run both at same time, not sequentially const [user, posts] = await Promise.all([ fetch("/api/user"), fetch("/api/posts") ]); console.log("Both loaded!"); }
Fetching data...
(1 second passes)
Data received!
User: { name: "Alice", age: 25 }
11 Fetch API (Advanced)
The Fetch API makes HTTP requests to servers. It's used to get data from APIs, submit forms, and communicate with backends.
// GET request (fetch data) async function getUsers() { const response = await fetch("https://jsonplaceholder.typicode.com/users"); const users = await response.json(); console.log("Users:", users); } // POST request (send data) async function createUser(userData) { const response = await fetch("https://api.example.com/users", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(userData) }); const newUser = await response.json(); console.log("Created:", newUser); } // Usage createUser({ name: "Alice", email: "alice@example.com" }); // Complete example with error handling async function fetchData(url) { try { const response = await fetch(url); if (!response.ok) { throw new Error(`HTTP error: ${response.status}`); } return await response.json(); } catch (error) { console.error("Fetch failed:", error); return null; } }
12 Classes (ES6 OOP)
ES6 Classes provide a cleaner syntax for object-oriented programming in JavaScript. They're syntactic sugar over prototypes but make code much more readable.
// Class declaration class Person { // Constructor - called when creating new instance constructor(name, age) { this.name = name; this.age = age; } // Method greet() { console.log(`Hi, I'm ${this.name}!`); } // Getter get info() { return `${this.name}, ${this.age} years old`; } // Setter set birthday(date) { this._birthday = date; } // Static method - called on class, not instance static species() { return "Homo sapiens"; } } // Create instances const alice = new Person("Alice", 25); const bob = new Person("Bob", 30); alice.greet(); // Hi, I'm Alice! console.log(alice.info); // Alice, 25 years old console.log(Person.species()); // Homo sapiens // INHERITANCE with extends class Employee extends Person { constructor(name, age, role) { super(name, age); // Call parent constructor this.role = role; } // Override parent method greet() { super.greet(); // Call parent method console.log(`I work as a ${this.role}`); } work() { console.log(`${this.name} is working...`); } } const dev = new Employee("Carol", 28, "Developer"); dev.greet(); dev.work(); // Private fields (ES2022+) - prefix with # class BankAccount { #balance = 0; // Private - can't access from outside deposit(amount) { this.#balance += amount; } getBalance() { return this.#balance; } } const account = new BankAccount(); account.deposit(100); console.log(account.getBalance()); // 100 // console.log(account.#balance); // ERROR! Private field
Hi, I'm Alice! Alice, 25 years old Homo sapiens Hi, I'm Carol! I work as a Developer Carol is working... 100
13 Destructuring
Destructuring extracts values from arrays or properties from objects into distinct variables. It makes code cleaner and avoids repetitive access patterns.
// ARRAY DESTRUCTURING const colors = ["red", "green", "blue"]; // Old way const first = colors[0]; const second = colors[1]; // Destructuring way const [red, green, blue] = colors; console.log(red, green, blue); // red green blue // Skip elements const [primary, , tertiary] = colors; console.log(primary, tertiary); // red blue // Rest operator - collect remaining const [head, ...rest] = [1, 2, 3, 4, 5]; console.log(head); // 1 console.log(rest); // [2, 3, 4, 5] // OBJECT DESTRUCTURING const person = { name: "Alice", age: 25, city: "NYC", job: "Developer" }; // Extract specific properties const { name, age } = person; console.log(name, age); // Alice 25 // Rename while destructuring const { name: userName, city: location } = person; console.log(userName, location); // Alice NYC // Default values const { country = "USA" } = person; console.log(country); // USA (default used) // Nested destructuring const company = { name: "TechCorp", address: { street: "123 Main St", city: "Boston" } }; const { address: { city: companyCity } } = company; console.log(companyCity); // Boston // Function parameter destructuring function printUser({ name, age, job = "Unknown" }) { console.log(`${name}, ${age}, ${job}`); } printUser(person); // Alice, 25, Developer
red green blue red blue 1 [2, 3, 4, 5] Alice 25 Alice NYC USA Boston Alice, 25, Developer
14 Spread & Rest Operators
The ... operator has two uses: Spread expands arrays/objects,
Rest collects multiple elements. Same syntax, opposite behaviors.
// ========== SPREAD OPERATOR ========== // Spread array elements const nums1 = [1, 2, 3]; const nums2 = [4, 5, 6]; const combined = [...nums1, ...nums2]; console.log(combined); // [1, 2, 3, 4, 5, 6] // Copy array (shallow) const original = [1, 2, 3]; const copy = [...original]; copy.push(4); console.log(original); // [1, 2, 3] - unchanged! console.log(copy); // [1, 2, 3, 4] // Spread in function calls const numbers = [5, 10, 3, 8]; console.log(Math.max(...numbers)); // 10 // Spread objects const defaults = { theme: "dark", lang: "en" }; const userPrefs = { theme: "light" }; const settings = { ...defaults, ...userPrefs }; console.log(settings); // { theme: "light", lang: "en" } - userPrefs overwrites // Add properties while spreading const person = { name: "Alice", age: 25 }; const updated = { ...person, age: 26, city: "NYC" }; console.log(updated); // { name: "Alice", age: 26, city: "NYC" } // ========== REST OPERATOR ========== // Rest in function parameters function sum(...nums) { return nums.reduce((a, b) => a + b, 0); } console.log(sum(1, 2, 3, 4)); // 10 // Rest in destructuring const [first, ...others] = [1, 2, 3, 4]; console.log(first); // 1 console.log(others); // [2, 3, 4] const { name, ...restProps } = { name: "Bob", age: 30, city: "LA" }; console.log(name); // Bob console.log(restProps); // { age: 30, city: "LA" }
[1, 2, 3, 4, 5, 6]
[1, 2, 3]
[1, 2, 3, 4]
10
{ theme: "light", lang: "en" }
{ name: "Alice", age: 26, city: "NYC" }
10
1
[2, 3, 4]
Bob
{ age: 30, city: "LA" }
15 Local Storage
Local Storage lets you store data in the browser that persists even after closing the tab. Perfect for user preferences, cached data, and offline functionality.
// STORE data (string only) localStorage.setItem("username", "Alice"); localStorage.setItem("theme", "dark"); // RETRIEVE data const username = localStorage.getItem("username"); console.log("Username:", username); // Alice // CHECK if key exists if (localStorage.getItem("theme")) { console.log("Theme found!"); } // STORE objects/arrays (must convert to JSON) const user = { name: "Bob", age: 30, preferences: { notifications: true } }; // Convert object to JSON string localStorage.setItem("user", JSON.stringify(user)); // RETRIEVE and parse back to object const storedUser = JSON.parse(localStorage.getItem("user")); console.log(storedUser.name); // Bob console.log(storedUser.preferences.notifications); // true // STORE arrays const todos = [ { id: 1, text: "Learn JS", done: true }, { id: 2, text: "Build project", done: false } ]; localStorage.setItem("todos", JSON.stringify(todos)); // REMOVE single item localStorage.removeItem("theme"); // CLEAR all storage // localStorage.clear(); // Removes everything! // Get all keys console.log("Keys in storage:"); for (let i = 0; i < localStorage.length; i++) { const key = localStorage.key(i); console.log(` ${key}: ${localStorage.getItem(key)}`); } // SESSION STORAGE - same API, but clears on tab close sessionStorage.setItem("temp", "This disappears when tab closes");
Username: Alice
Theme found!
Bob
true
Keys in storage:
username: Alice
user: {"name":"Bob","age":30,"preferences":{"notifications":true}}
todos: [{"id":1,"text":"Learn JS","done":true},{"id":2,"text":"Build project","done":false}]
⚠️ Local Storage Limitations
• Only stores strings (use JSON for objects)
• ~5MB limit per domain
• Synchronous (can block UI for large data)
• Not secure for sensitive data (accessible via JS)
16 ES Modules
Modules let you split code across files and import/export functionality. This enables better organization, code reuse, and maintainability.
// utils.js - Export multiple items // Named exports export const PI = 3.14159; export function add(a, b) { return a + b; } export function multiply(a, b) { return a * b; } export class Calculator { add(a, b) { return a + b; } subtract(a, b) { return a - b; } } // Default export (one per file) export default function greet(name) { return `Hello, ${name}!`; }
// main.js - Import from module // Import default export import greet from './utils.js'; // Import named exports import { PI, add, multiply } from './utils.js'; // Import with alias import { Calculator as Calc } from './utils.js'; // Import everything as namespace import * as utils from './utils.js'; // Usage console.log(greet("Alice")); // Hello, Alice! console.log(add(5, 3)); // 8 console.log(PI); // 3.14159 const calc = new Calc(); console.log(calc.add(10, 5)); // 15 // Using namespace import console.log(utils.multiply(4, 5)); // 20
<!-- Add type="module" to use ES modules in browser --> <script type="module" src="main.js"></script>
Hello, Alice! 8 3.14159 15 20
💡 Module Best Practices
• One default export per file (main functionality)
• Use named exports for utilities and helpers
• Keep modules focused on single responsibility
• Use index.js to re-export from folders