110 Questions · Click any card to reveal the answer · Track your progress below
var, let, and const with examples.
Easy
▼
undefined, can be reassigned and redeclared, creates global properties.
let is block-scoped, hoisted but in TDZ (Temporal Dead Zone), can be reassigned but not redeclared.
const is block-scoped, hoisted but in TDZ, cannot be reassigned or redeclared (but objects/arrays it references CAN be mutated).
var is function-scoped (leaks out of blocks), let and const are block-scoped. Always use const by default.var, let, and const?
Medium
▼
undefined.
let and const are hoisted but NOT initialized — they sit in a Temporal Dead Zone (TDZ) from the start of the block until the declaration line. Accessing them before declaration throws ReferenceError.
Function declarations are fully hoisted (name + body). Function expressions are NOT.
== and ===?
Easy
▼
5 == "5" → true (string coerced to number)
5 === "5" → false (different types)
null == undefined → true
null === undefined → false
Always use === to avoid subtle bugs.null and undefined?
Easy
▼
undefined is automatically assigned by JavaScript; null is only assigned by the developer.
• typeof undefined === "undefined" but typeof null === "object" (a famous JS bug from the first implementation).
• undefined == null is true (loose equality treats them as equal), but undefined === null is false.
• Use null when you want to explicitly clear a variable. Use undefined to check if a variable/property has never been assigned.
this in: a regular function, an arrow function, and a method.
Medium
▼
this depends on HOW it's called (dynamic binding). Called globally → global object (undefined in strict mode).
Method: this = the object before the dot (the caller).
Arrow function: this = the enclosing scope where it was DEFINED (lexical/static binding) — cannot be changed with call/apply/bind.
this; they inherit from enclosing scope. Regular functions get their own this based on how they're called.
2. arguments object: Arrow functions don't have an arguments object — use rest parameters (...args) instead. Regular functions have arguments.
3. Constructor: Arrow functions CANNOT be used with new. They have no prototype and throw errors as constructors.
4. Syntax: Arrow functions have shorter syntax with implicit return.
5. call/apply/bind: These have no effect on this in arrow functions.addEventListener's third parameter controls which phase the handler runs in:
• false (default) = the handler runs during the bubbling phase.
• true = the handler runs during the capturing phase.
Common methods:
• event.stopPropagation() — stops the event from continuing to other elements (prevents bubbling/capturing).
• event.stopImmediatePropagation() — stops other handlers on the same element too.
• event.preventDefault() — prevents the default behavior (e.g., form submission, link navigation).
call, apply, and bind? Give one example each.
Medium
▼
this refers to.
call: invokes immediately, arguments passed individually.
apply: invokes immediately, arguments passed as an array.
bind: does NOT invoke immediately, returns a NEW function with this permanently bound.
map (transforms each element), filter (keeps elements that pass a test), reduce (accumulates into a single value). Also: forEach, find, sort, every, some.
console.log(a); var a = 10; console.log(a);
Easy
▼
undefined, then 10.
Explanation: JavaScript hoists var declarations to the top of their scope during compilation and initializes them to undefined. So when console.log(a) runs the first time, the declaration var a has been hoisted (but not the assignment), so it prints undefined. Then the assignment a = 10 executes. The second console.log(a) sees the now-assigned value 10.
This is different from let which is hoisted but not initialized (TDZ).console.log(b); let b = 20;
Easy
▼
ReferenceError: Cannot access 'b' before initialization.
Explanation: let (and const) are hoisted to the top of their block scope, but unlike var, they are NOT initialized. They remain in a "Temporal Dead Zone" (TDZ) from the start of the block until the actual declaration line is reached. Any access to b before its declaration throws a ReferenceError.
This is a safety feature — it prevents using a variable before it's properly initialized. The TDZ exists from the beginning of the block scope to just before the let / const declaration.function outer() { let count = 0; return function inner() { count++; console.log(count); } } const fn = outer(); fn(); fn(); fn();
Medium
▼
1, 2, 3.
Explanation: This is a classic closure example. When outer() is called, it creates a local variable count = 0 and returns the inner function. The inner function forms a closure over the outer function's scope — it "remembers" the count variable even after outer() has finished executing.
Each time fn() is called, it increments the same count variable and prints it. Since count is captured by reference (not by value), it persists between calls: 1, then 2, then 3.
If you called outer() again and got a new function, you'd get a fresh count starting at 0 again.obj.getName() and obj.getNameArrow() with name: "TCS"
Medium
▼
TCS, then undefined.
Explanation: For obj.getName(): this is a regular method call, so this inside the function refers to obj (the object before the dot). Since obj.name is "TCS", it prints TCS.
For obj.getNameArrow(): arrow functions don't have their own this. They inherit this from the enclosing lexical scope where they were defined (not where they're called). Since the arrow function was defined in the global/module scope, this refers to the global object (or undefined in strict mode). The global object has no name property, so it prints undefined.
This is the key difference: regular functions get a dynamic this based on how they're called; arrow functions have a static/lexical this.setTimeout(() => console.log("A"), 0); Promise.resolve().then(() => console.log("B")); console.log("C");
Hard
▼
C, B, A.
Explanation: JavaScript's event loop processes code in a specific order:
1. Sync code first: console.log("C") runs immediately → prints C.
2. Microtasks second: Promise.resolve().then(...) schedules a microtask. After all sync code finishes, the event loop drains ALL microtasks before moving to macrotasks → prints B.
3. Macrotasks last: setTimeout(..., 0) schedules a macrotask. Even with 0ms delay, it always goes into the macrotask queue → prints A.
Key rule: Microtasks (Promise callbacks, queueMicrotask, MutationObserver) always have higher priority than macrotasks (setTimeout, setInterval, I/O). The event loop completely drains the microtask queue before processing any macrotask.for (var i = 0; i < 3; i++) { setTimeout(() => console.log(i), 0); }
Medium
▼
3, 3, 3.
Explanation: var is function-scoped, not block-scoped. There's only ONE i variable shared across all iterations. The loop runs synchronously: i=0, i=1, i=2, then i=3 (loop exits). By the time the setTimeout callbacks execute (they're macrotasks), the loop has already completed and i is 3.
All three callbacks reference the same i variable, which is now 3.
Fix: Use let instead of var — let creates a new binding per iteration, so each callback captures a different value.for (let i = 0; i < 3; i++) { setTimeout(() => console.log(i), 0); }
Medium
▼
0, 1, 2.
Explanation: let is block-scoped. In a for loop, each iteration creates a new binding for i. So each callback's closure captures a different i: the first captures i=0, the second i=1, the third i=2.
This is fundamentally different from var (which shares one variable). This is why let is preferred in for loops — it gives you the behavior you'd expect.const arr = [1,2,3]; const arr2 = arr; arr2.push(4); console.log(arr);
Easy
▼
[1, 2, 3, 4].
Explanation: Arrays (and objects) in JavaScript are reference types. When you do const arr2 = arr, you're not creating a copy — you're making arr2 point to the same array in memory as arr. So when you push(4) to arr2, you're modifying the same array that arr references.
To create a true copy, you'd need:
• const arr2 = [...arr] (spread operator)
• const arr2 = arr.slice()
• const arr2 = JSON.parse(JSON.stringify(arr)) (deep copy)
This applies to objects too: const obj2 = obj creates a reference, not a copy.const x = {a:1}; const y = {a:1}; console.log(x === y);
Easy
▼
false.
Explanation: Objects in JavaScript are compared by reference (memory address), not by their content. Even though x and y have the exact same structure and values ({a:1}), they are two different objects stored at different memory locations. Therefore === compares their references and finds them different.
To compare by content, you'd need to compare individual properties or use JSON.stringify(x) === JSON.stringify(y) (not recommended for complex objects).
This is different from primitives (number, string, boolean) which are compared by value.const data = [1,2,3,4,5]; const result = data.filter(n => n%2===0).map(n => n*10); console.log(result);
Medium
▼
[20, 40].
Explanation: This chains two higher-order functions:
1. filter(n => n%2===0) keeps only even numbers → returns [2, 4].
2. map(n => n*10) multiplies each remaining element by 10 → returns [20, 40].
Both filter and map return new arrays — they don't mutate the original data array. This is a functional programming pattern where you chain methods to transform data in a readable, pipeline-like way.
Note: This creates two intermediate arrays. For very large datasets, you might use a single reduce for better performance..then()/.catch() for handling, supports chaining, avoids callback hell.
Async/await: syntactic sugar over Promises, async makes function return Promise, await pauses until Promise settles, makes async code look synchronous, easier error handling with try/catch..then() chaining.
Async/await makes it even cleaner with synchronous-looking code and try/catch error handling.Promise.then/catch/finally, queueMicrotask(), MutationObserver, process.nextTick (Node).
Macrotasks (lower priority): setTimeout, setInterval, setImmediate, I/O callbacks.
Execution order: after each macrotask, event loop drains ALL microtasks before next macrotask.
Rule: microtasks always run before macrotasks.process.nextTick — fastest, runs after current op, before other microtasks.
(2) Promise.then / other microtasks.
(3) setTimeout / setImmediate / macrotasks..catch() handler or try/catch block to handle it, it becomes an "unhandled promise rejection."
Behavior by environment:
• Browser: prints a warning in the console. In modern browsers, it may also trigger an unhandledrejection event on the window.
• Node.js v15+: crashes the process by default (like an uncaught exception), because unhandled rejections are considered serious bugs.
• Node.js v10–v14: just prints a deprecation warning (UnhandledPromiseRejectionWarning).
Best practices:
• Always use .catch() on promise chains.
• Always wrap await calls in try/catch.
• Use process.on('unhandledRejection', ...) as a safety net in Node.js.
• Never silently ignore rejected promises.new Promise:
util.promisify (Node.js).
(3) Use built-in promise versions (e.g., fs.promises).async before a function? What does it return?
Easy
▼
async before a function declaration does two things:
1. It allows you to use the await keyword inside the function body to pause execution until a Promise settles.
2. It makes the function always return a Promise.
If you return a non-Promise value (e.g., return 42), it's automatically wrapped in Promise.resolve(42). If the function throws an error, the returned Promise is rejected with that error.
try/catch blocks. When an async function throws an error (or an await encounters a rejected promise), it causes the returned promise to reject. Without try/catch, this leads to an unhandled promise rejection.
.catch() per individual await:
await fetch(url).catch(err => defaultOnError(err))
Best practice: always use try/catch in async functions. Consider wrapping async route handlers with an error-handling utility in Express (e.g., asyncHandler).console.log("1"); setTimeout(() => console.log("2"), 0); Promise.resolve().then(() => console.log("3")); console.log("4");
Hard
▼
1, 4, 3, 2.
Explanation:
1. console.log("1") — sync code, runs first → 1.
2. setTimeout(() => console.log("2"), 0) — schedules a macrotask (goes to the macrotask queue).
3. Promise.resolve().then(() => console.log("3")) — schedules a microtask.
4. console.log("4") — sync code → 4.
5. After all sync code finishes, the event loop drains microtasks → prints 3.
6. Then it processes the macrotask → prints 2.
Order summary: sync first (1, 4) → microtask (3) → macrotask (2).X, A, Y, C.
Explanation:
1. console.log("X") — sync, runs first → X.
2. test() is called. Inside, console.log("A") runs synchronously → A.
3. Then await is encountered. The function pauses and returns a pending promise. Execution goes back to the caller.
4. console.log("Y") — sync, runs next → Y.
5. After all sync code finishes and the awaited promise resolves, console.log("C") runs → C.
Key insight: await pauses the async function but doesn't block the main thread. It's like a yield point — the function suspends and the rest of the code continues executing.D, A, B, C.
Explanation:
1. console.log("D") — sync code runs first → D.
2. Promise.resolve().then(() => console.log("A")).then(() => console.log("B")) — creates a Promise chain. The first .then is a microtask. After sync code finishes, the microtask queue is drained:
- First .then runs → A.
- Its return value resolves the next promise in the chain, so the second .then also becomes a microtask → B.
3. setTimeout(() => console.log("C"), 0) — macrotask, runs after all microtasks → C.
Key insight: Chained .then() calls each create a new microtask. The event loop resolves the entire chain (A → B) before processing any macrotask (C).Done, then Caught: Oops (asynchronously).
Explanation:
1. f1() is called — it's an async function that immediately throws an error. This causes the returned promise to be rejected.
2. .catch(handler) is attached to the rejected promise, but the catch handler doesn't run synchronously.
3. console.log("Done") runs immediately as sync code → Done.
4. After sync code finishes, the microtask queue is processed. The .catch() handler runs → prints Caught: Oops.
Key insight: Even though f1 throws "immediately", the error is caught asynchronously via the Promise chain. The .catch() callback is always scheduled as a microtask, never runs synchronously.C, A, B Err.
Explanation:
1. console.log("C") — sync code runs first → C.
2. Promise.reject("Err") creates a rejected promise. When .then() is chained to a rejected promise, the then's callback is skipped (because the promise was rejected, not resolved). However, the then still produces a new rejected promise.
3. The rejected promise from .then() becomes a microtask → A is logged (this is the .then()'s rejection handler, or if A is from a separate chain).
4. .catch(handler) catches the rejection → B Err.
Key insight: .then() on a rejected promise passes the rejection down the chain. .catch() is needed to handle it. This demonstrates how error handling flows through promise chains.require() syntax, synchronous loading, module.exports, no tree shaking.
ES Modules: import/export syntax, asynchronous compile-time loading, supports tree shaking, top-level await.
CommonJS uses .js default, ES modules use .mjs or "type":"module" in package.json.module.exports is the actual object that gets exported from a module. exports is a shorthand reference that initially points to the same object.
module.exports for default exports (replace the entire object).
• Use exports.name = ... for named exports (add properties).
• Never reassign exports directly (e.g., exports = { ... }) — this breaks the reference to module.exports and the exports won't work.
• When importing, the consumer gets whatever module.exports points to.
fs.readFile(), fs.writeFileSync(), fs.readdir()
• Async versions are preferred for servers; sync for scripts/CLI.
http: create HTTP servers and make HTTP requests.
• http.createServer(), http.get(), http.request()
• Foundation for frameworks like Express.
path: handle and manipulate file system paths cross-platform.
• path.join() — joins segments with proper separators (/ or \)
• path.resolve() — resolves to absolute path
• path.extname(), path.basename()
os: retrieve operating system information.
• os.cpus(), os.freemem(), os.totalmem()
• os.hostname(), os.platform()
Other important modules: events (EventEmitter), stream, crypto, url, querystring, child_process.fs.readFile (async version) is non-blocking — it starts the read operation and immediately moves on, executing the callback when done. Other requests continue to be processed while the file is being read.
fs.readFileSync is blocking — it halts the entire Node.js event loop until the file is fully read. No other code can execute during this time.
fs.createReadStream, stdin).
Writable (destination — fs.createWriteStream, stdout).
Duplex (both — net.Socket).
Transform (modifies data — zlib.createGzip).
Use .pipe() or .pipeline() for automatic backpressure handling.stream.pipe() (auto-handles), stream.pipeline() (safer), or manually: check write() return value (false when full), listen for 'drain' event.on(event, listener) / addListener(event, listener) — register a listener for an event (can be called multiple times).
• once(event, listener) — register a listener that fires only once, then automatically removed.
• emit(event, ...args) — fire an event, calling all registered listeners with the provided arguments.
• removeListener(event, listener) / off(event, listener) — remove a specific listener.
• removeAllListeners(event) — remove all listeners for an event.
process object, and many Node.js core modules. Most custom services extend EventEmitter for inter-component communication.process.nextTick: runs immediately after current operation, before any other async (even microtasks).
setImmediate: runs in "check" phase of next event loop iteration, after I/O callbacks (macrotask).
Priority: sync → nextTick → Promise.then → setImmediate.
Use nextTick for ASAP operations; setImmediate for after I/O.package.json is the manifest/configuration file for every Node.js project. It defines project metadata, scripts, and dependencies.
Important fields:
• name — the package name (used when publishing to npm).
• version — semantic version (major.minor.patch).
• scripts — custom commands you can run with npm run <name> (e.g., "start": "node server.js", "test": "jest").
• dependencies — packages needed in production (installed with npm install).
• devDependencies — packages needed only for development (installed with npm install --save-dev).
• main — entry point when the package is require()'d (e.g., "main": "index.js").
• type — "module" enables ES module syntax (import/export).
• engines — specifies compatible Node.js versions (e.g., "engines": { "node": ">=18" }).
You create it with npm init (interactive) or npm init -y (defaults).express (web framework), mongoose (MongoDB ODM), jsonwebtoken (auth).
devDependencies are packages needed only during development and testing. Examples: jest (testing), nodemon (auto-restart), eslint (linting), typescript (compiler).
node_modules size, install time, and potential security vulnerabilities. Heroku, Docker, and most CI/CD tools run npm install --production by default.package-lock.json records the exact versions of every package and sub-dependency in your project. While package.json might specify "express": "^4.18.0" (which allows any compatible version), package-lock.json pins it to exactly 4.18.2 (or whatever was installed).
Why it matters:
• Reproducibility: ensures every developer and CI/CD environment installs the exact same dependency versions.
• Prevents "works on my machine": without it, different people might get slightly different versions.
• Faster installs: npm can skip resolution and use the lock file directly.
Best practices:
• Always commit package-lock.json to version control.
• Never commit node_modules — it's regenerated from package.json + lock file.
• Delete node_modules and package-lock.json, then run npm install to fully refresh dependencies.process.env global object, which stores all environment variables as key-value pairs.
To load from a .env file:
process.env are always strings (e.g., process.env.PORT is "3000", not 3000). Use parseInt() or Number() for numbers.
• Set a fallback with || "default" in case the variable is undefined.
• Add .env to .gitignore — never commit secrets (API keys, database URLs, passwords).
• Create a .env.example file with placeholder values for team reference.
Common use cases: database connection strings, API keys, port numbers, environment mode (development/production).removeListener() or once() for one-time events.
• Closures holding large objects: a function that captures a large array/object in its scope keeps it alive even if it's no longer needed.
• Global variables: accidentally creating globals (e.g., forgetting var/let/const) — these are never garbage collected.
• Unclosed streams: not calling stream.destroy() or stream.close() keeps buffers in memory.
• Cache without eviction: growing in-memory caches (like Maps or objects) without limits or TTL.
• Timers not cleared: setInterval or setTimeout references that are never cleared with clearInterval/clearTimeout.
• Circular references: object A references B which references A (rare with modern V8's garbage collector, but can still cause issues in large graphs).
Detection tools:
• process.memoryUsage() — check heap size periodically.
• Chrome DevTools (via --inspect flag) — heap snapshots, allocation timeline.
• clinic.js — Node.js performance profiling tool.
• --max-old-space-size — increase heap to observe if it grows unbounded.fs.readFileSync blocks ALL other requests.
Fix: use async fs.promises.readFile or streams.
res.json, res.status), centralized error handling, huge ecosystem.(req, res, next) signature. Can execute code, modify req/res, end the cycle, or call next().
A route handler is middleware that ends the response (no next()).
Global middleware runs for all routes; route-specific runs only for that route.next() to continue. If it sends a response, the pipeline stops.
Global app.use() runs for ALL routes. Error middleware (4 params) must be defined LAST.req.body.
express.json() — parses JSON payloads with Content-Type: application/json. Used for APIs where the client sends JSON in the request body.
express.urlencoded() — parses form submissions with Content-Type: application/x-www-form-urlencoded. Used for HTML form submissions (the default for <form> elements).
req.body is undefined. This is the most common beginner mistake in Express — forgetting to add body parsing middleware.
The extended option in urlencoded() controls whether it uses the qs library (true, nested objects) or querystring (false, flat).app.use() registers middleware that runs for ALL HTTP methods (GET, POST, PUT, DELETE, etc.) at a given path (or all paths if no path is specified).
app.get() registers a handler that runs only for GET requests at the specified path. Similarly, app.post(), app.put(), app.delete() are method-specific.
app.use() for global middleware (logging, auth, CORS) and app.get/post/put/delete() for route handlers.app.js.
Location header.
• 204 No Content — success but no body (DELETE).
4xx — Client errors (your fault):
• 400 Bad Request — invalid input (missing fields, wrong format, validation error).
• 401 Unauthorized — not authenticated (missing or invalid token).
• 403 Forbidden — authenticated but not authorized (no permission for this resource).
• 404 Not Found — resource doesn't exist or wrong URL.
• 409 Conflict — conflict with current state (e.g., duplicate email).
• 422 Unprocessable Entity — valid syntax but semantically incorrect.
5xx — Server errors (our fault):
• 500 Internal Server Error — generic server crash/error.
• 502 Bad Gateway — upstream server returned invalid response.
• 503 Service Unavailable — server is overloaded or under maintenance.
Memory trick: 4xx = client problem, 5xx = server problem.try/catch or an asyncHandler wrapper. Async throws won't auto-trigger Express error handler.
(err, req, res, next).cors package: app.use(cors()).
For specific origin: app.use(cors({ origin: "http://localhost:3000" })).jwt.sign(), verify with jwt.verify().Authorization header → extract token → verify with jwt.verify() → attach decoded user to req.user → call next().
For roles: create a roleCheck middleware checking req.user.role.res.send() — the most versatile method. Auto-detects the content type based on the data type (string → text/html, object → application/json, Buffer → application/octet-stream). Sets Content-Type header automatically. Can send strings, Buffers, arrays, or objects.
res.json() — specifically for JSON responses. Always sets Content-Type: application/json. Converts the data to JSON string using JSON.stringify(). Use this when sending structured data to APIs.
res.sendFile() — sends a file from the server's filesystem. Automatically sets appropriate headers (Content-Type based on file extension, Content-Length, caching headers). Use path.join(__dirname, 'file.html') for the path.
:paramName. Accessed via req.params.paramName.
• Example: GET /users/42 with route /users/:id → req.params.id = "42" (always a string).
• Use for identifying specific resources (IDs, slugs).
Query params: key-value pairs after ? in the URL. Accessed via req.query.keyName.
• Example: GET /users?role=admin&sort=name → req.query.role = "admin", req.query.sort = "name".
• Use for filtering, sorting, pagination, search.
• All values are always strings — use parseInt() for numbers.
Request body: the data sent in the request body (POST, PUT, PATCH). Accessed via req.body.
• Requires body-parsing middleware: app.use(express.json()).
• Use for creating/updating resources.
dotenv is a package that loads environment variables from a .env file into process.env. This keeps sensitive configuration (API keys, database URLs, secrets) out of your source code.
Setup:
.env to .gitignore — never commit secrets.
• Create .env.example with placeholder values (no real secrets) for team reference.
• In production, use platform-specific env management (Heroku config vars, Docker secrets, AWS SSM).
• dotenv doesn't override existing env variables — if PORT is already set, .env won't replace it.fetchData() throws, Express won't catch it.
Fix: wrap in try/catch or asyncHandler.
NULL.
Characteristics:
• Must be unique — no duplicates allowed.
• Must be not null — every row must have a value.
• Only one primary key per table (though it can be a composite key — multiple columns combined).
Types:
• Natural key: real-world data (e.g., email, SSN) — risky if the data changes.
• Surrogate key: auto-generated (e.g., auto-incrementing integer, UUID) — recommended for most cases.
WHERE clauses frequently.
• Columns used in JOIN conditions.
• Columns used in ORDER BY or GROUP BY.
• Columns with high cardinality (many unique values).
NULLs if no match).
_id field (auto-generated if not provided).
• Documents in the same collection can have different fields (dynamic schema).
• Values can be strings, numbers, arrays, nested objects, dates, ObjectIds, etc.
• Maximum document size is 16 MB.
_id field (ObjectId) if you don't provide one.
• You can provide your own _id (string, number, etc.).
• Returns an InsertOneResult / InsertManyResult with acknowledged: true and insertedIds.
• In Mongoose (ODM): User.create({ name: "Alice" }) or new User({...}).save().session.beginTransaction() with Mongoose, or Prisma's $transaction().populate() (Mongoose) or eager loading (Sequelize/Prisma).
• Use JOINs in SQL.
• Use batch queries: WHERE userId IN (id1, id2, ...).
Impact: with 10,000 users, you'd make 10,001 queries instead of 1-2. This is a major performance killer.[["1",1], ["2",2], ["3",3]].
3. Sort by count (descending).
4. Return the first entry's key (element with highest count).
Time complexity: O(n log n) due to sorting. Could be O(n) with a single-pass max-tracking approach.
Alternative (Map-based, handles all types):
Map.reduce to accumulate items into groups by the specified key.
{} as the accumulator.
2. For each item, get its group key (e.g., age = 20).
3. If that group doesn't exist yet, create an empty array for it.
4. Push the current item into the group.
5. Return the accumulator.
Time: O(n) — single pass. Useful for aggregating data in analytics, reports, and data processing.flat() is ES2019+.n % 2 === 0 checks if a number is even (divisible by 2 with no remainder).
• reduce accumulates the sum, starting from 0.
• Even numbers are added to the accumulator; odd numbers are skipped.
Time: O(n). Single-pass reduce is more memory-efficient (no intermediate array).complement = target - current.
2. Check if the complement already exists in our map.
3. If yes → we found the pair! Return both indices.
4. If no → store the current element and its index, move on.
Example: twoSum([2,7,11,15], 9)
• i=0: complement = 9-2 = 7. Map empty. Store {2:0}.
• i=1: complement = 9-7 = 2. Map has 2! Return [0, 1].
Time: O(n) single pass. Space: O(n) for the map.resolve(value) — call when the async operation succeeds.
• reject(error) — call when it fails.
Promises have three states:
1. Pending — initial state, neither resolved nor rejected.
2. Fulfilled — operation completed successfully (resolve called).
3. Rejected — operation failed (reject called).
Consuming Promises:
• .then(fn) — runs when resolved.
• .catch(fn) — runs when rejected.
• .finally(fn) — runs regardless of outcome.
Key benefit: avoids callback hell, enables chaining, and provides built-in error propagation.