🚀 Introduction — JavaScript Today
JavaScript is a universal language: browsers, servers (Node.js), edge functions, desktop apps, mobile frameworks, and more. Its asynchronous model and lightweight objects make it great for DSA when paired with correct data structures and patterns.
This guide focuses on conceptual clarity: runtime behavior, async patterns, modern ES features, and DSA-friendly idioms for competitive and production code.
⚙️ JS Engines & Runtime
Major engines: V8 (Chrome, Node.js), SpiderMonkey (Firefox), JavaScriptCore (Safari).
- Parser → AST → Baseline JIT → Optimizing JIT: engines parse code, run baseline, then optimize hot functions.
- Hidden classes & inline caches optimize object property access — predictable shapes are faster.
- Memory: GC-managed heap for objects; stack for call frames and primitives.
Implication: writing predictable object shapes and avoiding excessive dynamic changes helps performance.
🧩 Types, Coercion & Equality
JS has dynamic types and subtle coercion rules. Know the differences between == and ===.
```
typeof 42 // "number"
typeof "hi" // "string"
[] + {} // "[object Object]" or "[object Object]" depending on engine (avoid).
0 == "0" // true (coercion)
0 === "0" // false (strict) Prefer ===, avoid relying on coercion, and use Number()/String() for conversions when explicitness helps.
🔒 Scope, Hoisting & Closures
Three scope keywords: var (function-scoped, hoisted), let and const (block-scoped, no temporal dead zone after initialization).
```
function makeAdder(n) {
return function(x) { return x + n; }; // closed-over n
}
const add5 = makeAdder(5);
console.log(add5(2)); // 7 Closures capture variables by reference to the environment — watch loops capturing variables (use let or IIFE fixes).
🧠 Functions: Declarations, Expressions & Arrow Functions
Arrow functions are concise but have lexical this. Understand when to use each.
```
function foo(){ console.log(this); } // dynamic this
const bar = () => { console.log(this); } // lexically bound this
const add = (a,b) => a + b; Use arrow functions for callbacks where you want lexical this; use function declarations when you need hoisting or prototype methods.
🧩 Objects, Prototypes & Classes
JS uses prototype-based inheritance. ES6 class is syntax sugar over prototypes.
```
class Animal {
constructor(name){ this.name = name; }
speak(){ return this.name + ' makes a noise'; }
}
class Dog extends Animal {
speak(){ return this.name + ' barks'; }
}
const d = new Dog('Rex'); Prefer composition over deep inheritance chains. Keep object shapes stable to benefit JIT optimizations.
⚡ Async Patterns: Callbacks → Promises → Async/Await
JS is single-threaded (per runtime) with asynchronous I/O. Understand the evolution:
- Callbacks: basic but error-prone (callback hell).
- Promises: chainable, catchable, avoid pyramid of doom.
- Async/Await: syntactic sugar over promises for readable async code.
```
function wait(ms){ return new Promise(res => setTimeout(res, ms)); }
async function fetchAndProcess(){
await wait(100);
const res = await fetch('/data');
const json = await res.json();
return json;
} Always handle rejected promises (use try/catch or .catch). Use Promise.allSettled for partial failures.
🔁 Event Loop, Microtasks & Macrotasks
Execution ordering:
- Call stack executes code synchronously.
- Microtasks (Promises .then, queueMicrotask) run after current stack and before next macrotask.
- Macrotasks (setTimeout, setInterval, I/O callbacks) run after microtasks.
```
console.log('start');
setTimeout(()=> console.log('timeout'),0);
Promise.resolve().then(()=> console.log('promise'));
console.log('end');
// Output: start, end, promise, timeout Use microtask/macro understanding to avoid surprising ordering (e.g., update UI after awaited ops).
📦 Modules: ES Modules & CommonJS
Two module systems:
- ES Modules (ESM):
import/export, static, supported by browsers & Node (with type: module). - CommonJS:
require/module.exports, Node legacy, dynamic loading.
```
/* ES Module */
export function add(a,b){ return a+b; }
import { add } from './math.js';
/* CommonJS (Node)
module.exports = { add };
const { add } = require('./math');
*/ Prefer ESM for new projects; understand bundlers (webpack, rollup, Vite) for browser builds.
🌐 DOM, Events & Browser APIs
The DOM is the browser object model. Use event delegation and passive listeners where appropriate.
```
document.getElementById('btn').addEventListener('click', (e) => {
e.preventDefault();
fetch('/api').then(r => r.json()).then(data => console.log(data));
}); Familiarize with fetch, WebSockets, localStorage, service workers, and performance APIs.
🟩 Node.js Essentials
Node.js uses V8 and libuv for async I/O. Key modules: fs, http, stream, worker_threads.
```
import fs from 'fs/promises'; // ESM
const data = await fs.readFile('./file.txt', 'utf8');
import http from 'http';
const server = http.createServer((req,res) => {
res.end('hello');
});
server.listen(3000); Understand streams for memory-efficient I/O and prefer async/await for clarity. Use PM2, Docker, and proper logging in production.
📚 Data Structures: Arrays, Map, Set, WeakMap
- Array: dynamic indexed list; many helpful methods (map, filter, reduce).
- Map: key-value store with arbitrary keys and predictable iteration order.
- Set: unique values.
- WeakMap / WeakSet: keys held weakly for GC (use for caches with object keys).
```
const m = new Map(); m.set({id:1}, 'obj'); // object key
const s = new Set([1,2,3]); s.has(2); // true
const arr = [1,2,3].map(x => x*2); Choose data structures based on access patterns: use Map for frequent key lookups, Set for unique collections, arrays for ordered data.
♻️ Functional Patterns & Immutability
JS supports functional style: pure functions, higher-order functions, immutability (with libraries or spread syntax).
``` const nums = [1,2,3]; const squares = nums.map(x => x*x); const filtered = nums.filter(x => x % 2 === 1); const sum = nums.reduce((a,b) => a+b, 0);
Prefer immutability in shared state scenarios (React/Redux patterns). Use structured cloning or libs for complex immutable updates.
⚙️ Algorithms & Helpers
JavaScript doesn't include a heap by default; implement or use libraries for priority queues. Use built-in Array.prototype.sort (be mindful it sorts strings by default).
``` const a = [5,2,9,1]; a.sort((x,y) => x - y); // numeric ascending
Remember complexities: array push/pop O(1), shift/unshift O(n), map/get/set average O(1) for Map/Set.
🎯 DSA Techniques in JavaScript
- Use typed arrays (Int32Array, Float64Array) for numeric performance when applicable.
- Implement priority queue for k-th elements or Dijkstra.
- Prefer Map over object when keys are not strings or you need predictable semantics.
- Avoid excessive object churn in hot loops — reuse arrays or preallocate when possible.
- Use iterative solutions to avoid deep recursion limits in Node/browser stacks.
🧩 Expanded DSA Examples (JavaScript)
1. BFS (Adjacency List)
```
function bfs(adj, start) {
const n = adj.length;
const vis = Array(n).fill(false);
const q = [start]; vis[start] = true;
const res = [];
while (q.length) {
const u = q.shift(); // O(n) — for large graphs use deque implementation
res.push(u);
for (const v of adj[u]) {
if (!vis[v]) { vis[v] = true; q.push(v); }
}
}
return res;
}
```
2. Sliding Window Maximum (Deque implemented)
```
function maxSlidingWindow(nums, k) {
const dq = []; // store indices
const res = [];
for (let i = 0; i < nums.length; i++) {
if (dq.length && dq[0] === i - k) dq.shift();
while (dq.length && nums[dq[dq.length-1]] < nums[i]) dq.pop();
dq.push(i);
if (i >= k - 1) res.push(nums[dq[0]]);
}
return res;
}
```
3. Min-Heap (Priority Queue)
```
class MinHeap {
constructor(){ this.a = []; }
size(){ return this.a.length; }
push(x){ this.a.push(x); this._siftUp(this.a.length-1); }
pop(){
if(!this.a.length) return undefined;
const r = this.a[0];
const last = this.a.pop();
if(this.a.length) { this.a[0]=last; this._siftDown(0); }
return r;
}
_siftUp(i){
while(i>0){
const p = Math.floor((i-1)/2);
if(this.a[p] <= this.a[i]) break;
[this.a[p], this.a[i]] = [this.a[i], this.a[p]];
i = p;
}
}
_siftDown(i){
const n = this.a.length;
while(true){
let l = 2*i+1, r = 2*i+2, smallest = i;
if(lCopy
```
4. Union-Find (Disjoint Set)
```
class DSU {
constructor(n){ this.p = Array.from({length:n}, (_,i) => i); this.r = Array(n).fill(0); }
find(x){ return this.p[x] === x ? x : (this.p[x] = this.find(this.p[x])); }
union(a,b){
a = this.find(a); b = this.find(b); if(a === b) return false;
if(this.r[a] < this.r[b]) [a,b] = [b,a];
this.p[b] = a; if(this.r[a] === this.r[b]) this.r[a]++;
return true;
}
}
```
5. Bit Manipulation — Single Number (XOR)
```
function singleNumber(nums) {
return nums.reduce((acc, x) => acc ^ x, 0);
}
```
Practice: prefer typed arrays and heap implementation for performance-critical tasks; tests matter — Node and browser stacks can differ.
✅ Best Practices & Common Pitfalls
- Use
constby default,letfor reassignments; avoidvar. - Avoid mutating objects passed into functions unless intended (prefer pure functions).
- Always handle promise rejections — uncaught rejections can crash Node (older versions) or be ignored.
- Use linting & formatters (ESLint, Prettier) and strict TypeScript or JSDoc for safer large codebases.
- Be explicit with conversions; avoid relying on coercion rules for correctness.
- Prefer small, testable functions. Write unit tests for critical DSA helpers (heap, dsu).
🏁 Final Summary — JavaScript Proficiency Checklist
Key takeaways to be productive and DSA-ready:
- Understand runtime & engine optimizations (object shapes, hidden classes).
- Master async flow: event loop, promises, async/await, microtasks.
- Use appropriate data structures (Map/Set/TypedArray) and implement missing ones (heap, deque).
- Prefer readable, testable patterns; avoid subtle coercion bugs.
- For competitive problems, pre-optimize algorithmic choices and prefer Node CLI with fast I/O patterns.
Next: implement these DSA patterns in JS projects and contest practice; write small benchmarks when performance matters.