COURSE ยท 10 LESSONS ยท 100% FREE
๐ŸŸก

JavaScript Mastery

Modern JS from basics to advanced โ€” 10 lessons covering variables, DOM, async, ES6+, OOP, Canvas, Node.js, and testing.

10lessons
250+code examples
Zeroprerequisites
01 JavaScript Basics
Variables (let/const), data types, operators, template literals, and core JS fundamentals.

Variables โ€” let, const, var

JavaScript has three ways to declare variables. let allows reassignment, const does not, and var is the legacy way โ€” avoid it in modern code.

// const โ€” cannot be reassigned (preferred for most cases)
const name = "Mayank";
const PI = 3.14159;

// let โ€” block-scoped, can be reassigned
let count = 0;
count = 1; // โœ… allowed

// var โ€” function-scoped, hoisted (avoid)
var legacy = "old style";
๐Ÿ’ก Always prefer const by default. Only use let when you know the value will change. Never use var in modern JS.

Data Types

JavaScript has 8 data types โ€” 7 primitives and Object.

// Primitives
const str = "Hello";          // string
const num = 42;               // number
const big = 9007199254740991n; // bigint
const flag = true;            // boolean
const empty = null;           // null
const nothing = undefined;    // undefined
const sym = Symbol("id");     // symbol

// Reference type
const obj = { name: "Mayank" }; // object
const arr = [1, 2, 3];          // array (also object)

// typeof operator
console.log(typeof str);    // "string"
console.log(typeof num);    // "number"
console.log(typeof obj);    // "object"
console.log(typeof null);   // "object" (historic bug!)
console.log(typeof sym);    // "symbol"

Operators

// Arithmetic
5 + 3   // 8
5 - 3   // 2
5 * 3   // 15
5 / 3   // 1.666...
5 % 3   // 2 (remainder)
5 ** 3  // 125 (exponentiation)

// Comparison
5 == "5"   // true  (loose โ€” type coercion!)
5 === "5"  // false (strict โ€” always prefer this)
null == undefined  // true
null === undefined // false

// Logical
true && false  // false (AND)
true || false  // true  (OR)
!true          // false (NOT)
??             // nullish coalescing (newer)
?.             // optional chaining (newer)

// Nullish coalescing
const input = null;
const value = input ?? "default"; // "default"
// vs || which treats 0 and "" as falsy
const val2 = 0 || "fallback";    // "fallback"
const val3 = 0 ?? "fallback";    // 0

Template Literals

const name = "Mayank";
const age = 15;

// String concatenation (old way)
const old = "My name is " + name + " and I am " + age + " years old.";

// Template literal (modern way)
const modern = `My name is ${name} and I am ${age} years old.`;

// Multi-line strings
const html = `
  <div class="card">
    <h2>${name}</h2>
    <p>Age: ${age}</p>
  </div>
`;

// Expressions inside ${}
const price = 9.99;
const msg = `Total: $${(price * 1.18).toFixed(2)}`;

Key Takeaways

  • Use const by default, let when reassignment is needed
  • Always use === instead of == for comparisons
  • Template literals (`${}`) replace string concatenation
  • typeof null returns "object" โ€” it's a known JS bug
  • ?? (nullish coalescing) is better than || for defaults
02 DOM Manipulation
Selecting elements, handling events, modifying styles, classes, and dataset attributes.

Selecting Elements

// Modern selectors (preferred)
const el = document.querySelector('.my-class');
const all = document.querySelectorAll('li.item');

// Classic selectors (still valid)
const byId = document.getElementById('app');
const byTag = document.getElementsByTagName('div');
const byClass = document.getElementsByClassName('card');

// querySelector is the most flexible โ€” CSS selectors work
const first = document.querySelector('#app > ul > li:first-child');
const links = document.querySelectorAll('a[href^="https"]');

Event Handling

const btn = document.querySelector('#submitBtn');

// addEventListener (preferred)
btn.addEventListener('click', (e) => {
  e.preventDefault();
  console.log('Clicked!', e.target);
});

// Common events
'click'      'dblclick'     'mouseenter'   'mouseleave'
'keydown'    'keyup'        'input'        'change'
'submit'     'focus'        'blur'         'scroll'
'load'       'resize'       'DOMContentLoaded'

// Event delegation (handle events on parent)
document.querySelector('ul').addEventListener('click', (e) => {
  if (e.target.tagName === 'LI') {
    console.log('List item clicked:', e.target.textContent);
  }
});
๐Ÿ’ก Event delegation is faster than adding listeners to every element. Handle events on a parent and check e.target.

Modifying DOM Elements

const card = document.querySelector('.card');

// Text & HTML
card.textContent = 'New text';
card.innerHTML = '<strong>Bold</strong> text';

// Attributes
card.setAttribute('id', 'main-card');
card.getAttribute('href');
card.removeAttribute('disabled');

// Dataset (data-* attributes)
// <div data-user-id="42" data-role="admin">
card.dataset.userId;   // "42"
card.dataset.role;     // "admin"
card.dataset.newKey = 'value'; // sets data-new-key

// Classes
card.classList.add('active');
card.classList.remove('hidden');
card.classList.toggle('selected');
card.classList.contains('active'); // true/false

// Styles
card.style.backgroundColor = '#1a1a2e';
card.style.color = '#fff';
card.style.cssText = 'padding: 1rem; margin: 0;';

// Create & append elements
const div = document.createElement('div');
div.className = 'alert';
div.textContent = 'Hello!';
document.body.appendChild(div);

// Remove element
card.remove();

Key Takeaways

  • querySelector / querySelectorAll are the most flexible selectors
  • Use dataset to read/write data-* attributes
  • classList.toggle() is great for show/hide patterns
  • Use event delegation instead of per-element listeners
  • .remove() deletes an element; .replaceChildren() replaces all children
03 Asynchronous JavaScript
Callbacks, Promises, async/await, Promise.all, and proper error handling.

The Event Loop

JavaScript is single-threaded. The event loop lets it handle async operations without blocking. The call stack runs synchronous code first, then processes the microtask queue (Promises), then the macrotask queue (setTimeout, etc.).

console.log('1');                // sync
setTimeout(() => console.log('2'), 0);  // macrotask
Promise.resolve().then(() => console.log('3')); // microtask
console.log('4');                // sync

// Output: 1, 4, 3, 2
// Sync code runs first, then microtasks, then macrotasks

Callbacks

// Callback pattern (old way)
function fetchData(url, callback) {
  setTimeout(() => {
    callback(null, { data: 'result' });
  }, 1000);
}

fetchData('/api/users', (err, data) => {
  if (err) console.error(err);
  console.log(data);
});

// Callback Hell (Christmas Tree Problem)
fetchUser(id, (user) => {
  fetchPosts(user.id, (posts) => {
    fetchComments(posts[0].id, (comments) => {
      // ๐Ÿ˜ฉ Deeply nested, hard to read/debug
    });
  });
});

Promises

// Creating a Promise
const promise = new Promise((resolve, reject) => {
  const success = true;
  if (success) resolve('Done!');
  else reject(new Error('Failed'));
});

// Chaining โ€” no more callback hell
fetchUser(id)
  .then(user => fetchPosts(user.id))
  .then(posts => fetchComments(posts[0].id))
  .then(comments => console.log(comments))
  .catch(err => console.error(err))  // catches any error in chain
  .finally(() => console.log('cleanup'));

// Promise.all โ€” run in parallel, wait for all
const [users, posts] = await Promise.all([
  fetch('/api/users').then(r => r.json()),
  fetch('/api/posts').then(r => r.json())
]);

// Promise.allSettled โ€” wait for all, regardless of success
const results = await Promise.allSettled([
  fetch('/api/fast'),
  fetch('/api/slow'),
  fetch('/api/fail')
]);
results.forEach(r => {
  if (r.status === 'fulfilled') console.log(r.value);
  else console.log(r.reason);
});

async/await

// async/await โ€” syntactic sugar over Promises
async function loadUser(id) {
  try {
    const res = await fetch(`/api/users/${id}`);
    if (!res.ok) throw new Error(`HTTP ${res.status}`);
    const user = await res.json();
    return user;
  } catch (err) {
    console.error('Failed to load user:', err);
    throw err;
  }
}

// Usage
const user = await loadUser(42);

// Parallel async operations
async function loadDashboard() {
  const [user, posts, notifications] = await Promise.all([
    loadUser(1),
    fetchPosts(),
    fetchNotifications()
  ]);
  return { user, posts, notifications };
}

// Async iteration
async function processItems(items) {
  for (const item of items) {
    await processItem(item); // sequential
  }
}

// For parallel
async function processAll(items) {
  await Promise.all(items.map(item => processItem(item)));
}
๐Ÿ’ก Always use try/catch with async/await. Unhandled promise rejections can crash Node.js apps and show warnings in browsers.

AbortController

// Cancel fetch requests
const controller = new AbortController();

fetch('/api/data', { signal: controller.signal })
  .then(res => res.json())
  .then(data => console.log(data))
  .catch(err => {
    if (err.name === 'AbortError') console.log('Request cancelled');
  });

// Cancel after 5 seconds
setTimeout(() => controller.abort(), 5000);

Key Takeaways

  • Promises replaced callback hell โ€” always return Promises
  • async/await makes async code read like synchronous code
  • Promise.all() for parallel operations, for...of with await for sequential
  • Microtasks (Promises) run before macrotasks (setTimeout)
  • Use AbortController to cancel fetch requests
04 Fetch API & HTTP
Making HTTP requests, handling JSON, error handling, abort controller, and CORS.

Basic Fetch

// GET request
const res = await fetch('https://api.example.com/users');
const data = await res.json();
console.log(data);

// POST with JSON body
const res2 = await fetch('https://api.example.com/users', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer token123'
  },
  body: JSON.stringify({
    name: 'Mayank',
    age: 15
  })
});

const created = await res2.json();
console.log(created);

Error Handling Patterns

// Pattern 1: Check res.ok
async function fetchData(url) {
  const res = await fetch(url);
  if (!res.ok) {
    throw new Error(`HTTP ${res.status}: ${res.statusText}`);
  }
  return res.json();
}

// Pattern 2: Wrapper function
async function safeFetch(url, options = {}) {
  try {
    const res = await fetch(url, options);
    if (!res.ok) {
      const error = await res.json().catch(() => ({}));
      throw new Error(error.message || `HTTP ${res.status}`);
    }
    return { data: await res.json(), error: null };
  } catch (err) {
    return { data: null, error: err.message };
  }
}

// Usage
const { data, error } = await safeFetch('/api/users');
if (error) console.error(error);

Request Options

fetch('/api/data', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Accept': 'application/json',
    'X-Custom-Header': 'value'
  },
  body: JSON.stringify({ key: 'value' }),
  mode: 'cors',           // 'cors' | 'no-cors' | 'same-origin'
  credentials: 'include', // 'include' sends cookies cross-origin
  cache: 'no-cache',      // 'default' | 'no-cache' | 'reload' | 'force-cache'
  redirect: 'follow',     // 'follow' | 'error' | 'manual'
  signal: controller.signal // AbortController
});

FormData & File Uploads

// FormData for file uploads
const form = document.querySelector('#uploadForm');
form.addEventListener('submit', async (e) => {
  e.preventDefault();
  const formData = new FormData(form);
  // or manually:
  // const formData = new FormData();
  // formData.append('file', fileInput.files[0]);
  // formData.append('name', 'My File');

  const res = await fetch('/api/upload', {
    method: 'POST',
    body: formData // browser sets Content-Type automatically
  });
});

Interceptor Pattern

// Reusable fetch wrapper with auth
async function apiFetch(url, options = {}) {
  const token = localStorage.getItem('token');
  const headers = {
    'Content-Type': 'application/json',
    ...options.headers
  };
  if (token) headers['Authorization'] = `Bearer ${token}`;

  const res = await fetch(url, { ...options, headers });

  if (res.status === 401) {
    localStorage.removeItem('token');
    window.location.href = '/login';
    throw new Error('Unauthorized');
  }

  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  return res.json();
}
๐Ÿ’ก fetch() only rejects on network failure, not HTTP errors (404, 500). Always check res.ok or res.status.

Key Takeaways

  • fetch() returns a Promise โ€” use await or .then()
  • Always check res.ok โ€” fetch doesn't reject on 404/500
  • Set Content-Type: application/json and use JSON.stringify() for POST
  • Use FormData for file uploads โ€” don't set Content-Type manually
  • Create wrapper functions for auth headers and error handling
05 ES6+ Features
Arrow functions, destructuring, spread/rest, modules, optional chaining, and modern syntax.

Arrow Functions

// Regular function
function add(a, b) {
  return a + b;
}

// Arrow function (concise body โ€” implicit return)
const add = (a, b) => a + b;

// Single parameter โ€” no parens needed
const double = x => x * 2;

// No parameters
const getTimestamp = () => Date.now();

// Multi-line body โ€” needs return
const process = (data) => {
  const cleaned = data.trim().toLowerCase();
  return { original: data, cleaned, length: cleaned.length };
};

// Arrow functions do NOT have their own 'this'
// They inherit 'this' from the enclosing scope
class Timer {
  constructor() {
    this.seconds = 0;
  }
  start() {
    // โš ๏ธ Regular function: 'this' would be the timer, but setTimeout callback loses it
    setInterval(() => {
      this.seconds++; // 'this' correctly refers to Timer instance
    }, 1000);
  }
}

Destructuring

// Object destructuring
const user = { name: 'Mayank', age: 15, city: 'Nagpur' };
const { name, age } = user; // name="Mayank", age=15

// Rename variables
const { name: userName, age: userAge } = user;

// Default values
const { role = 'student' } = user; // role="student"

// Nested destructuring
const data = {
  user: { profile: { name: 'Mayank' } }
};
const { user: { profile: { name } } } = data; // name="Mayank"

// Array destructuring
const [first, second, ...rest] = [1, 2, 3, 4, 5];
// first=1, second=2, rest=[3, 4, 5]

// Skip elements
const [, , third] = ['a', 'b', 'c']; // third='c'

// Swap variables
let a = 1, b = 2;
[a, b] = [b, a]; // a=2, b=1

// Function parameter destructuring
function greet({ name, age }) {
  return `Hello ${name}, you are ${age}!`;
}
greet({ name: 'Mayank', age: 15 });

Spread & Rest

// Spread operator (...) โ€” expands
const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5]; // [1, 2, 3, 4, 5]

const obj1 = { a: 1, b: 2 };
const obj2 = { ...obj1, c: 3 }; // { a: 1, b: 2, c: 3 }
// Shallow copy
const copy = { ...obj1 };

// Spread in function calls
const nums = [3, 1, 4, 1, 5];
Math.max(...nums); // 5

// Rest parameters โ€” collects into array
function sum(...numbers) {
  return numbers.reduce((total, n) => total + n, 0);
}
sum(1, 2, 3, 4); // 10

// Rest in destructuring
const [head, ...tail] = [1, 2, 3]; // tail=[2, 3]

Modules (import/export)

// math.js โ€” export named functions
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;
export const PI = 3.14159;

// Default export โ€” one per file
export default class Calculator {
  // ...
}

// app.js โ€” import
import Calculator, { add, subtract, PI } from './math.js';

// Import all
import * as Math from './math.js';
Math.add(1, 2);

// Dynamic import (code splitting)
const module = await import('./heavy-module.js');

Optional Chaining & More

// Optional chaining (?.) โ€” safe deep access
const user = { profile: { address: { city: 'Nagpur' } } };
const city = user?.profile?.address?.city; // "Nagpur"
const zip = user?.profile?.address?.zip;   // undefined (no error!)

// Optional chaining with methods
const result = arr?.map(x => x * 2); // undefined if arr is null/undefined

// Nullish coalescing (??) โ€” default for null/undefined only
const config = { timeout: 0 };
const timeout = config.timeout ?? 3000; // 0 (not 3000!)
const fallback = config.timeout || 3000; // 3000 (wrong!)

// Object shorthand
const name = 'Mayank';
const age = 15;
const person = { name, age }; // { name: 'Mayank', age: 15 }

// Computed property names
const key = 'score';
const obj = { [key]: 100 }; // { score: 100 }

// for...of (iterates values)
for (const item of [1, 2, 3]) console.log(item);

// for...in (iterates keys โ€” for objects)
for (const key in { a: 1, b: 2 }) console.log(key);
๐Ÿ’ก Arrow functions don't have their own this, arguments, or super. They're great for callbacks but not for object methods or constructors.

Key Takeaways

  • Arrow functions are concise but lack their own this
  • Destructuring simplifies extracting values from objects/arrays
  • Spread copies/expands, Rest collects โ€” same syntax, different use
  • ES Modules: import/export โ€” one default, many named per file
  • Optional chaining (?.) prevents TypeError on null/undefined
06 Classes & OOP
Class syntax, inheritance, private fields, static methods, and OOP patterns in JS.

Class Basics

class Animal {
  // Public field
  species;

  // Private field (#)
  #age;

  // Constructor
  constructor(name, age) {
    this.name = name;
    this.#age = age;
    this.species = 'Unknown';
  }

  // Public method
  speak() {
    return `${this.name} makes a sound`;
  }

  // Getter
  get age() {
    return this.#age;
  }

  // Setter with validation
  set age(value) {
    if (value < 0) throw new Error('Age cannot be negative');
    this.#age = value;
  }

  // Static method
  static create(name, age) {
    return new Animal(name, age);
  }

  // Static property
  static kingdom = 'Animalia';
}

const cat = new Animal('Cat', 3);
console.log(cat.speak());  // "Cat makes a sound"
console.log(cat.age);      // 3 (getter)
cat.age = 5;               // setter
console.log(Animal.kingdom); // "Animalia"

Inheritance

class Dog extends Animal {
  constructor(name, age, breed) {
    super(name, age); // call parent constructor
    this.breed = breed;
  }

  // Override parent method
  speak() {
    return `${this.name} barks!`;
  }

  // New method
  fetch(item) {
    return `${this.name} fetches the ${item}`;
  }
}

const rex = new Dog('Rex', 5, 'German Shepherd');
console.log(rex.speak());       // "Rex barks!"
console.log(rex.fetch('ball')); // "Rex fetches the ball"
console.log(rex.species);      // "Unknown" (inherited)

Static Methods & Factory Pattern

class User {
  #email;
  #password;

  constructor(email, password) {
    this.#email = email;
    this.#password = password;
    this.createdAt = new Date();
  }

  // Factory method โ€” alternative to constructor
  static fromJSON(json) {
    const data = JSON.parse(json);
    return new User(data.email, data.password);
  }

  // Static method for validation
  static validateEmail(email) {
    return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
  }

  checkPassword(password) {
    return this.#password === password;
  }
}

// Usage
const user = User.fromJSON('{"email":"m@test.com","pw":"123"}');
console.log(User.validateEmail('test@test.com')); // true

Mixins (Multiple Inheritance)

// JS doesn't support multiple inheritance, but mixins work
const Serializable = (superclass) => class extends superclass {
  serialize() {
    return JSON.stringify(this);
  }
};

const Loggable = (superclass) => class extends superclass {
  log() {
    console.log(this.toString());
  }
};

class BaseModel {
  constructor(id) { this.id = id; }
}

// Mixin chain
class User extends Loggable(Serializable(BaseModel)) {
  constructor(id, name) {
    super(id);
    this.name = name;
  }
}

const u = new User(1, 'Mayank');
u.serialize(); // '{"id":1,"name":"Mayank"}'
u.log();       // User { id: 1, name: 'Mayank' }
๐Ÿ’ก Private fields (#field) are truly private โ€” they can't be accessed from outside the class. Use them instead of the _prefix convention.

Key Takeaways

  • Use # for truly private fields and methods
  • extends + super() for inheritance
  • Static methods are called on the class, not instances
  • Getters/setters let you control property access
  • Mixins solve the "need multiple inheritance" problem
07 Web Storage
localStorage, sessionStorage, cookies, IndexedDB, and when to use each.

localStorage & sessionStorage

// localStorage โ€” persists until manually cleared
localStorage.setItem('theme', 'dark');
localStorage.getItem('theme');  // "dark"
localStorage.removeItem('theme');
localStorage.clear();

// Store objects โ€” must serialize
const user = { name: 'Mayank', age: 15 };
localStorage.setItem('user', JSON.stringify(user));
const stored = JSON.parse(localStorage.getItem('user'));

// sessionStorage โ€” cleared when tab closes
sessionStorage.setItem('token', 'abc123');

// Helper wrapper
const Storage = {
  set(key, value, type = 'local') {
    const store = type === 'local' ? localStorage : sessionStorage;
    store.setItem(key, JSON.stringify(value));
  },
  get(key, type = 'local') {
    const store = type === 'local' ? localStorage : sessionStorage;
    const item = store.getItem(key);
    return item ? JSON.parse(item) : null;
  },
  remove(key, type = 'local') {
    const store = type === 'local' ? localStorage : sessionStorage;
    store.removeItem(key);
  }
};

Storage.set('user', { name: 'Mayank' });
Storage.get('user'); // { name: 'Mayank' }

Cookies

// Set cookie (basic โ€” no helper)
document.cookie = "username=Mayank; expires=Fri, 31 Dec 2026 23:59:59 GMT; path=/";

// Read all cookies
console.log(document.cookie); // "username=Mayank; theme=dark"

// Cookie options
document.cookie = "token=abc123; path=/; max-age=86400; SameSite=Strict; Secure";

// Delete cookie
document.cookie = "token=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/";

// Better cookie helper
const Cookie = {
  set(name, value, days = 7) {
    const expires = new Date(Date.now() + days * 864e5).toUTCString();
    document.cookie = `${name}=${encodeURIComponent(value)}; expires=${expires}; path=/; SameSite=Strict`;
  },
  get(name) {
    return document.cookie
      .split('; ')
      .find(row => row.startsWith(name + '='))
      ?.split('=')[1]
      ?.decodeURIComponent();
  },
  remove(name) {
    document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`;
  }
};

IndexedDB

// IndexedDB โ€” for large/structured data
const DB_NAME = 'MyApp';
const DB_VERSION = 1;

function openDB() {
  return new Promise((resolve, reject) => {
    const request = indexedDB.open(DB_NAME, DB_VERSION);

    request.onupgradeneeded = (e) => {
      const db = e.target.result;
      if (!db.objectStoreNames.contains('users')) {
        const store = db.createObjectStore('users', { keyPath: 'id', autoIncrement: true });
        store.createIndex('name', 'name', { unique: false });
      }
    };

    request.onsuccess = (e) => resolve(e.target.result);
    request.onerror = (e) => reject(e.target.error);
  });
}

async function addUser(user) {
  const db = await openDB();
  const tx = db.transaction('users', 'readwrite');
  const store = tx.objectStore('users');
  store.add(user);
  return new Promise((resolve, reject) => {
    tx.oncomplete = () => resolve();
    tx.onerror = () => reject(tx.error);
  });
}

async function getAllUsers() {
  const db = await openDB();
  const tx = db.transaction('users', 'readonly');
  const store = tx.objectStore('users');
  return new Promise((resolve, reject) => {
    const request = store.getAll();
    request.onsuccess = () => resolve(request.result);
    request.onerror = () => reject(request.error);
  });
}
๐Ÿ’ก Use localStorage for small preferences (theme, settings). Use IndexedDB for large/complex data. Cookies are for server-readable data (auth tokens). All web storage is same-origin.

When to Use What

  • localStorage โ€” persistent, client-only, ~5MB, synchronous
  • sessionStorage โ€” per-tab, cleared on close, ~5MB
  • Cookies โ€” sent to server with every request, ~4KB, has expiry
  • IndexedDB โ€” large data, async, structured, ~unlimited
  • Cache API โ€” service worker caching, offline support
08 Canvas & Graphics
2D canvas context, drawing shapes, animations with requestAnimationFrame, and Three.js intro.

Canvas Setup

const canvas = document.querySelector('#myCanvas');
const ctx = canvas.getContext('2d');

// Set canvas size
canvas.width = 800;
canvas.height = 600;

// Draw a rectangle
ctx.fillStyle = '#6c63ff';
ctx.fillRect(50, 50, 200, 100);

// Draw a circle
ctx.beginPath();
ctx.arc(400, 300, 50, 0, Math.PI * 2);
ctx.fillStyle = '#00e676';
ctx.fill();
ctx.strokeStyle = '#fff';
ctx.lineWidth = 2;
ctx.stroke();

// Draw text
ctx.font = '24px Space Grotesk';
ctx.fillStyle = '#ffffff';
ctx.fillText('Hello Canvas!', 50, 400);

// Draw a line
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(800, 600);
ctx.strokeStyle = 'rgba(255,255,255,0.3)';
ctx.stroke();

Animation with requestAnimationFrame

// Smooth animation loop
const canvas = document.querySelector('#animCanvas');
const ctx = canvas.getContext('2d');
canvas.width = 800;
canvas.height = 400;

let x = 0;
let y = 200;
let dx = 4;
let radius = 20;

function animate() {
  // Clear canvas
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  // Draw ball
  ctx.beginPath();
  ctx.arc(x, y, radius, 0, Math.PI * 2);
  ctx.fillStyle = '#6c63ff';
  ctx.fill();

  // Update position
  x += dx;

  // Bounce off walls
  if (x + radius > canvas.width || x - radius < 0) {
    dx = -dx;
  }

  requestAnimationFrame(animate);
}

animate();

Particle System Example

class Particle {
  constructor(x, y) {
    this.x = x;
    this.y = y;
    this.vx = (Math.random() - 0.5) * 4;
    this.vy = (Math.random() - 0.5) * 4;
    this.life = 1;
    this.decay = Math.random() * 0.02 + 0.005;
    this.size = Math.random() * 3 + 1;
  }

  update() {
    this.x += this.vx;
    this.y += this.vy;
    this.life -= this.decay;
  }

  draw(ctx) {
    ctx.globalAlpha = this.life;
    ctx.fillStyle = '#6c63ff';
    ctx.beginPath();
    ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
    ctx.fill();
    ctx.globalAlpha = 1;
  }
}

const particles = [];

canvas.addEventListener('click', (e) => {
  for (let i = 0; i < 20; i++) {
    particles.push(new Particle(e.offsetX, e.offsetY));
  }
});

function animate() {
  ctx.fillStyle = 'rgba(5, 5, 8, 0.1)';
  ctx.fillRect(0, 0, canvas.width, canvas.height);

  for (let i = particles.length - 1; i >= 0; i--) {
    particles[i].update();
    particles[i].draw(ctx);
    if (particles[i].life <= 0) particles.splice(i, 1);
  }

  requestAnimationFrame(animate);
}

Three.js Quick Start

// Three.js โ€” 3D graphics library
// Include: <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>

// Scene setup
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// Create a cube
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshPhongMaterial({ color: 0x6c63ff });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);

// Add light
const light = new THREE.PointLight(0xffffff, 1, 100);
light.position.set(5, 5, 5);
scene.add(light);

// Camera position
camera.position.z = 3;

// Animation loop
function animate() {
  requestAnimationFrame(animate);
  cube.rotation.x += 0.01;
  cube.rotation.y += 0.01;
  renderer.render(scene, camera);
}

animate();
๐Ÿ’ก Always call requestAnimationFrame for smooth animations โ€” it syncs with the display refresh rate (usually 60fps). Use ctx.clearRect() before each frame to avoid trails.

Key Takeaways

  • Canvas 2D is great for 2D games, charts, and visualizations
  • requestAnimationFrame is the correct way to animate in browsers
  • Three.js makes 3D graphics accessible with scene/camera/renderer pattern
  • Always clear the canvas each frame or use semi-transparent clear for trails
  • Canvas is pixel-based โ€” for SVG-like shapes, use the actual SVG API
09 Node.js Basics
CommonJS vs ES Modules, fs, http module, npm, and Express.js basics.

CommonJS vs ES Modules

// CommonJS (Node.js default)
// math.js
const add = (a, b) => a + b;
module.exports = { add };

// app.js
const { add } = require('./math.js');

// ES Modules (add "type": "module" to package.json)
// math.js
export const add = (a, b) => a + b;

// app.js
import { add } from './math.js';

File System (fs)

import fs from 'fs/promises'; // async (preferred)
import fsSync from 'fs';      // sync

// Read file (async)
const data = await fs.readFile('file.txt', 'utf-8');

// Write file
await fs.writeFile('output.txt', 'Hello World');

// Append
await fs.appendFile('log.txt', 'New line\n');

// Check if file exists
const exists = await fs.access('file.txt').then(() => true).catch(() => false);

// List directory
const files = await fs.readdir('./src');

// Create directory
await fs.mkdir('new-dir', { recursive: true });

// Delete file
await fs.unlink('temp.txt');

// Rename/move
await fs.rename('old.txt', 'new.txt');

// Get file info
const stats = await fs.stat('file.txt');
console.log(stats.size, stats.mtime);

HTTP Module (vanilla)

import http from 'http';

const server = http.createServer((req, res) => {
  if (req.url === '/' && req.method === 'GET') {
    res.writeHead(200, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ message: 'Hello World' }));
  } else if (req.url === '/users' && req.method === 'POST') {
    let body = '';
    req.on('data', chunk => body += chunk);
    req.on('end', () => {
      const user = JSON.parse(body);
      res.writeHead(201, { 'Content-Type': 'application/json' });
      res.end(JSON.stringify({ id: 1, ...user }));
    });
  } else {
    res.writeHead(404);
    res.end('Not Found');
  }
});

server.listen(3000, () => console.log('Server running on port 3000'));

npm Basics

# Initialize project
npm init -y

# Install dependencies
npm install express
npm install -D nodemon  # dev dependency

# Scripts in package.json
{
  "scripts": {
    "start": "node server.js",
    "dev": "nodemon server.js",
    "test": "jest"
  }
}

# Run scripts
npm start
npm run dev

# Global install
npm install -g typescript

# Lock file โ€” never delete package-lock.json
# node_modules โ€” add to .gitignore

Express.js Basics

import express from 'express';
const app = express();

// Middleware
app.use(express.json()); // parse JSON bodies
app.use(express.urlencoded({ extended: true }));

// CORS middleware
app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin', '*');
  res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
  next();
});

// Routes
app.get('/api/users', (req, res) => {
  res.json([{ id: 1, name: 'Mayank' }]);
});

app.get('/api/users/:id', (req, res) => {
  const user = users.find(u => u.id === parseInt(req.params.id));
  if (!user) return res.status(404).json({ error: 'Not found' });
  res.json(user);
});

app.post('/api/users', (req, res) => {
  const { name, email } = req.body;
  const user = { id: users.length + 1, name, email };
  users.push(user);
  res.status(201).json(user);
});

app.put('/api/users/:id', (req, res) => {
  const user = users.find(u => u.id === parseInt(req.params.id));
  if (!user) return res.status(404).json({ error: 'Not found' });
  Object.assign(user, req.body);
  res.json(user);
});

app.delete('/api/users/:id', (req, res) => {
  const index = users.findIndex(u => u.id === parseInt(req.params.id));
  if (index === -1) return res.status(404).json({ error: 'Not found' });
  users.splice(index, 1);
  res.status(204).end();
});

// Error handling middleware
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).json({ error: 'Something went wrong!' });
});

app.listen(3000, () => console.log('API running on http://localhost:3000'));
๐Ÿ’ก Use express.json() middleware before routes to parse JSON request bodies. Always validate user input โ€” never trust req.body.

Key Takeaways

  • ES Modules (import/export) are the modern standard โ€” add "type": "module"
  • fs/promises (async) over fs (sync) โ€” don't block the event loop
  • npm is the package manager โ€” always commit package-lock.json
  • Express: routes, middleware, req.params, req.query, req.body
  • Use nodemon in development for auto-restart on file changes
10 Testing
Jest, Vitest, unit tests, mocking, describe/it/expect, and testing best practices.

Why Test?

Tests catch bugs before they reach production. They serve as documentation and enable safe refactoring. A good test suite gives you confidence to change code without breaking things.

Jest / Vitest Setup

# Jest
npm install -D jest
# package.json: "test": "jest"

# Vitest (faster, ESM-friendly)
npm install -D vitest
# package.json: "test": "vitest"

# Run tests
npm test
npm test -- --watch     # watch mode
npm test -- --coverage  # with coverage report

Basic Tests

// math.js
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;
export const divide = (a, b) => {
  if (b === 0) throw new Error('Cannot divide by zero');
  return a / b;
};

// math.test.js
import { describe, it, expect } from 'vitest'; // vitest
// For Jest: just use global describe/it/expect

describe('Math operations', () => {
  it('should add two numbers', () => {
    expect(add(2, 3)).toBe(5);
  });

  it('should subtract two numbers', () => {
    expect(subtract(5, 3)).toBe(2);
  });

  it('should throw on divide by zero', () => {
    expect(() => divide(10, 0)).toThrow('Cannot divide by zero');
  });

  it('should handle negative numbers', () => {
    expect(add(-1, -1)).toBe(-2);
    expect(subtract(-1, -1)).toBe(0);
  });
});

Matchers

// Equality
expect(value).toBe(42);           // strict equality (===)
expect(value).toEqual({ a: 1 });  // deep equality

// Truthiness
expect(value).toBeTruthy();
expect(value).toBeFalsy();
expect(value).toBeNull();
expect(value).toBeUndefined();
expect(value).toBeDefined();

// Numbers
expect(value).toBeGreaterThan(5);
expect(value).toBeLessThanOrEqual(10);
expect(value).toBeCloseTo(3.14, 1); // precision

// Strings
expect(str).toMatch(/regex/);
expect(str).toContain('sub');

// Arrays
expect(arr).toHaveLength(3);
expect(arr).toContain('item');
expect(arr).toEqual(expect.arrayContaining([1, 2]));

// Objects
expect(obj).toHaveProperty('name');
expect(obj).toHaveProperty('name', 'Mayank');
expect(obj).toMatchObject({ name: 'Mayank' });

// Async
await expect(promise).resolves.toBe(value);
await expect(promise).rejects.toThrow(error);

Mocking

// Mock a function
const mockFn = vi.fn(); // vitest
// const mockFn = jest.fn(); // jest

mockFn('hello');
expect(mockFn).toHaveBeenCalledWith('hello');
expect(mockFn).toHaveBeenCalledTimes(1);

// Mock return values
mockFn.mockReturnValue(42);
expect(mockFn()).toBe(42);

// Mock implementation
mockFn.mockImplementation((a, b) => a + b);
expect(mockFn(2, 3)).toBe(5);

// Mock a module
vi.mock('./api.js'); // vitest
// jest.mock('./api.js');

import { fetchUser } from './api.js';
fetchUser.mockResolvedValue({ id: 1, name: 'Mayank' });

// Spy on object method
const spy = vi.spyOn(obj, 'method');
obj.method();
expect(spy).toHaveBeenCalled();

Testing Async Code

// Testing async functions
describe('API', () => {
  it('should fetch user data', async () => {
    const user = await fetchUser(1);
    expect(user).toHaveProperty('name');
    expect(user.name).toBe('Mayank');
  });

  it('should handle API errors', async () => {
    await expect(fetchUser(999)).rejects.toThrow('User not found');
  });
});

// Testing with beforeEach/afterEach
describe('Counter', () => {
  let counter;

  beforeEach(() => {
    counter = new Counter();
  });

  it('starts at 0', () => {
    expect(counter.count).toBe(0);
  });

  it('increments', () => {
    counter.increment();
    expect(counter.count).toBe(1);
  });

  it('resets', () => {
    counter.increment();
    counter.increment();
    counter.reset();
    expect(counter.count).toBe(0);
  });
});

Testing React Components (Bonus)

// Using React Testing Library
import { render, screen, fireEvent } from '@testing-library/react';

it('renders correctly', () => {
  render(<Button label="Click me" />);
  expect(screen.getByText('Click me')).toBeInTheDocument();
});

it('calls onClick when clicked', () => {
  const handleClick = vi.fn();
  render(<Button label="Click" onClick={handleClick} />);
  fireEvent.click(screen.getByText('Click'));
  expect(handleClick).toHaveBeenCalledTimes(1);
});
๐Ÿ’ก Write tests that describe behavior, not implementation. Test what the code does, not how it does it. This makes tests resilient to refactoring.

Testing Best Practices

  • Follow AAA: Arrange โ†’ Act โ†’ Assert
  • One assertion per test (when possible)
  • Test edge cases: null, empty, boundary values
  • Mock external dependencies (APIs, databases)
  • Use describe blocks to group related tests
  • Keep tests fast โ€” avoid real network calls
  • Aim for meaningful coverage, not 100% line coverage

Key Takeaways

  • Jest and Vitest are the most popular JS test frameworks
  • describe/it/expect are the core testing primitives
  • Mock functions and modules to isolate code under test
  • Use beforeEach/afterEach for setup and cleanup
  • Tests are an investment โ€” they save time debugging later

๐Ÿ“ JavaScript Quirks & Common Patterns

The Weird Parts

// Type coercion oddities
[] + []          // "" (empty string)
[] + {}          // "[object Object]"
{} + []          // 0 (in some contexts)
true + true      // 2
"5" - 3          // 2 (string โ†’ number)
"5" + 3          // "53" (number โ†’ string)

// Hoisting
var x = 1;
var y = 2;
console.log(x + y); // 3
// var is hoisted to top of function

// let/const are hoisted but not initialized (TDZ)
// console.log(z); // ReferenceError (Temporal Dead Zone)
let z = 1;

// Closures
function counter() {
  let count = 0;
  return {
    increment: () => ++count,
    getCount: () => count
  };
}
const c = counter();
c.increment(); // 1
c.increment(); // 2
c.getCount();  // 2

Common Patterns

// Debounce โ€” delay execution until user stops typing
function debounce(fn, ms = 300) {
  let timer;
  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => fn(...args), ms);
  };
}

// Throttle โ€” execute at most once per interval
function throttle(fn, ms = 300) {
  let last = 0;
  return (...args) => {
    const now = Date.now();
    if (now - last >= ms) {
      last = now;
      fn(...args);
    }
  };
}

// Memoize โ€” cache results
function memoize(fn) {
  const cache = new Map();
  return (...args) => {
    const key = JSON.stringify(args);
    if (!cache.has(key)) cache.set(key, fn(...args));
    return cache.get(key);
  };
}

// Deep clone (simple)
const clone = structuredClone(obj); // modern way
// or
const clone2 = JSON.parse(JSON.stringify(obj)); // no functions/dates

// Flatten array
const flat = [1, [2, [3, [4]]]].flat(Infinity); // [1, 2, 3, 4]

Useful Array Methods

const nums = [1, 2, 3, 4, 5];

// map โ€” transform each element
nums.map(n => n * 2); // [2, 4, 6, 8, 10]

// filter โ€” keep elements that pass test
nums.filter(n => n > 3); // [4, 5]

// reduce โ€” accumulate into single value
nums.reduce((sum, n) => sum + n, 0); // 15

// find โ€” first match
nums.find(n => n > 3); // 4

// some/every โ€” boolean checks
nums.some(n => n > 4);  // true
nums.every(n => n > 0); // true

// flatMap โ€” map + flatten one level
['hello world', 'foo bar'].flatMap(s => s.split(' '));
// ['hello', 'world', 'foo', 'bar']

// at() โ€” negative indexing
nums.at(-1);  // 5
nums.at(-2);  // 4

Useful Object Methods

const obj = { a: 1, b: 2, c: 3 };

Object.keys(obj);    // ['a', 'b', 'c']
Object.values(obj);  // [1, 2, 3]
Object.entries(obj); // [['a', 1], ['b', 2], ['c', 3]]

// fromEntries โ€” reverse of entries
Object.fromEntries([['a', 1], ['b', 2]]); // { a: 1, b: 2 }

// Freeze โ€” prevent mutations (shallow)
Object.freeze(obj);
obj.a = 99; // fails silently (strict mode: error)

// assign โ€” merge objects (mutates first!)
Object.assign({}, defaults, userSettings);

// pick/omit helpers
const pick = (obj, keys) =>
  Object.fromEntries(keys.filter(k => k in obj).map(k => [k, obj[k]]));

const omit = (obj, keys) =>
  Object.fromEntries(Object.entries(obj).filter(([k]) => !keys.includes(k)));

๐Ÿ“š Resources & Further Learning

๐Ÿ“–
MDN Web Docs
The definitive reference for JavaScript and web APIs.
developer.mozilla.org โ†’
๐Ÿ“˜
JavaScript.info
Modern JavaScript tutorial โ€” from basics to advanced with interactive examples.
javascript.info โ†’
๐Ÿ”
Can I Use
Browser compatibility tables for JavaScript and web features.
caniuse.com โ†’
โšก
V8 Docs
Google's JavaScript engine โ€” how JS works under the hood.
v8.dev โ†’
๐ŸŽฏ
Node.js Docs
Official Node.js documentation โ€” APIs, guides, and best practices.
nodejs.org โ†’
๐Ÿงช
Vitest Docs
Blazing fast unit testing framework โ€” Jest compatible, native ESM.
vitest.dev โ†’
AI
JavaScript Tutor
Nemotron 120B
Ask me anything about JavaScript! I can help with code examples, debugging, best practices, or explain any concept from the lessons above.