What is JavaScript?
JavaScript (JS) is the only programming language that runs natively in every web browser. It's what makes web pages interactive — clicking buttons, fetching data, animating elements, validating forms.
Analogy: HTML is the skeleton, CSS is the skin, JavaScript is the muscles
and nervous system — it makes things move and respond.
JavaScript was created in 10 days in 1995 by Brendan Eich at Netscape. Originally a quick scripting language, it's now used for:
- Browser interactivity (its original home)
- Server-side development (Node.js — Level 7)
- Mobile apps (React Native)
- Desktop apps (Electron)
- Machine learning (TensorFlow.js)
Variables: let, const, var
// var — old, function-scoped, avoid in modern code
var name = "Alice";
// let — block-scoped, reassignable (use for values that change)
let count = 0;
count = 1; // ✅
// const — block-scoped, NOT reassignable (use by default)
const API_URL = "https://api.example.com";
API_URL = "other"; // ❌ TypeError
// const objects and arrays can be mutated (just not reassigned)
const user = { name: "Alice" };
user.name = "Bob"; // ✅ fine — we're mutating, not reassigning
user = {}; // ❌ TypeError
Rule of thumb: Always use const. If you need to reassign, switch to let. Never use var.
Data Types
JavaScript has 7 primitive types + objects:
// Primitives (immutable, compared by value)
let str = "hello"; // string
let num = 42; // number (JS has one number type — floats and ints)
let big = 9007199254740993n; // BigInt (for very large integers)
let bool = true; // boolean
let nothing = null; // intentional absence of value
let missing = undefined; // variable declared but not assigned
let sym = Symbol("id"); // unique identifier (advanced)
// Objects (mutable, compared by reference)
let obj = { key: "value" };
let arr = [1, 2, 3]; // arrays are objects
let fn = function() {}; // functions are objects
Type coercion gotcha — JS silently converts types, which causes bugs:
"5" + 3 // "53" (string concatenation!)
"5" - 3 // 2 (numeric subtraction)
0 == false // true
0 === false// false ← always use === (strict equality)
Always use === (strict equality) — it checks both value AND type.
Functions
// Function declaration (hoisted — can be called before its definition)
function greet(name) {
return `Hello, ${name}!`;
}
// Function expression
const greet = function(name) {
return `Hello, ${name}!`;
};
// Arrow function (concise; does NOT have its own `this`)
const greet = (name) => `Hello, ${name}!`;
// Default parameters
const connect = (host = "localhost", port = 3000) => `${host}:${port}`;
// Rest parameters (...args collects remaining args into array)
const sum = (...nums) => nums.reduce((a, b) => a + b, 0);
sum(1, 2, 3, 4); // 10
// Spread operator (opposite: expands an array/object)
const nums = [1, 2, 3];
Math.max(...nums); // 3
const copy = [...nums]; // [1, 2, 3] — shallow copy
Closures — The Most Important Concept in JS
A closure is a function that "remembers" the variables from its surrounding scope even after that scope has finished executing.
function makeCounter() {
let count = 0; // This variable lives in makeCounter's scope
return function() {
count++; // The inner function "closes over" count
return count;
};
}
const counter = makeCounter(); // makeCounter finishes executing
counter(); // 1 — but count is still alive inside the closure!
counter(); // 2
counter(); // 3
const counter2 = makeCounter();
counter2(); // 1 — independent closure, own count
Why closures matter:
- Data privacy —
countis inaccessible from outside; only the returned function can touch it - Factory functions — create customized functions
- Memoization — cache previous results (see Dynamic Programming)
- Event handlers — retain access to variables when an event fires later
// Classic closure bug in loops (with var)
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// Prints: 3 3 3 (all share the same var i)
// Fix 1: use let (block-scoped, creates new binding each iteration)
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// Prints: 0 1 2 ✅
this — The Most Confusing Part of JS
this refers to the object that invoked the function, not where the function was defined.
const user = {
name: "Alice",
greet() {
console.log(`Hi, I'm ${this.name}`); // this = user
}
};
user.greet(); // "Hi, I'm Alice" ✅
// Losing `this`
const fn = user.greet;
fn(); // "Hi, I'm undefined" ❌ — this = global object (or undefined in strict mode)
// Fix: arrow functions don't have their own `this` — they inherit from enclosing scope
const user2 = {
name: "Bob",
greet: () => console.log(`Hi, I'm ${this.name}`)
};
user2.greet(); // "Hi, I'm undefined" ❌ — arrow functions capture outer `this`, not user2
// Fix for class methods: bind, or use regular functions as methods
class User {
constructor(name) { this.name = name; }
greet() { return `Hi, I'm ${this.name}`; } // regular function as method ✅
}
Rule: use regular functions for methods (they get this from the caller), arrow functions for callbacks (they inherit this from the enclosing scope).
Arrays & Objects — Modern Syntax
// Destructuring
const { name, age = 25 } = user; // object destructuring (with default)
const [first, , third] = [10, 20, 30]; // array destructuring (skip index 1)
// Spread & rest
const merged = { ...obj1, ...obj2 }; // merge objects
const clone = [...arr]; // shallow copy array
// Optional chaining — no more "Cannot read property of undefined"
const city = user?.address?.city; // undefined if any step is null/undefined
const first = users?.[0]?.name;
// Nullish coalescing
const name = user.name ?? "Anonymous"; // use right side only if left is null/undefined
// vs logical OR (||): uses right side if left is falsy (0, "", false)
const count = data.count ?? 0; // 0 wins; data.count || 0 would also be 0
The Event Loop — How JS Is Single-Threaded but Non-Blocking
JavaScript runs on one thread — it can only execute one thing at a time. But it handles I/O (network, timers, files) without blocking through the event loop.
┌─────────────────────────────────────────┐
│ Call Stack │ ← synchronous code runs here
│ (runs one function at a time) │
└─────────────────────────────────────────┘
↑ when stack empty
┌─────────────────────────────────────────┐
│ Callback Queue │ ← setTimeout, setInterval callbacks
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│ Microtask Queue │ ← Promise .then(), await (runs FIRST)
└─────────────────────────────────────────┘
Order of execution: synchronous → microtasks → callback queue
console.log("1");
setTimeout(() => console.log("2"), 0); // callback queue
Promise.resolve().then(() => console.log("3")); // microtask queue
console.log("4");
// Output: 1, 4, 3, 2
// Why: sync (1, 4) → microtasks (3) → callbacks (2)
Promises & Async/Await
A Promise represents a value that will be available in the future (or an error).
// Creating a Promise
function fetchUser(id) {
return new Promise((resolve, reject) => {
// Simulating async work (HTTP request, file read, etc.)
setTimeout(() => {
if (id > 0) resolve({ id, name: "Alice" });
else reject(new Error("Invalid ID"));
}, 1000);
});
}
// Consuming with .then()/.catch() (older, but good to know)
fetchUser(1)
.then(user => console.log(user.name)) // Alice
.catch(err => console.error(err.message));
// Consuming with async/await (modern, preferred — reads like synchronous code)
async function main() {
try {
const user = await fetchUser(1); // pauses until promise resolves
console.log(user.name); // Alice
} catch (err) {
console.error(err.message);
}
}
Parallel vs sequential async:
// Sequential (slow — each waits for the previous)
const user = await fetchUser(1);
const posts = await fetchPosts(1); // waits for fetchUser to finish first
// Parallel (fast — both start simultaneously)
const [user, posts] = await Promise.all([fetchUser(1), fetchPosts(1)]);
Use Promise.all when operations are independent. Use sequential await when one depends on the other.
DOM Manipulation
The DOM (Document Object Model) is the tree of HTML elements that JavaScript can read and modify.
// Selecting elements
const header = document.getElementById("main-header");
const buttons = document.querySelectorAll(".btn"); // returns NodeList
const first = document.querySelector(".card"); // first match
// Reading and writing
header.textContent = "New Title"; // safe text (no HTML parsing)
header.innerHTML = "<strong>Bold</strong>"; // renders HTML (XSS risk if user input!)
header.style.color = "red";
// Classes
element.classList.add("active");
element.classList.remove("active");
element.classList.toggle("active");
element.classList.contains("active"); // boolean
// Attributes
input.getAttribute("placeholder");
input.setAttribute("disabled", "");
input.removeAttribute("disabled");
// Creating and inserting elements
const p = document.createElement("p");
p.textContent = "New paragraph";
document.body.appendChild(p);
parent.insertBefore(p, referenceNode);
Events
// addEventListener (preferred — can add multiple listeners)
button.addEventListener("click", function(event) {
event.preventDefault(); // stop default behavior (e.g., form submission)
event.stopPropagation(); // stop event bubbling up to parent elements
console.log("Clicked!", event.target);
});
// Event delegation — attach one listener to a parent, not many to children
// Efficient for dynamic lists
document.getElementById("todo-list").addEventListener("click", (event) => {
if (event.target.matches(".delete-btn")) {
event.target.closest("li").remove();
}
});
Event bubbling: events propagate up the DOM tree (child → parent → document). This is why event delegation works.
Modules (ES Modules)
Modern JS splits code into modules — files that explicitly export and import things.
// utils.js
export function formatDate(date) {
return date.toLocaleDateString();
}
export const MAX_ITEMS = 100;
export default class Logger { /* ... */ } // one default export per file
// main.js
import Logger from './utils.js'; // default import
import { formatDate, MAX_ITEMS } from './utils.js'; // named imports
import { formatDate as fd } from './utils.js'; // aliased import
import * as utils from './utils.js'; // namespace import
Modules are loaded in browsers via <script type="module"> and in Node.js natively (.mjs or "type": "module" in package.json).
Common Interview Questions
Practice
- Beginner: Write a closure-based bank account with
deposit(amount),withdraw(amount), andbalance()methods — balance should be private. - Core: Implement
debounce(fn, delay)— a function that delays callingfnuntildelaymilliseconds after the last call. Use it for search input. - Async: Fetch a list of users from
https://jsonplaceholder.typicode.com/usersand display their names in the DOM, with loading and error states. - Event delegation: Build a dynamic TODO list where items can be added and deleted, using only one event listener on the parent
<ul>.
Next: TypeScript — typed JavaScript for large-scale applications.