Skip to main content

Programming Fundamentals

A comprehensive guide to core programming concepts with a focus on JavaScript, TypeScript, and the Node.js ecosystem. This reference covers the fundamentals every developer needs — from language mechanics and paradigms to runtime internals and memory management.


Table of Contents


JavaScript (ES2024+)

JavaScript is a high-level, dynamically-typed, interpreted (and JIT-compiled) language conforming to the ECMAScript specification. It powers the web alongside HTML and CSS, and runs on servers via runtimes like Node.js and Deno.

Core Building Blocks

Variables and scope:

// Block-scoped, cannot be redeclared
let count = 0;

// Block-scoped, cannot be reassigned (but objects are mutable)
const MAX_LIMIT = 100;

// Function-scoped, hoisted — avoid in modern code
var legacy = 'pre-ES6';

Data types — JavaScript has 7 primitive types and 1 structural type:

TypeExampleNotes
string'hello'Immutable, indexed by character
number42, 3.14, InfinityIEEE 754 double (no int/float)
bigint9007199254740991nArbitrary-precision integer
booleantrue, false
undefinedlet x;Variable declared but unassigned
nulllet x = null;Intentional absence of value
symbolSymbol('id')Guaranteed unique key
object{ name: 'Alice' }Structural type — arrays, functions, dates are all objects

Operators: arithmetic (+, -, *, **, /, %), assignment (=, +=, etc.), comparison (== vs ===, != vs !==), logical (&&, ||, ??, !), bitwise (&, |, ^, ~, <<, >>, >>>), and the ternary operator (condition ? a : b).

Control flow:

// Conditional
if (score > 90) {
/* ... */
} else if (score > 70) {
/* ... */
} else {
/* ... */
}

// Switch (uses strict comparison)
switch (status) {
case 'active':
/* ... */ break;
case 'pending':
/* ... */ break;
default: /* ... */
}

// Loops
for (let i = 0; i < items.length; i++) {
/* ... */
}
for (const item of iterable) {
/* ... */
} // values
for (const key in object) {
/* ... */
} // keys
while (condition) {
/* ... */
}
do {
/* ... */
} while (condition);

Functions:

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

// Expression (not hoisted)
const multiply = function (a, b) {
return a * b;
};

// Arrow (lexical this, no arguments object)
const divide = (a, b) => a / b;

// Default parameters
function greet(name = 'guest') {
return `Hello, ${name}`;
}

// Rest parameters
function sum(...numbers) {
return numbers.reduce((a, b) => a + b, 0);
}

Objects and arrays:

// Object literal
const user = { name: 'Alice', age: 30 };

// Computed properties
const key = 'role';
const staff = { [key]: 'admin' };

// Shorthand
const name = 'Bob';
const person = { name }; // { name: 'Bob' }

// Array creation and destructuring
const arr = [1, 2, 3];
const [first, second] = arr;

// Common array methods
const doubled = arr.map((x) => x * 2);
const evens = arr.filter((x) => x % 2 === 0);
const sum = arr.reduce((acc, x) => acc + x, 0);

Modern Features (ES2020–ES2024)

  • Optional chaining (?.) — safe property access: user?.address?.city
  • Nullish coalescing (??) — fallback only for null/undefined: value ?? 'default'
  • Logical assignment (||=, &&=, ??=) — assign conditionally
  • Top-level await (ES2022, in modules) — await without wrapping in async function
  • Array .at() — negative indexing: arr.at(-1) for last element
  • Object .hasOwn() — safer than .hasOwnProperty: Object.hasOwn(obj, 'key')
  • Array.prototype.toSorted() / toReversed() / toSpliced() — immutable array operations (ES2023)
  • Promise.withResolvers() (ES2024) — create promise + resolve/reject in one call
  • RegExp v flag (ES2024) — set notation and string properties in character classes
  • Temporal API (Stage 3, approaching ES2025) — modern replacement for Date

TypeScript

TypeScript is a statically typed superset of JavaScript that compiles to plain JavaScript. It catches type errors at build time, provides superior editor tooling, and makes large codebases maintainable.

Core Type System

// Primitive types
let name: string = 'Alice';
let age: number = 30;
let active: boolean = true;

// Arrays
let scores: number[] = [95, 87, 91];
let matrix: number[][] = [
[1, 2],
[3, 4],
];

// Tuples (fixed-length, typed positions)
let pair: [string, number] = ['age', 30];

// Union types — value can be one of several types
let id: string | number = 'abc123';

// Type aliases
type Status = 'idle' | 'loading' | 'success' | 'error';

// Interfaces — define object shapes (extendable)
interface User {
id: number;
name: string;
email?: string; // optional property
readonly createdAt: Date; // immutable after creation
}

// Generics — types as parameters
function firstElement<T>(arr: T[]): T | undefined {
return arr[0];
}

// Utility types
type PartialUser = Partial<User>; // all properties optional
type RequiredUser = Required<User>; // all properties required
type ReadonlyUser = Readonly<User>; // all properties readonly
type UserContact = Pick<User, 'email'>; // select subset
type UserWithoutId = Omit<User, 'id'>; // exclude subset

Key Concepts

  • Structural typing — TypeScript uses duck typing; two types are compatible if their shapes match, regardless of explicit declarations
  • strict mode (tsconfig.json) — enables strictNullChecks, noImplicitAny, strictFunctionTypes, and more. Always use it for new projects
  • unknown vs anyunknown is the type-safe counterpart of any; you must narrow it before use
  • never — represents values that never occur (e.g., a function that always throws or infinite loops)
  • Enums — named constants. Prefer const enum or string literal unions to avoid generated code overhead
  • Declaration files (.d.ts) — describe the shape of existing JS libraries for the type checker

Programming Paradigms

Functional Programming (FP)

FP treats computation as the evaluation of mathematical functions, avoiding mutable state and side effects.

Core principles:

  • Pure functions — same input always produces same output, no side effects (no mutation, no I/O, no external state changes)

    // Pure
    const add = (a, b) => a + b;

    // Impure — mutates external state
    let total = 0;
    const addToTotal = (n) => {
    total += n;
    };
  • Immutability — data is never changed in place; new copies are returned

    // Instead of arr.push(4), use:
    const newArr = [...arr, 4];

    // Instead of obj.age = 31, use:
    const newObj = { ...obj, age: 31 };
  • Higher-order functions — functions that take or return other functions

    const multiply = (factor) => (value) => value * factor;
    const double = multiply(2);
    double(5); // 10
  • Function composition — combining simple functions into complex pipelines

    const compose = (f, g) => (x) => f(g(x));
    const addOne = (x) => x + 1;
    const square = (x) => x * x;
    const squareThenAddOne = compose(addOne, square);
    squareThenAddOne(3); // 10
  • Declarative style — describe what to do, not how

    // Imperative
    const doubled = [];
    for (let i = 0; i < nums.length; i++) {
    doubled.push(nums[i] * 2);
    }

    // Declarative
    const doubled = nums.map((x) => x * 2);

Common FP techniques: currying, partial application, recursion over loops, monads (Promise, Array.flatMap), and pattern matching (TC39 proposal).

Object-Oriented Programming (OOP)

OOP models programs as collections of objects that contain data and behavior.

Core principles:

  • Encapsulation — bundle data and methods, control access

    class BankAccount {
    #balance = 0; // ES2022 private field

    deposit(amount) {
    if (amount > 0) this.#balance += amount;
    return this.#balance;
    }

    get balance() {
    return this.#balance;
    }
    }
  • Inheritance — create specialized classes from general ones

    class Animal {
    constructor(name) {
    this.name = name;
    }
    speak() {
    return `${this.name} makes a sound`;
    }
    }

    class Dog extends Animal {
    speak() {
    return `${this.name} barks`;
    }
    }
  • Polymorphism — objects of different types respond to the same interface

    const animals = [new Animal('generic'), new Dog('Rex')];
    animals.forEach((a) => console.log(a.speak()));
    // "generic makes a sound"
    // "Rex barks"
  • Abstraction — expose only essential details, hide complexity

Prototype-based nature of JS: JavaScript uses prototypal inheritance under the hood. class syntax is syntactic sugar over the prototype chain. Every object has an internal [[Prototype]] link; property access walks up this chain.

const parent = {
greet() {
return 'hello';
},
};
const child = Object.create(parent);
child.greet(); // 'hello' — found via prototype chain

Asynchronous Programming

JavaScript is single-threaded with a non-blocking event loop. Long-running operations (network, file I/O, timers) are delegated to the runtime, and their results are processed asynchronously via callbacks, promises, or async/await.

Evolution of Async Patterns

Callbacks (original approach):

fetchData('/api/users', (err, data) => {
if (err) {
console.error(err);
return;
}
processUsers(data, (err, result) => {
if (err) {
console.error(err);
return;
}
console.log(result);
});
});
// Problem: callback hell — deeply nested, hard to read/error-handle

Promises (ES6):

fetchData('/api/users')
.then((data) => processUsers(data))
.then((result) => console.log(result))
.catch((err) => console.error(err));
// Promise states: pending → fulfilled (resolved) or rejected
// .then() returns a new promise, enabling chaining

Async/await (ES2017):

async function loadUsers() {
try {
const data = await fetchData('/api/users');
const result = await processUsers(data);
console.log(result);
} catch (err) {
console.error(err);
}
}
// Reads like synchronous code; error handling via try/catch

Key Async Patterns

  • Promise.all([]) — run in parallel, fail if any rejects
  • Promise.allSettled([]) — run in parallel, get all results (fulfilled or rejected)
  • Promise.race([]) — resolve/reject with the first to settle
  • Promise.any([]) — resolve with the first to fulfill, reject only if all reject
  • for await...of — iterate over async iterables (streams, generators)

The Event Loop

Call Stack → executes synchronous code
↓ (when stack is empty)
Microtask Queue → Promises (.then/catch/finally), queueMicrotask, MutationObserver
↓ (when microtask queue is empty)
Macrotask Queue → setTimeout, setInterval, I/O callbacks, setImmediate (Node.js)

Microtasks run before the next macrotask. This is why Promise.resolve().then(...) runs before setTimeout(..., 0).


Event-Driven Programming

Event-driven architecture decouples producers (emitters) from consumers (listeners). The flow of execution is determined by events rather than a sequential script.

Pattern:

// Node.js EventEmitter
import { EventEmitter } from 'events';

const emitter = new EventEmitter();

// Register listener
emitter.on('orderPlaced', (order) => {
console.log(`Processing order #${order.id}`);
});

// Emit event
emitter.emit('orderPlaced', { id: 1234 });

Browser events:

button.addEventListener('click', (event) => {
console.log('Button clicked', event.target);
});

Key concepts:

  • Observer pattern — subject maintains a list of observers and notifies them of state changes
  • Event bubbling/capturing (DOM) — events propagate up (bubble) or down (capture) the DOM tree
  • Custom eventsnew CustomEvent('myEvent', { detail: {...} }) for application-specific events

Memory Management

JavaScript uses automatic garbage collection (GC), but understanding how memory works is essential for writing performant applications.

Memory Lifecycle

  1. Allocate — memory is allocated when variables are declared, objects created, functions defined
  2. Use — read/write operations on allocated memory
  3. Release — GC frees memory when objects become unreachable

Stack vs Heap

RegionWhat goes thereLifetime
StackPrimitives, function call frames, references to heap objectsAutomatic (push/pop with function calls)
HeapObjects, arrays, functions, closuresManaged by GC

Garbage Collection Algorithms

  • Mark-and-sweep (modern engines): Start from roots (global object, call stack), mark all reachable objects, sweep away unmarked ones. This is the primary algorithm in V8.
  • Reference counting (legacy): Track number of references to each object; free when count reaches zero. Fails with circular references, so modern engines don't rely on it.
  • Generational collection: Most objects die young. V8 splits heap into "new space" (young generation, fast Scavenge GC) and "old space" (objects surviving multiple GC cycles, slower Mark-Sweep-Compact).

Common Memory Leaks

  • Accidental globals — assigning to undeclared variable creates a global property
  • Forgotten timers/callbackssetInterval references keep closures alive
  • Detached DOM nodes — JavaScript references to removed DOM elements prevent their GC
  • Closures retaining large objects — inner functions keep outer scope alive

Detection: Use Chrome DevTools Memory panel (heap snapshots, allocation timeline) or process.memoryUsage() in Node.js.


Error Handling

Robust error handling prevents crashes and provides meaningful feedback.

Try/Catch/Finally

try {
const data = JSON.parse(input);
processData(data);
} catch (error) {
// Narrow the error type
if (error instanceof SyntaxError) {
console.error('Invalid JSON:', error.message);
} else {
throw error; // rethrow unexpected errors
}
} finally {
cleanup(); // runs regardless of error
}

Custom Error Types

class ValidationError extends Error {
constructor(message, field) {
super(message);
this.name = 'ValidationError';
this.field = field;
}
}

throw new ValidationError('Required field missing', 'email');

Error Handling Patterns

  • Fail fast — validate early, throw immediately on invalid state
  • Catch at boundaries — handle errors at API entry points, not deep in internals
  • Never swallow errors — always log or propagate; empty catch blocks hide bugs
  • Async error handling — unhandled promise rejections crash Node.js processes; always .catch() or try/catch with await
  • Operational vs programmer errors — operational (network failure, file not found) should be handled gracefully; programmer (null reference, type error) should be fixed in code
// Global handlers (last resort)
process.on('uncaughtException', (err) => {
console.error('Uncaught exception:', err);
process.exit(1);
});

process.on('unhandledRejection', (reason) => {
console.error('Unhandled rejection:', reason);
});

Modules

Modules encapsulate code into reusable, self-contained units with explicit dependencies.

CommonJS (CJS) — Node.js default

// Export
module.exports = { add, subtract };
exports.multiply = (a, b) => a * b; // shorthand

// Import
const math = require('./math');
const { add } = require('./math');
  • Synchronous loading (works on server, problematic in browser)
  • require() can be called conditionally
  • module.exports is a singleton — cached after first load

ES Modules (ESM) — modern standard

// Named export
export const add = (a, b) => a + b;
export function subtract(a, b) {
return a - b;
}

// Default export
export default class Calculator {
/* ... */
}

// Import
import Calculator, { add, subtract } from './math.js';
import * as math from './math.js';
  • Static analysis possible (tree-shaking, bundler optimization)
  • Asynchronous loading (works natively in browsers)
  • import must be top-level (except dynamic import())
  • Strict mode by default

Key Differences

FeatureCJSESM
Syntaxrequire / module.exportsimport / export
LoadingSynchronousAsynchronous
This at top levelthis === module.exportsthis === undefined
Live bindingsNo (copy of exports)Yes (bindings are live)
Dynamic importAlways dynamicimport() expression
File extension.js / .cjs.mjs / .js (with "type": "module")

Package Management

npm (Node Package Manager)

# Initialize a project
npm init -y

# Install dependencies
npm install express # production dependency
npm install -D jest typescript # dev dependency
npm install -g npm-check-updates # global install

# Scripts (in package.json)
"scripts": {
"start": "node index.js",
"test": "jest",
"build": "tsc"
}

# Run scripts
npm run build

Versioning (SemVer)

Packages follow MAJOR.MINOR.PATCH:

  • ^1.2.3 — compatible with >=1.2.3 &lt;2.0.0 (default for npm install)
  • ~1.2.3 — compatible with >=1.2.3 &lt;1.3.0
  • 1.2.3 — exact version only

Lock Files

package-lock.json (npm) or yarn.lock (yarn) pins exact dependency trees for reproducible builds. Commit these files to version control.

Alternative Package Managers

  • yarn — deterministic installs, workspaces, Plug'n'Play
  • pnpm — disk-efficient (content-addressable storage), strict dependency isolation
  • bun — all-in-one runtime, bundler, and package manager (drop-in npm compatible)

Node.js Runtime

Node.js is a JavaScript runtime built on Chrome's V8 engine, designed for building scalable network applications.

Architecture

JavaScript Code

Node.js APIs (fs, http, path, crypto, ...)

libuv (async I/O, event loop, thread pool)

Operating System

Key Characteristics

  • Single-threaded event loop — one main thread handles all JS execution; I/O is offloaded to the kernel or thread pool
  • Non-blocking I/O — operations that would block (file reads, DB queries) use callbacks/promises, so the thread stays free for other requests
  • Event-driven — the event loop picks up completed I/O operations and invokes their callbacks

Global Objects

  • global — the global namespace (like window in browsers)
  • process — information about and control over the current Node.js process (env vars, argv, exit, memory)
  • __dirname, __filename — current directory and file path (CJS only; in ESM use import.meta.url)
  • Buffer — raw binary data handling
  • console — logging utilities

Core Modules (selection)

ModulePurpose
fsFile system operations
httpHTTP server and client
pathFile path utilities
cryptoCryptographic functions
streamStreaming data processing
eventsEventEmitter base class
child_processSpawn subprocesses
worker_threadsTrue multi-threading
clusterMulti-process load balancing

The Event Loop in Detail

Node.js event loop phases (each phase has a FIFO queue of callbacks):

  1. timerssetTimeout, setInterval callbacks
  2. pending callbacks — deferred I/O callbacks
  3. idle, prepare — internal use
  4. poll — retrieve new I/O events; execute I/O callbacks
  5. checksetImmediate callbacks
  6. close callbackssocket.on('close', ...) etc.

Between each phase, the loop processes process.nextTick and microtask queues.


V8 Engine Internals

V8 is Google's open-source JavaScript and WebAssembly engine, written in C++. It powers Chrome, Node.js, Deno, and Electron.

Execution Pipeline

JavaScript Source Code

Parser → AST (Abstract Syntax Tree)

Ignition (Interpreter) → Bytecode

TurboFan (Optimizing Compiler) → Machine Code
↓ (if assumptions fail)
Deoptimization → back to Bytecode

Key Components

  • Ignition — V8's interpreter. Generates bytecode from AST quickly with low memory overhead. All code starts here.
  • TurboFan — optimizing compiler. Identifies "hot" functions (frequently executed), collects type feedback (inline caches), and generates highly optimized machine code with speculative optimizations.
  • Sparkplug — a fast, non-optimizing compiler introduced to bridge the gap between Ignition and TurboFan. Compiles bytecode to machine code quickly without heavy optimization.
  • Maglev (since Chrome 114/V8 11.4) — a mid-tier optimizing compiler that generates better code than Sparkplug but faster than TurboFan.

Key Optimizations

  • Inline caching — V8 remembers the types of objects seen at property access sites, avoiding repeated lookups
  • Hidden classes (Maps) — V8 creates internal "shape" descriptors for objects with the same property layout, enabling fast property access like C structs
  • Function inlining — replacing a function call with the function body to avoid call overhead

Writing V8-Friendly Code

  • Keep object shapes consistent — add properties in the same order, don't delete properties dynamically
  • Avoid polymorphic functions — prefer functions that operate on consistent types
  • Keep functions small — easier for TurboFan to inline
  • Avoid try/catch in hot paths — prevents some optimizations
  • Use monomorphic arrays — don't mix types in arrays ([1, 'two', {}]) as V8 optimizes for homogeneous arrays

Memory Layout

V8's heap is divided into spaces:

  • New space — young generation, scavenged quickly
  • Old space — survived multiple GC cycles, includes old pointer and old data spaces
  • Large object space — objects exceeding size threshold
  • Code space — JIT-compiled code objects
  • Cell/PropertyCell/Map spaces — internal metadata

Further Reading

← Back to Home · © sparshjaswal