Javascript in advanced

29 min read
Javascript in advanced

JavaScript, ES6, Node.js & Async/Await Complete Guide

Table of Contents

  1. JavaScript Fundamentals
  2. JavaScript Advanced Concepts
  3. ES6+ Features
  4. Node.js
  5. Async/Await & Promises
  6. Interview Questions

JavaScript Fundamentals

Data Types

JavaScript has 8 data types:

Primitive Types (7):

// 1. String
const name = "John";

// 2. Number
const age = 30;
const price = 19.99;

// 3. BigInt
const bigNumber = 9007199254740991n;

// 4. Boolean
const isActive = true;

// 5. Undefined
let x; // undefined

// 6. Null
const empty = null;

// 7. Symbol
const id = Symbol('id');

Non-Primitive Type (1):

// 8. Object (includes arrays, functions, dates, etc.)
const person = { name: "John", age: 30 };
const numbers = [1, 2, 3];
const greet = function() { return "Hello"; };

Type Coercion

// Implicit coercion
console.log("5" + 3);      // "53" (string)
console.log("5" - 3);      // 2 (number)
console.log("5" * "2");    // 10 (number)
console.log(true + 1);     // 2
console.log(false + 1);    // 1

// Explicit coercion
console.log(Number("5"));   // 5
console.log(String(123));   // "123"
console.log(Boolean(0));    // false
console.log(Boolean(""));   // false
console.log(Boolean(null)); // false

Truthy & Falsy Values

// Falsy values (6 total)
false, 0, "", null, undefined, NaN

// Everything else is truthy
"0", "false", [], {}, function() {}

Comparison Operators

// == (loose equality - performs type coercion)
console.log(5 == "5");     // true
console.log(null == undefined); // true

// === (strict equality - no type coercion)
console.log(5 === "5");    // false
console.log(null === undefined); // false

// Object comparison (by reference)
const a = { x: 1 };
const b = { x: 1 };
const c = a;
console.log(a === b);      // false (different references)
console.log(a === c);      // true (same reference)

Variable Declarations

// var - function scoped, hoisted
var x = 1;

// let - block scoped, not hoisted (TDZ)
let y = 2;

// const - block scoped, not hoisted, cannot be reassigned
const z = 3;

// Hoisting example
console.log(a); // undefined (var is hoisted)
console.log(b); // ReferenceError (TDZ)
var a = 1;
let b = 2;

Functions

// Function Declaration (hoisted)
function greet(name) {
  return `Hello, ${name}!`;
}

// Function Expression (not hoisted)
const greet = function(name) {
  return `Hello, ${name}!`;
};

// Arrow Function
const greet = (name) => `Hello, ${name}!`;

// IIFE (Immediately Invoked Function Expression)
(function() {
  console.log("Executed immediately!");
})();

// Default Parameters
function greet(name = "World") {
  return `Hello, ${name}!`;
}

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

Scope

// Global Scope
var globalVar = "I'm global";

function outer() {
  // Function Scope
  var functionVar = "I'm in function";
  
  if (true) {
    // Block Scope (let, const)
    let blockVar = "I'm in block";
    var notBlockVar = "I'm still function scoped";
  }
  
  console.log(notBlockVar); // Works
  console.log(blockVar);    // ReferenceError
}

Arrays

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

// Mutating methods
arr.push(6);        // Add to end
arr.pop();          // Remove from end
arr.shift();        // Remove from start
arr.unshift(0);     // Add to start
arr.splice(1, 2);   // Remove/replace elements
arr.sort();         // Sort in place
arr.reverse();      // Reverse in place

// Non-mutating methods
arr.map(x => x * 2);           // Transform elements
arr.filter(x => x > 2);        // Filter elements
arr.reduce((a, b) => a + b);   // Reduce to single value
arr.find(x => x > 2);          // Find first match
arr.findIndex(x => x > 2);     // Find index of first match
arr.some(x => x > 2);          // Any element matches?
arr.every(x => x > 0);         // All elements match?
arr.includes(3);               // Contains element?
arr.slice(1, 3);               // Extract portion
arr.concat([6, 7]);            // Merge arrays
arr.flat();                    // Flatten nested arrays
arr.flatMap(x => [x, x * 2]);  // Map + flatten

Objects

const person = {
  name: "John",
  age: 30,
  greet() {
    return `Hello, I'm ${this.name}`;
  }
};

// Accessing properties
person.name;          // Dot notation
person["name"];       // Bracket notation

// Object methods
Object.keys(person);     // ["name", "age", "greet"]
Object.values(person);   // ["John", 30, function]
Object.entries(person);  // [["name", "John"], ["age", 30], ...]
Object.assign({}, person); // Shallow copy
Object.freeze(person);   // Immutable (shallow)
Object.seal(person);     // Can modify, can't add/delete

// Property descriptors
Object.defineProperty(person, 'id', {
  value: 1,
  writable: false,
  enumerable: true,
  configurable: false
});

JavaScript Advanced Concepts

Closures

A closure is a function that has access to variables from its outer (enclosing) scope, even after the outer function has returned.

function createCounter() {
  let count = 0; // Private variable
  
  return {
    increment() { return ++count; },
    decrement() { return --count; },
    getCount() { return count; }
  };
}

const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.getCount());  // 2
// count is not accessible directly

// Common pitfall with closures in loops
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100); // 3, 3, 3
}

// Fix with let (block scope)
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100); // 0, 1, 2
}

// Fix with closure
for (var i = 0; i < 3; i++) {
  ((j) => {
    setTimeout(() => console.log(j), 100);
  })(i); // 0, 1, 2
}

this Keyword

// 1. Global context
console.log(this); // Window (browser) or global (Node.js)

// 2. Object method
const obj = {
  name: "Object",
  greet() {
    console.log(this.name); // "Object"
  }
};

// 3. Arrow functions (lexical this)
const obj2 = {
  name: "Object",
  greet: () => {
    console.log(this.name); // undefined (inherits from enclosing scope)
  },
  greetCorrect() {
    const inner = () => {
      console.log(this.name); // "Object" (inherits from greetCorrect)
    };
    inner();
  }
};

// 4. Explicit binding
function greet() {
  console.log(this.name);
}

const person = { name: "John" };

greet.call(person);       // "John" - immediate execution
greet.apply(person);      // "John" - immediate execution
const boundGreet = greet.bind(person);
boundGreet();             // "John" - returns new function

// call vs apply
function introduce(greeting, punctuation) {
  console.log(`${greeting}, I'm ${this.name}${punctuation}`);
}

introduce.call(person, "Hello", "!");   // Arguments as list
introduce.apply(person, ["Hello", "!"]); // Arguments as array

// 5. Constructor (new keyword)
function Person(name) {
  this.name = name;
}
const john = new Person("John");
console.log(john.name); // "John"

Prototypes & Inheritance

// Prototype chain
function Animal(name) {
  this.name = name;
}

Animal.prototype.speak = function() {
  console.log(`${this.name} makes a sound`);
};

function Dog(name, breed) {
  Animal.call(this, name);
  this.breed = breed;
}

// Set up inheritance
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

Dog.prototype.speak = function() {
  console.log(`${this.name} barks`);
};

const dog = new Dog("Rex", "German Shepherd");
dog.speak(); // "Rex barks"

// Prototype lookup
console.log(dog.__proto__ === Dog.prototype);              // true
console.log(Dog.prototype.__proto__ === Animal.prototype); // true
console.log(Animal.prototype.__proto__ === Object.prototype); // true

// Check prototype
console.log(dog instanceof Dog);    // true
console.log(dog instanceof Animal); // true
console.log(dog.hasOwnProperty('name'));  // true
console.log(dog.hasOwnProperty('speak')); // false (on prototype)

Event Loop & Call Stack

console.log('1'); // Synchronous - Call Stack

setTimeout(() => {
  console.log('2'); // Macro task - Task Queue
}, 0);

Promise.resolve().then(() => {
  console.log('3'); // Micro task - Microtask Queue
});

console.log('4'); // Synchronous - Call Stack

// Output: 1, 4, 3, 2

// Execution order:
// 1. Synchronous code runs first (Call Stack)
// 2. Microtasks (Promises, queueMicrotask, MutationObserver)
// 3. Macrotasks (setTimeout, setInterval, setImmediate, I/O)
┌───────────────────────────┐
│        Call Stack         │
│   (Synchronous code)      │
└───────────────────────────┘
            ↓
┌───────────────────────────┐
│     Microtask Queue       │
│  (Promises, async/await)  │
└───────────────────────────┘
            ↓
┌───────────────────────────┐
│    Macrotask Queue        │
│  (setTimeout, I/O, etc.)  │
└───────────────────────────┘

Memory Management & Garbage Collection

// Memory leaks examples

// 1. Forgotten timers
const data = getData();
setInterval(() => {
  console.log(data); // data is never garbage collected
}, 1000);

// 2. Detached DOM nodes
const button = document.getElementById('button');
const onClick = () => console.log('clicked');
button.addEventListener('click', onClick);
button.remove(); // Memory leak if listener not removed

// 3. Closures holding references
function outer() {
  const largeData = new Array(1000000);
  return function inner() {
    // largeData is retained even if not used
  };
}

// Fix: Set to null when done
let data = { large: new Array(1000000) };
// ... use data
data = null; // Allow garbage collection

Debounce & Throttle

// Debounce: Execute after delay, reset on new calls
function debounce(fn, delay) {
  let timeoutId;
  return function(...args) {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => fn.apply(this, args), delay);
  };
}

// Usage: Search input
const search = debounce((query) => {
  console.log('Searching:', query);
}, 300);

// Throttle: Execute at most once per interval
function throttle(fn, interval) {
  let lastTime = 0;
  return function(...args) {
    const now = Date.now();
    if (now - lastTime >= interval) {
      lastTime = now;
      fn.apply(this, args);
    }
  };
}

// Usage: Scroll handler
const handleScroll = throttle(() => {
  console.log('Scrolled');
}, 100);

Deep Clone vs Shallow Clone

const original = {
  name: "John",
  address: { city: "NYC" },
  hobbies: ["reading", "gaming"]
};

// Shallow Clone (nested objects share reference)
const shallowClone1 = { ...original };
const shallowClone2 = Object.assign({}, original);

shallowClone1.address.city = "LA";
console.log(original.address.city); // "LA" - mutated!

// Deep Clone methods
// 1. JSON (doesn't handle functions, undefined, symbols, circular refs)
const deepClone1 = JSON.parse(JSON.stringify(original));

// 2. structuredClone (modern, handles circular refs, not functions)
const deepClone2 = structuredClone(original);

// 3. Custom recursive function
function deepClone(obj, seen = new WeakMap()) {
  if (obj === null || typeof obj !== 'object') return obj;
  if (seen.has(obj)) return seen.get(obj);
  
  const clone = Array.isArray(obj) ? [] : {};
  seen.set(obj, clone);
  
  for (const key in obj) {
    if (obj.hasOwnProperty(key)) {
      clone[key] = deepClone(obj[key], seen);
    }
  }
  return clone;
}

Currying

// Transform f(a, b, c) into f(a)(b)(c)
function curry(fn) {
  return function curried(...args) {
    if (args.length >= fn.length) {
      return fn.apply(this, args);
    }
    return function(...nextArgs) {
      return curried.apply(this, args.concat(nextArgs));
    };
  };
}

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

const curriedAdd = curry(add);
console.log(curriedAdd(1)(2)(3));    // 6
console.log(curriedAdd(1, 2)(3));    // 6
console.log(curriedAdd(1)(2, 3));    // 6

// Practical use case
const log = curry((level, date, message) => {
  console.log(`[${level}] ${date}: ${message}`);
});

const errorLog = log('ERROR');
const todayErrorLog = errorLog(new Date().toISOString());
todayErrorLog('Something went wrong');

ES6+ Features

Let, Const & Block Scope

// Temporal Dead Zone (TDZ)
console.log(x); // ReferenceError
let x = 1;

// const with objects
const obj = { a: 1 };
obj.a = 2;        // Allowed - modifying property
obj = { b: 2 };   // TypeError - reassigning const

Arrow Functions

// Syntax variations
const fn1 = () => expression;
const fn2 = (a) => expression;
const fn3 = a => expression;
const fn4 = (a, b) => expression;
const fn5 = (a, b) => {
  // multiple statements
  return result;
};

// No own this, arguments, super, or new.target
const obj = {
  values: [1, 2, 3],
  double() {
    // Arrow inherits 'this' from double()
    return this.values.map(v => v * 2);
  }
};

// Cannot be used as constructors
const Foo = () => {};
new Foo(); // TypeError

Template Literals

const name = "World";

// Basic
const greeting = `Hello, ${name}!`;

// Multi-line
const html = `
  <div>
    <h1>${name}</h1>
  </div>
`;

// Tagged templates
function highlight(strings, ...values) {
  return strings.reduce((result, str, i) => {
    return result + str + (values[i] ? `<mark>${values[i]}</mark>` : '');
  }, '');
}

const user = "John";
const role = "admin";
highlight`User ${user} has role ${role}`;
// "User <mark>John</mark> has role <mark>admin</mark>"

Destructuring

// Array destructuring
const [a, b, ...rest] = [1, 2, 3, 4, 5];
const [first, , third] = [1, 2, 3]; // Skip elements
const [x = 10] = []; // Default value

// Object destructuring
const { name, age } = { name: "John", age: 30 };
const { name: userName } = { name: "John" }; // Rename
const { address: { city } } = { address: { city: "NYC" } }; // Nested
const { role = "user" } = {}; // Default value

// Function parameters
function greet({ name, age = 18 }) {
  console.log(`${name} is ${age}`);
}

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

Spread & Rest Operators

// Spread (expanding)
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 }

Math.max(...arr1); // 3

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

const [first, ...others] = [1, 2, 3, 4];
const { a, ...remaining } = { a: 1, b: 2, c: 3 };

Classes

class Animal {
  // Static property
  static kingdom = "Animalia";
  
  // Private field
  #id;
  
  constructor(name) {
    this.name = name;
    this.#id = Math.random();
  }
  
  // Instance method
  speak() {
    console.log(`${this.name} makes a sound`);
  }
  
  // Getter
  get info() {
    return `${this.name} (${this.#id})`;
  }
  
  // Setter
  set rename(newName) {
    this.name = newName;
  }
  
  // Static method
  static create(name) {
    return new Animal(name);
  }
}

class Dog extends Animal {
  constructor(name, breed) {
    super(name);
    this.breed = breed;
  }
  
  speak() {
    console.log(`${this.name} barks`);
  }
}

const dog = new Dog("Rex", "German Shepherd");
dog.speak(); // "Rex barks"

Modules

// Named exports
export const PI = 3.14159;
export function add(a, b) { return a + b; }
export class Calculator {}

// Default export
export default function main() {}

// Import named
import { PI, add } from './math.js';
import { add as sum } from './math.js'; // Rename

// Import default
import main from './main.js';

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

// Dynamic import
const module = await import('./module.js');

// Re-export
export { add } from './math.js';
export * from './utils.js';

Symbols

// Create unique identifiers
const id1 = Symbol('id');
const id2 = Symbol('id');
console.log(id1 === id2); // false

// Use as object keys
const obj = {
  [id1]: 'value1',
  name: 'John'
};

// Not enumerable
Object.keys(obj);           // ['name']
Object.getOwnPropertySymbols(obj); // [Symbol(id)]

// Well-known symbols
class MyArray {
  static [Symbol.hasInstance](instance) {
    return Array.isArray(instance);
  }
  
  *[Symbol.iterator]() {
    yield 1;
    yield 2;
  }
}

// Global symbol registry
const globalSym = Symbol.for('global');
Symbol.keyFor(globalSym); // 'global'

Iterators & Generators

// Iterator protocol
const iterable = {
  [Symbol.iterator]() {
    let count = 0;
    return {
      next() {
        count++;
        return count <= 3 
          ? { value: count, done: false }
          : { done: true };
      }
    };
  }
};

for (const value of iterable) {
  console.log(value); // 1, 2, 3
}

// Generator function
function* numberGenerator() {
  yield 1;
  yield 2;
  yield 3;
}

const gen = numberGenerator();
gen.next(); // { value: 1, done: false }
gen.next(); // { value: 2, done: false }
gen.next(); // { value: 3, done: false }
gen.next(); // { value: undefined, done: true }

// Generator with values
function* fibonacci() {
  let [prev, curr] = [0, 1];
  while (true) {
    yield curr;
    [prev, curr] = [curr, prev + curr];
  }
}

// Delegating generator
function* combined() {
  yield* [1, 2, 3];
  yield* "abc";
}
[...combined()]; // [1, 2, 3, 'a', 'b', 'c']

Map & Set

// Map - key-value pairs with any key type
const map = new Map();
map.set('name', 'John');
map.set({ id: 1 }, 'Object key');
map.get('name'); // 'John'
map.has('name'); // true
map.delete('name');
map.size; // 1
map.clear();

// WeakMap - keys must be objects, garbage collected
const weakMap = new WeakMap();
let obj = { id: 1 };
weakMap.set(obj, 'value');
obj = null; // Entry can be garbage collected

// Set - unique values
const set = new Set([1, 2, 2, 3]);
set.add(4);
set.has(3); // true
set.delete(3);
set.size; // 3

// Remove duplicates from array
const unique = [...new Set([1, 2, 2, 3])];

// WeakSet - objects only, garbage collected
const weakSet = new WeakSet();

Optional Chaining & Nullish Coalescing

const user = {
  name: "John",
  address: {
    city: "NYC"
  }
};

// Optional chaining (?.)
user?.address?.city;      // "NYC"
user?.contact?.phone;     // undefined (no error)
user?.getName?.();        // undefined (no error)
user?.hobbies?.[0];       // undefined (no error)

// Nullish coalescing (??)
const value = null ?? "default";      // "default"
const value2 = undefined ?? "default"; // "default"
const value3 = 0 ?? "default";        // 0 (0 is not nullish)
const value4 = "" ?? "default";       // "" (empty string is not nullish)

// Comparison with ||
const value5 = 0 || "default";  // "default" (0 is falsy)
const value6 = 0 ?? "default";  // 0 (0 is not nullish)

New Array Methods (ES2022+)

// Array.at() - negative indexing
const arr = [1, 2, 3, 4, 5];
arr.at(-1);  // 5
arr.at(-2);  // 4

// Array.findLast() / Array.findLastIndex()
const numbers = [1, 2, 3, 4, 3, 2, 1];
numbers.findLast(n => n > 2);      // 3
numbers.findLastIndex(n => n > 2); // 4

// Array.toSorted(), toReversed(), toSpliced() - non-mutating
const sorted = arr.toSorted();          // New sorted array
const reversed = arr.toReversed();      // New reversed array
const spliced = arr.toSpliced(1, 2, 9); // New spliced array

// Array.with() - non-mutating index replacement
const newArr = arr.with(0, 10); // [10, 2, 3, 4, 5]

// Object.groupBy() (ES2024)
const items = [
  { type: 'fruit', name: 'apple' },
  { type: 'fruit', name: 'banana' },
  { type: 'vegetable', name: 'carrot' }
];
Object.groupBy(items, item => item.type);
// { fruit: [...], vegetable: [...] }

Node.js

Node.js Architecture

┌─────────────────────────────────────────────────┐
│                  Application                     │
├─────────────────────────────────────────────────┤
│                    Node.js                       │
│  ┌───────────────┐  ┌────────────────────────┐  │
│  │   V8 Engine   │  │        libuv           │  │
│  │  (JavaScript) │  │  (Event Loop, Async I/O│  │
│  └───────────────┘  └────────────────────────┘  │
├─────────────────────────────────────────────────┤
│                 Operating System                 │
└─────────────────────────────────────────────────┘

Key Components:

  • V8: JavaScript engine (compiles JS to machine code)
  • libuv: Cross-platform async I/O library
  • Event Loop: Handles async operations
  • Thread Pool: For CPU-intensive operations (default: 4 threads)

Event Loop Phases

/*
   ┌───────────────────────────┐
┌─>│           timers          │ (setTimeout, setInterval)
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │     pending callbacks     │ (I/O callbacks deferred)
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │       idle, prepare       │ (internal use)
│  └─────────────┬─────────────┘      ┌───────────────┐
│  ┌─────────────┴─────────────┐      │   incoming:   │
│  │           poll            │<─────┤  connections, │
│  └─────────────┬─────────────┘      │   data, etc.  │
│  ┌─────────────┴─────────────┐      └───────────────┘
│  │           check           │ (setImmediate)
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
└──┤      close callbacks      │ (socket.on('close'))
   └───────────────────────────┘

Between each phase: process.nextTick() and Promises
*/

// Execution order
setTimeout(() => console.log('timeout'), 0);
setImmediate(() => console.log('immediate'));
process.nextTick(() => console.log('nextTick'));
Promise.resolve().then(() => console.log('promise'));
console.log('sync');

// Output: sync, nextTick, promise, timeout, immediate
// (timeout/immediate order may vary)

Modules System

// CommonJS (CJS) - Node.js default
// Synchronous loading, dynamic
const fs = require('fs');
const { readFile } = require('fs');
module.exports = { myFunction };
exports.myFunction = () => {};

// ES Modules (ESM) - Modern
// Async loading, static analysis
import fs from 'fs';
import { readFile } from 'fs';
export const myFunction = () => {};
export default myFunction;

// Use ESM in Node.js:
// 1. Use .mjs extension
// 2. Add "type": "module" in package.json

// Dynamic imports (works in both)
const module = await import('./module.js');

File System

const fs = require('fs');
const fsPromises = require('fs').promises;

// Sync (blocking - avoid in production)
const data = fs.readFileSync('file.txt', 'utf8');
fs.writeFileSync('file.txt', 'content');

// Callback (async)
fs.readFile('file.txt', 'utf8', (err, data) => {
  if (err) throw err;
  console.log(data);
});

// Promises (modern async)
const data = await fsPromises.readFile('file.txt', 'utf8');
await fsPromises.writeFile('file.txt', 'content');

// Streams (memory efficient for large files)
const readable = fs.createReadStream('large-file.txt');
const writable = fs.createWriteStream('output.txt');

readable.pipe(writable);

readable.on('data', chunk => console.log(chunk));
readable.on('end', () => console.log('Done'));
readable.on('error', err => console.error(err));

Streams

const { Readable, Writable, Transform, pipeline } = require('stream');
const fs = require('fs');

// Readable stream
const readable = new Readable({
  read(size) {
    this.push('data');
    this.push(null); // Signal end
  }
});

// Writable stream
const writable = new Writable({
  write(chunk, encoding, callback) {
    console.log(chunk.toString());
    callback();
  }
});

// Transform stream
const uppercase = new Transform({
  transform(chunk, encoding, callback) {
    callback(null, chunk.toString().toUpperCase());
  }
});

// Pipeline (handles errors and cleanup)
const { pipeline } = require('stream/promises');

await pipeline(
  fs.createReadStream('input.txt'),
  uppercase,
  fs.createWriteStream('output.txt')
);

// Stream types:
// - Readable: fs.createReadStream, http request
// - Writable: fs.createWriteStream, http response
// - Duplex: net.Socket (both read and write)
// - Transform: zlib.createGzip (modify data)

Events

const EventEmitter = require('events');

class MyEmitter extends EventEmitter {}
const emitter = new MyEmitter();

// Listen to events
emitter.on('data', (arg1, arg2) => {
  console.log('data event:', arg1, arg2);
});

// Listen once
emitter.once('init', () => {
  console.log('init - only runs once');
});

// Emit events
emitter.emit('data', 'arg1', 'arg2');
emitter.emit('init');

// Remove listener
const handler = () => console.log('handler');
emitter.on('event', handler);
emitter.off('event', handler);

// Error handling
emitter.on('error', (err) => {
  console.error('Error:', err);
});

// Get listener count
emitter.listenerCount('data');

// Async iteration (Node.js 10+)
const { on } = require('events');
for await (const [data] of on(emitter, 'data')) {
  console.log(data);
}

HTTP/HTTPS

const http = require('http');
const https = require('https');

// Create server
const server = http.createServer((req, res) => {
  const { method, url, headers } = req;
  
  // Read request body
  let body = '';
  req.on('data', chunk => body += chunk);
  req.on('end', () => {
    res.writeHead(200, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ message: 'Hello' }));
  });
});

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

// Make HTTP request
const options = {
  hostname: 'api.example.com',
  port: 443,
  path: '/users',
  method: 'GET',
  headers: { 'Content-Type': 'application/json' }
};

const req = https.request(options, (res) => {
  let data = '';
  res.on('data', chunk => data += chunk);
  res.on('end', () => console.log(JSON.parse(data)));
});

req.on('error', console.error);
req.end();

// Using fetch (Node.js 18+)
const response = await fetch('https://api.example.com/users');
const data = await response.json();

Process & Environment

// Environment variables
process.env.NODE_ENV;
process.env.PORT;

// Command line arguments
process.argv; // [node path, script path, ...args]

// Current working directory
process.cwd();

// Exit
process.exit(0); // Success
process.exit(1); // Error

// Signals
process.on('SIGINT', () => {
  console.log('Received SIGINT. Graceful shutdown...');
  process.exit(0);
});

process.on('uncaughtException', (err) => {
  console.error('Uncaught Exception:', err);
  process.exit(1);
});

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

// Memory usage
process.memoryUsage();
// { rss, heapTotal, heapUsed, external, arrayBuffers }

// Next tick
process.nextTick(() => {
  // Executes before I/O callbacks
});

Child Processes

const { exec, execSync, spawn, fork } = require('child_process');

// exec - buffered output, shell
exec('ls -la', (error, stdout, stderr) => {
  if (error) console.error(error);
  console.log(stdout);
});

// execSync - synchronous
const output = execSync('ls -la', { encoding: 'utf8' });

// spawn - streamed output, no shell by default
const child = spawn('ls', ['-la']);
child.stdout.on('data', data => console.log(data.toString()));
child.stderr.on('data', data => console.error(data.toString()));
child.on('close', code => console.log(`Exit code: ${code}`));

// fork - for Node.js scripts, IPC channel
// parent.js
const child = fork('./child.js');
child.send({ type: 'START' });
child.on('message', msg => console.log('From child:', msg));

// child.js
process.on('message', msg => {
  console.log('From parent:', msg);
  process.send({ type: 'DONE' });
});

Worker Threads

const { Worker, isMainThread, parentPort, workerData } = require('worker_threads');

if (isMainThread) {
  // Main thread
  const worker = new Worker(__filename, {
    workerData: { num: 42 }
  });
  
  worker.on('message', result => {
    console.log('Result:', result);
  });
  
  worker.on('error', console.error);
  worker.on('exit', code => {
    if (code !== 0) console.error(`Worker stopped with code ${code}`);
  });
  
  worker.postMessage('start');
} else {
  // Worker thread
  parentPort.on('message', msg => {
    const result = heavyComputation(workerData.num);
    parentPort.postMessage(result);
  });
}

// Worker pool for better performance
const { StaticPool } = require('node-worker-threads-pool');
const pool = new StaticPool({
  size: 4,
  task: (n) => fibonacci(n)
});

const result = await pool.exec(40);

Buffer

// Create buffers
const buf1 = Buffer.alloc(10);           // Zero-filled
const buf2 = Buffer.allocUnsafe(10);     // Uninitialized (faster)
const buf3 = Buffer.from('Hello');       // From string
const buf4 = Buffer.from([1, 2, 3]);     // From array

// Convert
buf3.toString();                         // 'Hello'
buf3.toString('base64');                 // 'SGVsbG8='
Buffer.from('SGVsbG8=', 'base64').toString(); // 'Hello'

// Operations
buf1.length;                             // 10
buf1.write('Hi');                        // Write string
buf1[0];                                 // Read byte
Buffer.concat([buf1, buf2]);             // Concatenate
buf1.slice(0, 5);                        // Slice (shared memory!)
buf1.copy(buf2);                         // Copy
buf1.equals(buf2);                       // Compare

// JSON
const json = buf3.toJSON();
// { type: 'Buffer', data: [72, 101, 108, 108, 111] }

Async/Await & Promises

Callbacks (Traditional)

// Callback hell / Pyramid of doom
getData(function(a) {
  getMoreData(a, function(b) {
    getEvenMoreData(b, function(c) {
      getYetMoreData(c, function(d) {
        console.log(d);
      });
    });
  });
});

// Error-first callback pattern (Node.js convention)
function readFile(path, callback) {
  // ... async operation
  if (error) {
    callback(error, null);
  } else {
    callback(null, data);
  }
}

readFile('file.txt', (err, data) => {
  if (err) {
    console.error(err);
    return;
  }
  console.log(data);
});

Promises

// Creating a Promise
const promise = new Promise((resolve, reject) => {
  // Async operation
  setTimeout(() => {
    if (success) {
      resolve('Success!');
    } else {
      reject(new Error('Failed'));
    }
  }, 1000);
});

// Consuming Promises
promise
  .then(result => {
    console.log(result);
    return 'Next value';
  })
  .then(next => console.log(next))
  .catch(error => console.error(error))
  .finally(() => console.log('Always runs'));

// Promise states:
// - Pending: initial state
// - Fulfilled: operation completed successfully
// - Rejected: operation failed

// Promise static methods
Promise.resolve(value);        // Create fulfilled promise
Promise.reject(error);         // Create rejected promise

// All must succeed (fail-fast on first rejection)
Promise.all([p1, p2, p3])
  .then(([r1, r2, r3]) => {});

// All settle (never rejects)
Promise.allSettled([p1, p2, p3])
  .then(results => {
    results.forEach(result => {
      if (result.status === 'fulfilled') {
        console.log(result.value);
      } else {
        console.log(result.reason);
      }
    });
  });

// First to settle
Promise.race([p1, p2, p3])
  .then(firstResult => {});

// First to fulfill
Promise.any([p1, p2, p3])
  .then(firstSuccess => {})
  .catch(aggregateError => {}); // All rejected

// Promise.withResolvers() (ES2024)
const { promise, resolve, reject } = Promise.withResolvers();

Async/Await

// Basic syntax
async function fetchData() {
  try {
    const response = await fetch('/api/data');
    const data = await response.json();
    return data;
  } catch (error) {
    console.error('Error:', error);
    throw error;
  }
}

// Arrow function
const fetchData = async () => {
  const data = await fetch('/api/data');
  return data.json();
};

// Class method
class API {
  async getData() {
    return await fetch('/api/data');
  }
}

// Parallel execution
async function parallel() {
  // Sequential (slow)
  const a = await fetchA();
  const b = await fetchB();
  
  // Parallel (fast)
  const [a, b] = await Promise.all([fetchA(), fetchB()]);
  
  // Parallel with individual handling
  const results = await Promise.allSettled([fetchA(), fetchB()]);
}

// Error handling patterns
async function withErrorHandling() {
  // Try-catch
  try {
    await riskyOperation();
  } catch (error) {
    handleError(error);
  }
  
  // Catch on await
  const result = await riskyOperation().catch(handleError);
  
  // Optional catch
  const [error, data] = await to(riskyOperation());
  if (error) handleError(error);
}

// Helper for tuple return
function to(promise) {
  return promise
    .then(data => [null, data])
    .catch(err => [err, null]);
}

// Top-level await (ES2022, ESM only)
const config = await loadConfig();
export { config };

Advanced Async Patterns

// Async iteration
async function* asyncGenerator() {
  yield await fetchData(1);
  yield await fetchData(2);
  yield await fetchData(3);
}

for await (const data of asyncGenerator()) {
  console.log(data);
}

// Sequential processing with reduce
const results = await urls.reduce(async (accPromise, url) => {
  const acc = await accPromise;
  const data = await fetch(url);
  return [...acc, data];
}, Promise.resolve([]));

// Concurrent with limit
async function mapWithLimit(items, limit, fn) {
  const results = [];
  const executing = [];
  
  for (const item of items) {
    const promise = fn(item).then(result => {
      executing.splice(executing.indexOf(promise), 1);
      return result;
    });
    
    results.push(promise);
    executing.push(promise);
    
    if (executing.length >= limit) {
      await Promise.race(executing);
    }
  }
  
  return Promise.all(results);
}

// Retry pattern
async function retry(fn, retries = 3, delay = 1000) {
  for (let i = 0; i < retries; i++) {
    try {
      return await fn();
    } catch (error) {
      if (i === retries - 1) throw error;
      await new Promise(r => setTimeout(r, delay * (i + 1)));
    }
  }
}

// Timeout wrapper
function withTimeout(promise, ms) {
  const timeout = new Promise((_, reject) => {
    setTimeout(() => reject(new Error('Timeout')), ms);
  });
  return Promise.race([promise, timeout]);
}

// Debounced async function
function debounceAsync(fn, delay) {
  let timeoutId;
  let pendingPromise = null;
  
  return function(...args) {
    if (pendingPromise) {
      clearTimeout(timeoutId);
    }
    
    return new Promise((resolve, reject) => {
      timeoutId = setTimeout(async () => {
        try {
          const result = await fn.apply(this, args);
          resolve(result);
        } catch (error) {
          reject(error);
        }
      }, delay);
    });
  };
}

Converting Callbacks to Promises

const { promisify } = require('util');
const fs = require('fs');

// Using promisify
const readFile = promisify(fs.readFile);
const data = await readFile('file.txt', 'utf8');

// Manual promisification
function promisifiedReadFile(path, options) {
  return new Promise((resolve, reject) => {
    fs.readFile(path, options, (err, data) => {
      if (err) reject(err);
      else resolve(data);
    });
  });
}

// Promisify entire object
const fsPromises = require('fs').promises;
// or
const fs = require('fs/promises');

Interview Questions

JavaScript Questions

Q1: What is the difference between == and ===?

// == performs type coercion, === doesn't
5 == "5"   // true
5 === "5"  // false
null == undefined  // true
null === undefined // false

Q2: Explain closures with an example.

function counter() {
  let count = 0;
  return function() {
    return ++count;
  };
}
const increment = counter();
increment(); // 1
increment(); // 2
// count is enclosed in the returned function

Q3: What is event delegation?

// Instead of adding listeners to each child
document.getElementById('list').addEventListener('click', (e) => {
  if (e.target.matches('li')) {
    console.log(e.target.textContent);
  }
});
// Benefits: Better memory, handles dynamic elements

Q4: What is the difference between null and undefined?

// undefined: variable declared but not assigned
let x;
console.log(x); // undefined

// null: intentional absence of value
let y = null;

typeof undefined // "undefined"
typeof null      // "object" (historical bug)

Q5: Explain the difference between var, let, and const.

// var: function-scoped, hoisted, can redeclare
// let: block-scoped, TDZ, can reassign
// const: block-scoped, TDZ, cannot reassign (but objects can be mutated)

Q6: What is a pure function?

// Pure: same input = same output, no side effects
const add = (a, b) => a + b;

// Impure: depends on external state or has side effects
let total = 0;
const addToTotal = (n) => total += n;

Q7: What is hoisting?

console.log(x); // undefined (var is hoisted)
console.log(y); // ReferenceError (TDZ)
var x = 1;
let y = 2;

// Function declarations are fully hoisted
foo(); // Works!
function foo() {}

// Function expressions are not
bar(); // TypeError
var bar = function() {};

Q8: What is the prototype chain?

const obj = { a: 1 };
// obj -> Object.prototype -> null

const arr = [1, 2];
// arr -> Array.prototype -> Object.prototype -> null

obj.hasOwnProperty('a'); // Inherited from Object.prototype

Node.js Questions

Q1: What is the event loop and how does it work?

// Single-threaded, non-blocking I/O model
// Phases: timers -> pending -> idle -> poll -> check -> close
// Microtasks run between each phase (nextTick, Promises)

Q2: What is the difference between process.nextTick() and setImmediate()?

// nextTick: runs before I/O callbacks (microtask)
// setImmediate: runs after I/O callbacks (check phase)
process.nextTick(() => console.log('nextTick'));
setImmediate(() => console.log('immediate'));
// Output: nextTick, immediate

Q3: How do you handle uncaught exceptions in Node.js?

process.on('uncaughtException', (err) => {
  console.error('Uncaught:', err);
  // Graceful shutdown
  process.exit(1);
});

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

Q4: What are streams and when should you use them?

// Streams handle data piece by piece (chunks)
// Use for: large files, network data, real-time processing
// Types: Readable, Writable, Duplex, Transform

// Memory efficient: doesn't load entire file
const readable = fs.createReadStream('large-file.txt');
readable.pipe(res); // Stream to HTTP response

Q5: Explain the difference between CommonJS and ES Modules.

// CommonJS (CJS): synchronous, dynamic, Node.js default
const module = require('module');
module.exports = {};

// ES Modules (ESM): asynchronous, static, tree-shakeable
import module from 'module';
export default {};

Async/Await Questions

Q1: What happens if you don't await a Promise?

async function example() {
  fetchData(); // Returns Promise, doesn't wait
  // Next line executes immediately
  console.log('Done'); // Runs before fetchData completes
}

Q2: How do you handle errors in async/await?

// Try-catch
try {
  await riskyOperation();
} catch (error) {
  handleError(error);
}

// Or with .catch()
await riskyOperation().catch(handleError);

Q3: How do you run async operations in parallel?

// Wrong (sequential)
const a = await fetchA();
const b = await fetchB();

// Right (parallel)
const [a, b] = await Promise.all([fetchA(), fetchB()]);

Q4: What is the output of this code?

async function test() {
  console.log('1');
  await Promise.resolve();
  console.log('2');
}
console.log('3');
test();
console.log('4');

// Output: 3, 1, 4, 2
// Explanation: 
// - '3' (sync)
// - '1' (sync part of test)
// - '4' (sync)
// - '2' (after await, microtask)

Q5: How do you implement a timeout for an async operation?

function timeout(ms) {
  return new Promise((_, reject) => 
    setTimeout(() => reject(new Error('Timeout')), ms)
  );
}

async function fetchWithTimeout(url, ms) {
  return Promise.race([
    fetch(url),
    timeout(ms)
  ]);
}

Quick Reference Cheat Sheet

Type Checking

typeof "string"      // "string"
typeof 42            // "number"
typeof true          // "boolean"
typeof undefined     // "undefined"
typeof null          // "object" (bug!)
typeof {}            // "object"
typeof []            // "object"
typeof function(){}  // "function"
typeof Symbol()      // "symbol"
typeof 42n           // "bigint"

Array.isArray([])    // true
obj instanceof Class // prototype check
Object.prototype.toString.call([]) // "[object Array]"

Array Methods Summary

// Mutating: push, pop, shift, unshift, splice, sort, reverse, fill
// Non-mutating: map, filter, reduce, find, some, every, slice, concat
// ES2023: toSorted, toReversed, toSpliced, with

Promise Methods Summary

Promise.all([])        // All succeed or first reject
Promise.allSettled([]) // Wait for all, never rejects
Promise.race([])       // First to settle
Promise.any([])        // First to fulfill
Promise.resolve(v)     // Create fulfilled
Promise.reject(e)      // Create rejected

Common Gotchas

// 1. typeof null === "object"
// 2. NaN !== NaN (use Number.isNaN())
// 3. 0.1 + 0.2 !== 0.3 (floating point)
// 4. [] + [] === "" (string)
// 5. [] + {} === "[object Object]"
// 6. {} + [] === 0 (block + array)
// 7. Array(3) creates sparse array with length 3
// 8. parseInt("08") might return 0 in old browsers

Last updated: January 2026

Duong Ngo

Duong Ngo

Full-Stack AI Developer with 12+ years of experience

Comments