Knowledge/JavaScript/Introduction
23 Chapters
ES2024 Baseline
Living Document
Free License
Engineering Publication · Syed Omar Ibn Feroj← Back to portfolio
JS
Engineering Notes · Open Knowledge Repository

JavaScript — Engineering
Notes for Working Code

A working reference written from the angle of shipping production JavaScript — the language itself, then async, performance, modules and tooling. Compact on theory, dense on the patterns that survive contact with real systems.

23
Chapters
ES2024
Baseline
Living
Document
Free
License
Ch. 01
Getting Started
Where JavaScript runs, how output works, and the smallest useful program in a browser or a Node runtime.
1.1 — Output: console and DOM

JavaScript executes in two main hosts: a browser (with a window and a DOM) and a server runtime like Node.js. The console is universal across both.

JavaScript
// Browser or Node — both support console
console.log("hello");
console.log("value=", { id: 1, ok: true });

// Browser-only — the DOM is a tree of nodes
const el = document.getElementById("out");
el.textContent = "rendered from JS";
ℹ️
If your script touches the DOM, it must run after the target nodes exist. Either place the <script> tag at the end of <body>, mark it defer, or wrap DOM code in a DOMContentLoaded listener.
1.2 — The smallest browser program
HTML
<!doctype html>
<html>
  <body>
    <p id="out">loading…</p>
    <script>
      document.getElementById("out").textContent = "ready";
    </script>
  </body>
</html>
Ch. 02
Variables — let, const, var
Bindings, scope, hoisting, and the rule that solves 90% of beginner bugs.
2.1 — Three keywords, two you should reach for
JavaScript
const name = "omar";     // block-scoped, cannot rebind
let   count = 0;       // block-scoped, reassignable
var   legacy = 42;     // function-scoped, hoisted — avoid in new code

count++;
// name = "x";  // TypeError: Assignment to constant variable
💡
House rule: default to const. Promote to let the moment you genuinely need to rebind. Treat var as a code-smell in modern code.
2.2 — Scope is what makes let safer
JavaScript
function demo() {
  if (true) {
    let  inner = 1;
    var  outer = 2;
  }
  // inner -> ReferenceError (block-scoped)
  // outer -> 2          (hoisted to function scope)
}
2.3 — const with objects: the binding is frozen, the value isn't
JavaScript
const user = { name: "omar" };
user.name = "rupam";       // allowed — object is mutable
// user = {};                  // TypeError — re-binding blocked

// To freeze contents too:
const config = Object.freeze({ env: "prod" });
Ch. 03
Built-in Constants
The handful of language-level constants worth memorising.
JavaScript
true, false          // boolean literals
null                  // intentional absence of any value
undefined             // declared but not yet assigned
NaN                   // "not a number" — result of failed numeric ops
Infinity, -Infinity

Number.MAX_SAFE_INTEGER   // 2^53 - 1
Number.EPSILON             // smallest gap between two doubles
Math.PI, Math.E
⚠️
NaN is the only JavaScript value not equal to itself. Test with Number.isNaN(x), never x === NaN.
Ch. 04
Comments
Single-line, block, and JSDoc — and a working rule on when to write them.
JavaScript
// single line — for short context

/* block comment — for paragraphs */

/**
 * Multiply two numbers.
 * @param {number} a
 * @param {number} b
 * @returns {number}
 */
function mul(a, b) { return a * b; }
💡
Default to no comment. Reach for one only when the why is non-obvious — a hidden constraint, a workaround for a specific bug, a subtle invariant. Comments that restate the code rot faster than the code does.
Ch. 05
Console API
More than console.log — the diagnostic surface most engineers underuse.
JavaScript
console.log("info");
console.warn("yellow triangle");
console.error("red, with stack trace");
console.debug("verbose level");

// Tabular display — great for arrays of objects
console.table([{ id: 1, name: "a" }, { id: 2, name: "b" }]);

// Grouping
console.group("request");
console.log("url", url);
console.log("status", res.status);
console.groupEnd();

// Timers
console.time("db");
await db.query("…");
console.timeEnd("db");
Ch. 06
Data Types
Seven primitives, one object, and the typeof table that catches most beginners.
6.1 — Primitive vs reference
TypeCategorytypeofExample
stringprimitive"string""omar"
numberprimitive"number"42, 3.14, NaN
bigintprimitive"bigint"9007199254740993n
booleanprimitive"boolean"true
undefinedprimitive"undefined"undefined
nullprimitive"object" (legacy)null
symbolprimitive"symbol"Symbol("id")
objectreference"object" / "function"{}, [], fn
⚠️
typeof null === "object" is a historical bug that is now part of the spec. To check explicitly: x === null.
6.2 — Reference semantics in one example
JavaScript
const a = { n: 1 };
const b = a;          // b is a reference to the same object
b.n = 9;
console.log(a.n);     // 9 — they share state
Ch. 07
Strings
Template literals, the methods you'll reach for daily, and Unicode caveats.
JavaScript
// Template literals — multi-line, interpolation
const name = "omar";
const msg  = `hello, ${name.toUpperCase()}`;

// Searching
"foobar".includes("oba");   // true
"foobar".startsWith("foo"); // true
"foobar".indexOf("bar");    // 3

// Transforming
"  trim me  ".trim();
"a,b,c".split(",");          // ["a","b","c"]
["a","b","c"].join("|");       // "a|b|c"

// Padding / repeating
"7".padStart(3, "0");             // "007"
"ha".repeat(3);                    // "hahaha"
ℹ️
"𝟚".length === 2 — astral-plane characters count as two UTF-16 code units. For real character counts use [...str].length or Intl.Segmenter.
Ch. 08
Date & Time
The native Date object, ISO strings, and the case for Intl / Temporal.
JavaScript
const now = new Date();           // current instant
const t1  = Date.now();          // ms since epoch
const iso = now.toISOString();      // "2026-05-14T12:00:00.000Z"

// Parsing — accepts ISO 8601 reliably
const d = new Date("2026-05-14T09:00:00Z");

// Locale-aware formatting
new Intl.DateTimeFormat("en-GB", { dateStyle: "long", timeStyle: "short" }).format(now);
⚠️
The Date object is mutable and has surprising edge cases (months are 0-indexed, parsing of non-ISO strings is implementation-defined). For non-trivial work prefer Temporal (now in stage 3) or a vetted library.
Ch. 09
Arrays
Iteration, transformation, copy semantics, and the operator's mental model.
9.1 — Building, accessing, mutating
JavaScript
const xs = [1, 2, 3];
xs[0];                // 1
xs.length;             // 3

xs.push(4);            // append
xs.unshift(0);         // prepend
xs.pop();              // remove + return last
xs.shift();            // remove + return first
9.2 — The four methods that earn their keep
JavaScript
const nums = [1, 2, 3, 4];

nums.map(n => n * 2);            // [2,4,6,8]
nums.filter(n => n % 2);         // [1,3]
nums.reduce((a, n) => a + n, 0);   // 10
nums.find(n => n > 2);          // 3

// Non-mutating sort/reverse (ES2023)
nums.toSorted((a, b) => b - a);  // new array, original untouched
9.3 — Copying and spreading
JavaScript
const a = [1, 2, 3];
const b = [...a];        // shallow copy
const c = a.slice();     // same thing, older syntax
const d = [...a, 4, 5]; // concat

// Destructuring
const [first, ...rest] = a;
Ch. 10
Objects
Literal syntax, property access, spread, destructuring, and the dozen-line patterns you'll write hundreds of times.
JavaScript
const user = {
  id: 1,
  name: "omar",
  greet() { return `hi ${this.name}`; },
};

user.name;          // dot access
user["name"];       // bracket access (dynamic keys)
user?.profile?.bio; // optional chaining
user.role ?? "guest"; // nullish coalescing

// Spread + override
const patched = { ...user, name: "rupam" };

// Destructuring with rename + default
const { name: who, role = "guest" } = user;
💡
Spread ({...obj}) is shallow — nested objects are still shared. For deep copies use structuredClone(obj).
Ch. 11
Map & Set
When the built-in object isn't the right shape — keyed collections that get order, size and arbitrary keys right.
JavaScript
// Map — keyed by anything, preserves insertion order
const m = new Map();
m.set("x", 1);
m.set({ id: 7 }, "object as key");
m.get("x");
m.has("x");
m.size;

// Set — unique values, fast membership check
const s = new Set([1, 1, 2, 3]);
s.size;                  // 3
s.has(2);
[...s];                  // back to array
ℹ️
Use Map when keys are dynamic, non-string, or you need size. Use plain objects for fixed, known-at-write-time keys and JSON interop.
Ch. 12
Functions
Declarations, expressions, arrows, defaults, rest, and the closure model.
12.1 — Three ways to write one function
JavaScript
// Declaration — hoisted
function add(a, b) { return a + b; }

// Expression — not hoisted, can be anonymous
const sub = function (a, b) { return a - b; };

// Arrow — concise, no own this/arguments
const mul = (a, b) => a * b;
12.2 — Defaults and rest
JavaScript
function greet(name = "friend", ...titles) {
  return `hello ${titles.join(" ")} ${name}`;
}
greet();                       // "hello  friend"
greet("omar", "Mr.");          // "hello Mr. omar"
12.3 — Closures: functions remember
JavaScript
function counter() {
  let n = 0;
  return () => ++n;
}
const next = counter();
next(); next(); next(); // 3
Ch. 13
Classes
Sugar over the prototype chain — when classes earn their place and when a plain function is cleaner.
JavaScript
class Account {
  #balance = 0;            // private field

  constructor(owner) { this.owner = owner; }

  deposit(amount) {
    if (amount <= 0) throw new Error("non-positive");
    this.#balance += amount;
  }

  get balance() { return this.#balance; }

  static from(owner, opening) {
    const a = new Account(owner);
    a.deposit(opening);
    return a;
  }
}

const a = Account.from("omar", 100);
a.balance;   // 100
Ch. 14
Prototypes
The actual inheritance model under the class sugar.

Every JavaScript object has an internal link to another object — its prototype. Property lookups walk that chain until they find a match or hit null.

JavaScript
const animal = { eats() { return "chewing"; } };
const dog = Object.create(animal);
dog.eats();          // "chewing" — looked up via prototype

Object.getPrototypeOf(dog) === animal; // true

// class X extends Y { ... } is the same machinery
// with nicer syntax.
Ch. 15
Context — what this actually is
Four rules. Internalise them and a whole class of bugs disappears.
  1. Method callobj.fn()this === obj
  2. Plain callfn()undefined in strict mode, globalThis otherwise
  3. Constructor callnew Fn() → fresh instance
  4. Explicit bindfn.call(x) / .apply(x) / .bind(x) → whatever you pass
JavaScript
const user = {
  name: "omar",
  greet() { return this.name; },
  greetArrow: () => this?.name,
};
user.greet();              // "omar"      — method call
const f = user.greet;
f();                       // undefined  — detached, strict-mode plain call
user.greetArrow();         // undefined  — arrow captures enclosing this
⚠️
Arrow functions do not have their own this. They capture it from where they're defined, not where they're called. Don't use arrows for object methods that need this.
Ch. 16
Promises
A value that will exist later. Three states, one chain, and the four combinators worth knowing.
JavaScript
const p = new Promise((resolve, reject) => {
  setTimeout(() => resolve("done"), 100);
});

p.then(value => console.log(value))   // "done"
 .catch(err   => console.error(err))
 .finally(() => console.log("settled"));
16.2 — The four combinators
MethodResolves whenRejects when
Promise.allall fulfilany rejects (fail-fast)
Promise.allSettledall settlenever — always resolves
Promise.racefirst settlesfirst settles, if rejection
Promise.anyfirst fulfilsall reject (AggregateError)
Ch. 17
Async / Await
Sugar over promises that makes async code read like synchronous code — without losing what it really is.
JavaScript
async function loadUser(id) {
  const res = await fetch(`/api/users/${id}`);
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  return res.json();
}

// Parallel — start both before awaiting either
const [a, b] = await Promise.all([
  loadUser(1),
  loadUser(2),
]);
💡
Sequential awaits inside a loop are the most common performance trap in async JavaScript. If iterations are independent, build the array of promises and await Promise.all outside the loop.
Ch. 18
Error Handling
Throw, catch, finally — and how to surface enough information for the next engineer.
JavaScript
try {
  const data = JSON.parse(input);
  use(data);
} catch (err) {
  if (err instanceof SyntaxError) throw new Error("bad json", { cause: err });
  throw err;        // re-raise anything you can't handle
} finally {
  cleanup();
}

// Custom error type — preferred over passing strings around
class NotFoundError extends Error {
  constructor(resource) {
    super(`not found: ${resource}`);
    this.name = "NotFoundError";
  }
}
Ch. 19
Regular Expressions
Pattern matching that pays for itself — and how to keep it readable.
JavaScript
// Literal vs constructor
const rx1 = /^foo\d+$/;
const rx2 = new RegExp("^foo\\d+$");

// Common ops
"hello world".match(/o/g);              // ["o","o"]
"hello world".replace(/o/g, "0");     // "hell0 w0rld"
/\d+/.test("abc123");              // true

// Named capture groups
const m = "2026-05-14".match(/^(?<y>\d{4})-(?<m>\d{2})-(?<d>\d{2})$/);
m.groups.y; // "2026"
💡
Use the x flag (where supported) or the verbose-regex trick (split into a string built from comment-decorated parts) for any expression that doesn't fit on one line. Future-you will thank present-you.
Ch. 20
Performance
Where JavaScript actually spends time, and the working rules for keeping latency in check.

Measure first

Profile in the actual host before optimising. performance.now() and the browser profiler beat intuition.

🧱

Avoid layout thrash

Read all DOM measurements, then write. Interleaving forces synchronous reflow on every cycle.

Batch work

Group async writes with requestAnimationFrame or microtasks. Coalesce events with debounce / throttle.

🪶

Ship less

Code-split, lazy-load, tree-shake. The fastest code is the code that never reaches the browser.

JavaScript
// Microbenchmark template
const t0 = performance.now();
for (let i = 0; i < 1e6; i++) work(i);
const t1 = performance.now();
console.log(`took ${(t1 - t0).toFixed(2)} ms`);
Ch. 21
Debugging
The tools the browser already ships — most of them go unused.
JavaScript
// Hard breakpoint — pauses if devtools are open
debugger;

// Watch a value tree without spreading
console.dir(node);              // inspectable object view

// Trace where a call came from
console.trace("called from");

// Conditional logs without rebuilding
// (Right-click line gutter in devtools → "Add logpoint")
ℹ️
Source-map your production builds. A minified stack trace without source maps is an exercise in reading hex. With source maps the same trace points straight at the offending line in original source.
Ch. 22
Modules
ESM vs CommonJS, dynamic import, and how to keep dependency graphs honest.
22.1 — ES Modules (the default for new code)
JavaScript
// math.js
export function add(a, b) { return a + b; }
export const PI = 3.14159;
export default function main() { /* ... */ }

// caller.js
import main, { add, PI } from "./math.js";
import * as math from "./math.js";
22.2 — Dynamic import for code-splitting
JavaScript
async function onClick() {
  const { renderChart } = await import("./chart.js");
  renderChart();
}
💡
Static imports are cheap to reason about and easy to tree-shake. Reach for dynamic import() when payload size matters or the dependency is conditional (heavy editor, charting, route-specific bundle).
22.3 — CommonJS (legacy Node)
JavaScript
// still common in Node packages
const { add } = require("./math");
module.exports = { add };
Ch. 23
Tooling — the working JavaScript toolbox
The handful of tools that make a JavaScript codebase enjoyable to maintain at scale.
📦

Package manager

npm, pnpm, or bun. Lockfile committed. Workspaces for monorepos.

🧩

Bundler

esbuild / vite / rspack. Fast dev, tree-shake, code-split, source maps in prod.

🧹

Linter + formatter

eslint for correctness, prettier (or biome) for layout. Run in CI as gates, not opinions.

🧪

Tests

vitest / jest for unit, playwright for end-to-end. Fast feedback or no feedback.

🛡

TypeScript

Even in a JS codebase, run TS in checkJs mode via JSDoc — catches a surprising share of bugs before tests do.

🚏

CI gates

Type-check, lint, test, build — all four green before merge. Never skip a gate to ship.

package.json
{
  "scripts": {
    "dev":      "vite",
    "build":    "vite build",
    "test":     "vitest run",
    "lint":     "eslint . --max-warnings=0",
    "typecheck":"tsc --noEmit",
    "check":    "npm run lint && npm run typecheck && npm run test"
  }
}