JavaScript • 2026-10-10

JavaScript Tutorial: Variables, Functions, DOM, Async Code, and APIs

A practical JavaScript guide covering modern syntax, functions, arrays and objects, the DOM, events, promises, async/await, fetch, modules, classes, storage, and best practices.

A practical JavaScript guide covering modern syntax, functions, arrays and objects, the DOM, events, promises, async/await, fetch, modules, classes, storage, and best practices.

Developer workspace with code on a screen
Build your programming knowledge one practical example at a time.

What Is JavaScript?

JavaScript is a programming language that runs in browsers and, through runtimes such as Node.js, on servers. In a web page it can respond to clicks, update the DOM, validate input, and request data from APIs.

Where JavaScript runs

  • Browsers: interactive pages, animations, and single-page apps.
  • Node.js, Deno, Bun: servers, command-line tools, and build scripts.
  • Mobile and desktop: frameworks such as React Native and Electron.

Adding JavaScript to a page

  • Use <script src="app.js" defer></script> so the script runs after the HTML is parsed.
  • Use type="module" to enable import and export.
  • Open the browser console (F12) to test code and read errors.

Your first script

// app.js
console.log("Hello, JavaScript!");

const heading = document.querySelector("h1");
heading.textContent = "Hello from JavaScript";

Variables and Data Types

  • Use const by default for values that are not reassigned.
  • Use let when a variable must change.
  • Avoid var; it has confusing function scope and hoisting behavior.
  • Primitive types: string, number, bigint, boolean, undefined, null, symbol.
  • Everything else is an object, including arrays, functions, and dates.
  • Use strict equality (===) unless coercion is intentional.

Variables, types, and template strings

const name = "Asha";
let score = 10;
score += 5;

const isActive = true;
const nothing = null;
let notSet;                       // undefined

console.log(typeof name);         // "string"
console.log(typeof score);        // "number"
console.log(0.1 + 0.2 === 0.3);   // false (floating-point math)

console.log(`Player ${name} has ${score} points`);

console.log(1 == "1");            // true  (coercion)
console.log(1 === "1");           // false (preferred)

Truthy and falsy values

  • Falsy values: false, 0, "", null, undefined, NaN.
  • Everything else is truthy, including "0", [], and {}.
  • Use ?? to default only null/undefined, and ?. for safe property access.

Optional chaining and nullish coalescing

const user = { profile: { city: "Delhi" } };

console.log(user.profile?.city);        // "Delhi"
console.log(user.address?.street);      // undefined (no error)

const count = 0;
console.log(count || 10);               // 10  (0 is falsy)
console.log(count ?? 10);               // 0   (only null/undefined replaced)

Functions, Scope, and Closures

  • Function declarations are hoisted and can be called before they appear.
  • Arrow functions are concise and keep the surrounding this.
  • **Default parameters, rest (...args), and spread (...arr)** make functions flexible.
  • Scope: variables declared with let and const are block scoped.
  • Closures: a function remembers the variables from where it was created.

Function styles, defaults, and a closure

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

const multiply = (a, b) => a * b;

const greet = (name = "friend") => `Hello, ${name}!`;

const sum = (...numbers) => numbers.reduce((total, n) => total + n, 0);

function makeCounter() {
  let count = 0;                 // private, kept alive by the closure
  return () => ++count;
}

const counter = makeCounter();
console.log(counter());          // 1
console.log(counter());          // 2
console.log(sum(1, 2, 3, 4));    // 10

Arrays, Objects, and Destructuring

Most real-world JavaScript is about transforming collections of data. Array methods describe each operation clearly.

  • map transforms every item into a new array.
  • filter keeps items that pass a test.
  • find returns the first matching item.
  • reduce combines items into a single value.
  • some and every test whether any or all items match.
  • sort sorts in place; copy first with [...arr] or toSorted() to avoid mutation.

Transforming data with array methods

const products = [
  { id: 1, name: "Keyboard", price: 49, inStock: true },
  { id: 2, name: "Mouse", price: 25, inStock: false },
  { id: 3, name: "Monitor", price: 199, inStock: true },
];

const names = products.map((p) => p.name);
const available = products.filter((p) => p.inStock);
const monitor = products.find((p) => p.name === "Monitor");
const total = available.reduce((sum, p) => sum + p.price, 0);

console.log(names);        // ["Keyboard", "Mouse", "Monitor"]
console.log(total);        // 248

Destructuring, spread, and object shorthand

const { name, price } = products[0];
const [first, second, ...rest] = products;

const updated = { ...products[0], price: 45 };   // copy with change
const merged = [...products, { id: 4, name: "Webcam", price: 59, inStock: true }];

const entries = Object.entries({ a: 1, b: 2 });  // [["a", 1], ["b", 2]]

Control Flow and Loops

  • if/else if/else for decisions and switch for many fixed cases.
  • for...of iterates values of arrays, strings, and other iterables.
  • for...in iterates object keys; prefer Object.keys or Object.entries.
  • while and do...while repeat until a condition changes.
  • break exits a loop and continue skips to the next iteration.

Loops and conditions

const scores = [92, 74, 58, 85];

for (const score of scores) {
  if (score >= 90) console.log(score, "A");
  else if (score >= 70) console.log(score, "B");
  else console.log(score, "Needs work");
}

let attempts = 0;
while (attempts < 3) {
  console.log("Attempt", ++attempts);
}

Working with the DOM

The DOM represents the page as objects you can read and change from JavaScript.

  • Select: querySelector, querySelectorAll, getElementById.
  • Change text: textContent is safe for plain text.
  • Change classes: classList.add, remove, toggle.
  • Create elements: createElement, then append.
  • Attributes: setAttribute, dataset, and properties such as hidden.
  • Avoid innerHTML with user input because it can lead to cross-site scripting (XSS).

Creating and updating elements safely

const list = document.querySelector("#todo-list");

function addItem(text) {
  const li = document.createElement("li");
  li.textContent = text;           // safe: treated as text, not HTML
  li.classList.add("todo-item");
  li.dataset.created = Date.now();
  list.append(li);
}

addItem("Learn the DOM");
addItem("Build a small project");

document.body.classList.toggle("dark");

Events and Event Delegation

Events let your code react to clicks, typing, form submission, and more. Event delegation attaches one listener to a parent instead of many to children.

Listening for events and delegating clicks

const form = document.querySelector("#todo-form");
const input = document.querySelector("#todo-input");
const list = document.querySelector("#todo-list");

form.addEventListener("submit", (event) => {
  event.preventDefault();                 // stop page reload
  const text = input.value.trim();
  if (!text) return;
  addItem(text);
  input.value = "";
  input.focus();
});

// One listener handles clicks on every current and future item
list.addEventListener("click", (event) => {
  const item = event.target.closest("li");
  if (item) item.classList.toggle("done");
});

Asynchronous JavaScript: Promises and async/await

Network requests, timers, and file access take time. JavaScript handles them asynchronously so the page stays responsive.

The evolution of async code

  • Callbacks: functions passed to run later; deeply nested callbacks become hard to read.
  • Promises: objects representing a future result with .then(), .catch(), .finally().
  • async/await: syntax that makes promise code read top to bottom.

Promises and async/await compared

const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

// Promise chain
wait(500)
  .then(() => console.log("Half a second later"))
  .catch((err) => console.error(err));

// async/await version
async function run() {
  await wait(500);
  console.log("Half a second later");
}

// Run tasks in parallel
async function loadBoth() {
  const [a, b] = await Promise.all([fetchUser(1), fetchUser(2)]);
  return [a, b];
}

Calling APIs with fetch and Handling Errors

  • fetch only rejects on network failure; HTTP errors like 404 and 500 still resolve.
  • Always check response.ok before reading the body.
  • Wrap awaited calls in try/catch and show a helpful message to the user.
  • Use AbortController to cancel requests that are no longer needed.
  • Never put secret API keys in browser code.

A safe fetch helper with loading and error handling

async function getJson(url, { signal } = {}) {
  const response = await fetch(url, { signal });

  if (!response.ok) {
    throw new Error(`Request failed: ${response.status} ${response.statusText}`);
  }
  return response.json();
}

async function showUsers() {
  const status = document.querySelector("#status");
  const list = document.querySelector("#users");
  status.textContent = "Loading...";

  try {
    const users = await getJson("https://jsonplaceholder.typicode.com/users");
    list.replaceChildren(
      ...users.slice(0, 5).map((user) => {
        const li = document.createElement("li");
        li.textContent = `${user.name} (${user.email})`;
        return li;
      })
    );
    status.textContent = "";
  } catch (error) {
    status.textContent = "Could not load users. Please try again.";
    console.error(error);
  }
}

Modules and Classes

  • Modules split code into files with export and import.
  • Named exports can be many per file; a default export is one main value.
  • Classes bundle data and behavior with a constructor, methods, and optional private fields (#field).
  • Prefer small functions and plain objects; use classes when you need many instances with shared behavior.

A module that exports a class

// cart.js
export class Cart {
  #items = [];                      // private field

  add(product, quantity = 1) {
    this.#items.push({ product, quantity });
    return this;
  }

  get total() {
    return this.#items.reduce((sum, i) => sum + i.product.price * i.quantity, 0);
  }
}

export const formatPrice = (n) => `$${n.toFixed(2)}`;

// main.js  (loaded with <script type="module" src="main.js">)
import { Cart, formatPrice } from "./cart.js";

const cart = new Cart().add({ name: "Mouse", price: 25 }, 2);
console.log(formatPrice(cart.total));   // $50.00

Saving Data in the Browser

  • localStorage keeps strings across visits; sessionStorage lasts for one tab session.
  • Use JSON.stringify and JSON.parse to store objects and arrays.
  • Wrap parsing in try/catch because stored data can be missing or invalid.
  • Never store passwords or sensitive tokens in localStorage.

Persisting a to-do list

const KEY = "todos";

function loadTodos() {
  try {
    return JSON.parse(localStorage.getItem(KEY)) ?? [];
  } catch {
    return [];
  }
}

function saveTodos(todos) {
  localStorage.setItem(KEY, JSON.stringify(todos));
}

const todos = loadTodos();
todos.push({ id: crypto.randomUUID(), text: "Practice JavaScript", done: false });
saveTodos(todos);

Best Practices and Common Mistakes

  • Use const and let; never rely on accidental globals.
  • Prefer === and handle null/undefined explicitly.
  • Keep functions small and name them after what they do.
  • Handle errors in every async path and give users feedback.
  • Do not trust user input; validate on the server and avoid innerHTML/eval.
  • Debounce expensive handlers such as search-as-you-type.
  • Use ESLint and Prettier to catch mistakes and keep style consistent.
  • Learn browser DevTools: Console, Network, and the debugger.

Complete Example: A Persistent To-Do App

Save this as an HTML file and open it in a browser. It uses the DOM, events, delegation, localStorage, and safe text rendering.

A complete to-do app in one file

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>To-Do App</title>
  <style>
    body { font-family: system-ui, sans-serif; max-width: 32rem; margin: 2rem auto; padding: 0 1rem; }
    li.done span { text-decoration: line-through; opacity: 0.6; }
    li { display: flex; gap: 0.5rem; align-items: center; margin: 0.4rem 0; }
  </style>
</head>
<body>
  <h1>My To-Do List</h1>
  <form id="form">
    <label for="input">New task</label>
    <input id="input" required>
    <button>Add</button>
  </form>
  <ul id="list"></ul>

  <script>
    const KEY = "todos";
    const form = document.querySelector("#form");
    const input = document.querySelector("#input");
    const list = document.querySelector("#list");

    let todos = load();
    render();

    function load() {
      try { return JSON.parse(localStorage.getItem(KEY)) ?? []; }
      catch { return []; }
    }
    function save() { localStorage.setItem(KEY, JSON.stringify(todos)); }

    function render() {
      list.replaceChildren(
        ...todos.map((todo) => {
          const li = document.createElement("li");
          li.dataset.id = todo.id;
          li.classList.toggle("done", todo.done);

          const box = document.createElement("input");
          box.type = "checkbox";
          box.checked = todo.done;

          const text = document.createElement("span");
          text.textContent = todo.text;

          const del = document.createElement("button");
          del.textContent = "Delete";
          del.dataset.action = "delete";

          li.append(box, text, del);
          return li;
        })
      );
    }

    form.addEventListener("submit", (e) => {
      e.preventDefault();
      const text = input.value.trim();
      if (!text) return;
      todos.push({ id: crypto.randomUUID(), text, done: false });
      save(); render();
      form.reset(); input.focus();
    });

    list.addEventListener("click", (e) => {
      const li = e.target.closest("li");
      if (!li) return;
      const id = li.dataset.id;
      if (e.target.dataset.action === "delete") {
        todos = todos.filter((t) => t.id !== id);
      } else if (e.target.type === "checkbox") {
        todos = todos.map((t) => (t.id === id ? { ...t, done: !t.done } : t));
      } else return;
      save(); render();
    });
  </script>
</body>
</html>

Frequently Asked Questions

Is JavaScript the same as Java?

No. They are separate languages with different ecosystems and language designs, despite the similar names.

What is async/await?

It is syntax for working with promises in a sequential-looking style. Errors can be handled with try/catch.

Do I need a framework to learn JavaScript?

No. Learn language fundamentals and browser APIs first; frameworks such as React or Vue become much easier afterward.

What is the difference between let, const, and var?

const declares a binding that is not reassigned, let declares a reassignable block-scoped variable, and var is older function-scoped syntax that is best avoided.

Why does fetch not throw on a 404 error?

fetch only rejects for network-level failures. HTTP error statuses still resolve, so you must check response.ok and throw your own error.

What is the DOM?

The Document Object Model is the browser's tree of objects representing the page. JavaScript uses it to read and change content, styles, and structure.

← Back to All Articles