TCS AI Career — Exam Prep Guide

110 Questions · Click any card to reveal the answer · Track your progress below

§1 JavaScript Fundamentals Q1–Q10
Q1 Explain the difference between var, let, and const with examples. Easy
var is function-scoped, hoisted and initialized to 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 x = 1; var x = 2; // OK let a = 1; let a = 2; // SyntaxError! const c = 1; c = 2; // TypeError! const d = [1,2]; d.push(3); // OK — mutating the array // var leaks outside blocks: var y = 10 inside block; console.log(y); // 10 // let does NOT leak: let b = 20 inside block; console.log(b); // ReferenceError
Key insight: var is function-scoped (leaks out of blocks), let and const are block-scoped. Always use const by default.
Q2 What is hoisting in JavaScript? How does it differ for var, let, and const? Medium
Hoisting is JavaScript's behavior of moving declarations to the top of their scope during compilation. var declarations are hoisted and initialized to 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.
console.log(a); // undefined (hoisted + initialized) var a = 10; console.log(b); // ReferenceError (TDZ) let b = 20; greet(); // Works! declaration fully hoisted function greet() { console.log("Hi"); }
Q3 What is a closure? Give one practical use case. Medium
A closure is a function that "remembers" the variables from its outer scope even after the outer function has finished executing. The inner function has access to the outer function's variables via the scope chain.
function createCounter() { let count = 0; return { increment: () => ++count, getCount: () => count }; } const counter = createCounter(); counter.increment(); counter.increment(); console.log(counter.getCount()); // 2
Practical use cases: Data privacy/encapsulation, event handlers/callbacks, partial application/currying, memoization.
Q4 What is the difference between == and ===? Easy
== (abstract equality) compares values after type coercion — JS converts operands to the same type. === (strict equality) compares both value and type — no coercion. 5 == "5"true (string coerced to number) 5 === "5"false (different types) null == undefinedtrue null === undefinedfalse Always use === to avoid subtle bugs.
Q5 What is the difference between null and undefined? Easy
undefined means "a variable has been declared but has no value assigned yet" — it's the default for uninitialized variables, missing function arguments, and missing object properties. null means "intentionally no value" — you assign it explicitly to indicate emptiness or absence of an object. Key differences: • 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.
let a; console.log(a); // undefined — declared but not assigned console.log(typeof a); // "undefined" let b = null; console.log(b); // null console.log(typeof b); // "object" (historical bug) // Missing property: const obj = { name: "Alice" }; console.log(obj.age); // undefined (property doesn't exist) // Missing function argument: function test(x) { console.log(x); } test(); // undefined (no argument passed)
Q6 Explain this in: a regular function, an arrow function, and a method. Medium
Regular function: 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.
const obj = { name: "TCS", getName: function() { console.log(this.name); }, getNameArrow: () => { console.log(this.name); } }; obj.getName(); // "TCS" — this = obj obj.getNameArrow(); // undefined — this = global
Q7 What is the difference between an arrow function and a normal function? List at least 3 differences. Medium
1. this binding: Arrow functions don't have their own 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.
Q8 What is event bubbling and event capturing in the browser? Medium
When an event occurs on a DOM element, it travels through the DOM in three phases: (1) Capturing phase (top-down): window → document → html → body → ... → parent → target. The event travels from the root down to the target element. (2) Target phase: the event reaches the target element itself. (3) Bubbling phase (bottom-up): target → parent → ... → body → html → document → window. The event bubbles back up. 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).
<div id="parent"><button id="child">Click</button></div> parent.addEventListener("click", () => console.log("parent")); child.addEventListener("click", () => console.log("child")); // Click child → logs: "child", "parent" (bubbling)
Practical use: Event delegation — attach one listener to a parent instead of many to children (e.g., handling clicks on a dynamic list).
Q9 What is the difference between call, apply, and bind? Give one example each. Medium
All control what 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.
function greet(greeting, punctuation) { console.log(`${greeting}, ${this.name}${punctuation}`); } const user = { name: "Alice" }; greet.call(user, "Hello", "!"); // "Hello, Alice!" greet.apply(user, ["Hi", "..."]); // "Hi, Alice..." const bound = greet.bind(user, "Hey"); bound("?!"); // "Hey, Alice?!"
Q10 What are higher-order functions? Give 3 examples from Array methods. Easy
A higher-order function is a function that either takes a function as an argument or returns a function. Examples: map (transforms each element), filter (keeps elements that pass a test), reduce (accumulates into a single value). Also: forEach, find, sort, every, some.
const nums = [1, 2, 3, 4, 5]; const doubled = nums.map(n => n * 2); // [2, 4, 6, 8, 10] const evens = nums.filter(n => n % 2 === 0); // [2, 4] const sum = nums.reduce((acc, n) => acc + n, 0); // 15
§1B JS — Predict the Output Q11–Q20
Q11 console.log(a); var a = 10; console.log(a); Easy
Output: 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).
Q12 console.log(b); let b = 20; Easy
Output: 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.
Q13 Closure counter: function outer() { let count = 0; return function inner() { count++; console.log(count); } } const fn = outer(); fn(); fn(); fn(); Medium
Output: 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.
Q14 obj.getName() and obj.getNameArrow() with name: "TCS" Medium
Output: 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.
Q15 setTimeout(() => console.log("A"), 0); Promise.resolve().then(() => console.log("B")); console.log("C"); Hard
Output: 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.
Q16 for (var i = 0; i < 3; i++) { setTimeout(() => console.log(i), 0); } Medium
Output: 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 varlet creates a new binding per iteration, so each callback captures a different value.
Q17 for (let i = 0; i < 3; i++) { setTimeout(() => console.log(i), 0); } Medium
Output: 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.
Q18 const arr = [1,2,3]; const arr2 = arr; arr2.push(4); console.log(arr); Easy
Output: [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.
Q19 const x = {a:1}; const y = {a:1}; console.log(x === y); Easy
Output: 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.
Q20 const data = [1,2,3,4,5]; const result = data.filter(n => n%2===0).map(n => n*10); console.log(result); Medium
Output: [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.
§2 Promises, Async/Await & Event Loop Q21–Q30
Q21 What is the event loop in Node.js? Medium
The event loop allows Node.js to perform non-blocking I/O despite being single-threaded. Call Stack executes JS code. Web/Node APIs handle async operations (via libuv). Callback Queues hold completed callbacks. Event Loop checks if call stack is empty, then picks callbacks from queues. Order: sync → microtasks → macrotasks. In simple terms: it's a while-loop that continuously checks for and executes pending callbacks.
Q22 Difference between callbacks, promises, and async/await. Medium
Callbacks: pass a function as argument, nesting leads to callback hell, no built-in error handling. Promises: object representing future value, .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.
Q23 What is callback hell? How do promises or async/await help? Easy
Callback hell is deeply nested callbacks creating a "pyramid of doom" — hard to read, debug, and maintain. Promises flatten it with .then() chaining. Async/await makes it even cleaner with synchronous-looking code and try/catch error handling.
Q24 What is the difference between microtasks and macrotasks? Hard
Microtasks (higher priority): 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.
Q25 What is the order of execution among setTimeout, Promise.then, and process.nextTick? Hard
In Node.js: (1) process.nextTick — fastest, runs after current op, before other microtasks. (2) Promise.then / other microtasks. (3) setTimeout / setImmediate / macrotasks.
Q26 Explain Promise.all, Promise.allSettled, Promise.race, and Promise.any. Medium
Promise.all: resolves when ALL resolve, rejects if ANY rejects. Promise.allSettled: resolves when ALL settle (never rejects). Returns status + value for each. Promise.race: settles with the FIRST one to settle (resolve or reject). Promise.any: resolves with the FIRST to resolve, rejects only if ALL reject.
Q27 What happens if you don't handle a rejected promise? Easy
Unhandled promise rejection. When a Promise rejects and there's no .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.
Q28 How do you convert a callback-based function to a promise-based one? Medium
(1) Wrap in new Promise:
return new Promise((resolve, reject) => { callback((err, data) => { if (err) reject(err); else resolve(data); }); });
(2) Use util.promisify (Node.js). (3) Use built-in promise versions (e.g., fs.promises).
Q29 What is the purpose of 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.
async function getData() { return 42; // automatically becomes Promise.resolve(42) } getData().then(v => console.log(v)); // 42 async function fail() { throw new Error("Oops"); // returns rejected Promise } fail().catch(e => console.log(e.message)); // "Oops"
Q30 How do you handle errors in async/await code? Easy
Use 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.
async function fetchData() { try { const res = await fetch("/api/data"); const data = await res.json(); return data; } catch (err) { console.error("Failed:", err.message); return null; } }
Alternative approach: use .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).
§2B Async — Predict the Output Q31–Q35
Q31 console.log("1"); setTimeout(() => console.log("2"), 0); Promise.resolve().then(() => console.log("3")); console.log("4"); Hard
Output: 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).
Q32 async function test() with console.log("A"), await, console.log("C"). Called after console.log("X") and before console.log("Y"). Hard
Output: 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.
Q33 Promise chain with .then("A").then("B"), setTimeout("C", 0), console.log("D"). Hard
Output: 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).
Q34 async function f1() { throw new Error("Oops"); } f1().catch(...); console.log("Done"); Hard
Output: 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.
Q35 Promise chain returning Promise.reject("Err"), with .catch("B"), console.log("C"). Hard
Output: 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.
§3 Node.js Core Q36–Q50
Q36 Why is Node.js "single-threaded" but handles many concurrent requests? Medium
JS code runs on a single thread (call stack). But libuv offloads I/O to a thread pool (4 threads) or OS event notification. Event loop picks up callbacks when async ops complete. Non-blocking I/O doesn't block the main thread. Analogy: one chef delegating to assistants, checking back when done.
Q37 What is the role of libuv in Node.js? Medium
libuv is a cross-platform C library that provides async I/O. • Implements the event loop (timers, poll, check phases). • Thread pool (~4 threads) for file system, DNS. • Async networking (epoll/kqueue/IOCP). • Child processes, pipes, signal handling. It's the engine behind Node.js's non-blocking I/O.
Q38 Difference between CommonJS and ES modules. Medium
CommonJS: 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.
Q39 What is the purpose of module.exports vs exports? Easy
module.exports is the actual object that gets exported from a module. exports is a shorthand reference that initially points to the same object.
// These are equivalent for named exports: exports.name = "Alice"; exports.age = 25; // Same as: module.exports = { name: "Alice", age: 25 };
Key rules: • Use 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.
// ✅ Correct: exports.greet = () => "Hi"; // ❌ Wrong — breaks the link: exports = { greet: () => "Hi" }; // ✅ OK — replaces the whole export: module.exports = { greet: () => "Hi" };
Q40 Main modules: fs, http, path, os. Easy
Core Node.js built-in modules: fs (File System): read, write, delete, rename files and directories. Provides both sync and async APIs. • 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.
Q41 fs.readFile vs fs.readFileSync? When to avoid sync? Easy
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.
// ✅ Async (preferred for servers): fs.readFile("data.txt", "utf-8", (err, data) => { if (err) throw err; console.log(data); }); console.log("This runs immediately!"); // ❌ Sync (blocks everything): const data = fs.readFileSync("data.txt", "utf-8"); console.log(data); console.log("This only runs after file is read");
When to avoid sync: In web servers and APIs — blocking the event loop means ALL concurrent requests are stalled. A single slow file read could make your entire server unresponsive. When sync is fine: CLI tools, build scripts, startup initialization where nothing else needs to run.
Q42 What is a stream? What are the types? Medium
A stream handles data piece by piece (not all at once). Four types: Readable (source — 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.
Q43 What is backpressure in streams? Hard
Backpressure occurs when readable produces data faster than writable can consume. Buffer fills up. Fix: use stream.pipe() (auto-handles), stream.pipeline() (safer), or manually: check write() return value (false when full), listen for 'drain' event.
Q44 What is EventEmitter? Easy
EventEmitter is a core Node.js class that implements the publish/subscribe (pub/sub) pattern. It allows objects to emit named events and register listeners that respond to those events. Key methods: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.
const { EventEmitter } = require("events"); const emitter = new EventEmitter(); emitter.on("data", (msg) => console.log("Received:", msg)); emitter.emit("data", "Hello!"); // "Received: Hello!"
Extends: streams, HTTP servers, process object, and many Node.js core modules. Most custom services extend EventEmitter for inter-component communication.
Q45 process.nextTick vs setImmediate? Hard
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.
Q46 Purpose of package.json? Name 4 important fields. Easy
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).
Q47 dependencies vs devDependencies? Easy
dependencies are packages your application needs to run in production. Examples: 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).
# Install production dependency: npm install express # Install dev dependency: npm install --save-dev jest # Production install (skips devDependencies): npm install --production
Why the distinction? In production, you don't need testing frameworks or dev tools. Excluding them reduces node_modules size, install time, and potential security vulnerabilities. Heroku, Docker, and most CI/CD tools run npm install --production by default.
Q48 What is package-lock.json? Easy
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.
Q49 How to read environment variables? Easy
Use the process.env global object, which stores all environment variables as key-value pairs. To load from a .env file:
const dotenv = require("dotenv"); dotenv.config(); // reads .env file, adds to process.env const dbUrl = process.env.DATABASE_URL; const port = process.env.PORT || 3000;
Important notes: • All values from 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).
Q50 Common memory leaks in Node.js? Hard
Common memory leaks in Node.js: • Unclosed event listeners: adding listeners in a loop without removing them. Each listener keeps a reference to its callback, preventing garbage collection. Fix: use 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.
§3B Node.js Coding Q51–Q55
Q51 Write an HTTP server returning "Hello TCS" for / and 404 for others. Medium
const http = require("http"); const server = http.createServer((req, res) => { if (req.url === "/" && req.method === "GET") { res.writeHead(200, { "Content-Type": "text/plain" }); res.end("Hello TCS"); } else { res.writeHead(404, { "Content-Type": "text/plain" }); res.end("404 Not Found"); } }); server.listen(3000, () => console.log("Server running on port 3000"));
Q52 Read data.txt asynchronously with error handling. Medium
const fs = require("fs").promises; async function readFile() { try { const data = await fs.readFile("data.txt", "utf-8"); console.log(data); } catch (err) { console.error("Error:", err.message); } } readFile();
Q53 EventEmitter emitting dataReady with {id: 1}. Medium
const { EventEmitter } = require("events"); const emitter = new EventEmitter(); emitter.on("dataReady", (payload) => console.log("Data ready:", payload) ); emitter.emit("dataReady", { id: 1 });
Q54 Fix the synchronous file read in an Express route. Medium
Problem: fs.readFileSync blocks ALL other requests. Fix: use async fs.promises.readFile or streams.
app.get("/file", async (req, res) => { try { const content = await fs.promises.readFile("data.txt", "utf-8"); res.send(content); } catch (err) { res.status(500).send("Error reading file"); } });
Q55 Fetch multiple URLs with Promise.all. Medium
async function fetchAll(urls) { const promises = urls.map(url => fetch(url).then(res => { if (!res.ok) throw new Error(`${url} failed`); return res.json(); }) ); return Promise.all(promises); } const [users, posts] = await fetchAll(["/api/users", "/api/posts"]);
§4 Express & REST Q56–Q70
Q56 What is Express.js? Why use it over the http module? Easy
Minimal, flexible Node.js web framework. Over raw http: built-in routing (params, query, methods), middleware pipeline (body parsing, auth, logging), built-in helpers (res.json, res.status), centralized error handling, huge ecosystem.
Q57 What is middleware in Express? Medium
A function with (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.
Q58 What is the order of execution of multiple middlewares? Medium
Executes in definition order (top to bottom). Each must call 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.
Q59 express.json() and express.urlencoded()? Easy
Built-in middleware that parse request bodies so you can access data from 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).
const express = require("express"); const app = express(); app.use(express.json()); app.use(express.urlencoded({ extended: true })); app.post("/data", (req, res) => { console.log(req.body); // { name: "Alice" } — parsed body! res.json({ received: req.body }); });
Without these middleware: 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).
Q60 app.use() vs app.get()? Easy
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.
// Runs for ALL methods on /api/users: app.use("/api/users", (req, res, next) => { console.log("Request received"); next(); }); // Only runs for GET /api/users: app.get("/api/users", (req, res) => { res.json(users); }); // Only runs for POST /api/users: app.post("/api/users", (req, res) => { // create user... });
Typical pattern: use app.use() for global middleware (logging, auth, CORS) and app.get/post/put/delete() for route handlers.
Q61 What is a router in Express? Easy
A Router in Express is a mini Express application that handles routes for a specific resource or feature. It lets you organize routes into separate files/modules instead of putting everything in one app.js.
// routes/users.js const router = express.Router(); router.get("/", (req, res) => { /* list users */ }); router.get("/:id", (req, res) => { /* get user */ }); router.post("/", (req, res) => { /* create user */ }); module.exports = router; // app.js const userRoutes = require("./routes/users"); app.use("/users", userRoutes); // Now: GET /users, POST /users, GET /users/:id, etc.
Benefits:Separation of concerns: each resource has its own file. • Avoids massive app.js: routes are modular and maintainable. • Own middleware: routers can have middleware that only applies to their routes.
Q62 PUT vs PATCH? Easy
PUT replaces the ENTIRE resource with the data sent in the request. You must send the complete object — any fields not included are effectively removed/overwritten. PATCH partially updates a resource — you only send the fields you want to change. Fields not included remain unchanged.
// PUT /users/1 — replaces the entire user: { "name": "Alice", "email": "alice@new.com" } // → user becomes exactly { name: "Alice", email: "alice@new.com" } // PATCH /users/1 — partial update: { "email": "alice@new.com" } // → only email changes, name stays the same
Key differences: • PUT is idempotent (doing it multiple times has the same effect as once). PATCH is not necessarily idempotent. • Use PUT for full replacements, PATCH for partial updates.
Q63 Common HTTP status codes. Easy
Key HTTP status codes every developer should know: 2xx — Success:200 OK — request succeeded (GET, PUT, PATCH, DELETE). • 201 Created — resource created (POST). Should include a 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.
Q64 How to handle errors in async Express handlers? Medium
Use try/catch or an asyncHandler wrapper. Async throws won't auto-trigger Express error handler.
const asyncHandler = (fn) => (req, res, next) => Promise.resolve(fn(req, res, next)).catch(next); app.get("/data", asyncHandler(async (req, res) => { const data = await fetchData(); res.json(data); }));
Error middleware must have 4 params: (err, req, res, next).
Q65 What is CORS? How do you enable it in Express? Easy
CORS (Cross-Origin Resource Sharing) — browser security restricting cross-origin requests. Use the cors package: app.use(cors()). For specific origin: app.use(cors({ origin: "http://localhost:3000" })).
Q66 What is JWT? Medium
JSON Web Token — compact, URL-safe token for stateless auth. Has header (alg, type), payload (claims), and signature. Used for: API auth, session management, microservice auth, SSO. Sign with jwt.sign(), verify with jwt.verify().
Q67 How to secure routes with auth middleware? Medium
Check 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.
Q68 res.send() vs res.json() vs res.sendFile()? Easy
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.
// res.send — auto-detects type: res.send("Hello"); // text/html res.send({ name: "Alice" }); // application/json // res.json — always JSON: res.json({ name: "Alice" }); // Content-Type: application/json // res.sendFile — serves a file: res.sendFile(path.join(__dirname, "index.html"));
Q69 How to access route params, query params, and request body? Easy
Route params: dynamic segments in the URL path, defined with :paramName. Accessed via req.params.paramName. • Example: GET /users/42 with route /users/:idreq.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=namereq.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.
// GET /users/42?role=admin app.get("/users/:id", (req, res) => { console.log(req.params.id); // "42" console.log(req.query.role); // "admin" }); // POST /users with JSON body app.post("/users", (req, res) => { console.log(req.body.name); // "Alice" });
Q70 What is the purpose of dotenv? Easy
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:
// 1. Install: npm install dotenv // 2. Create .env file: PORT=3000 DATABASE_URL=mongodb://localhost/mydb API_KEY=secret123 // 3. Load at the top of your app: require("dotenv").config(); const port = process.env.PORT;
Best practices: • Add .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.
§4B Express Coding Q71–Q75
Q71 Minimal Express app with GET /health. Medium
const express = require("express"); const app = express(); app.get("/health", (req, res) => res.json({ status: "ok" }) ); app.listen(4000, () => console.log("Running on port 4000") );
Q72 Write a logger middleware. Medium
function logger(req, res, next) { console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`); next(); } app.use(logger);
Q73 Write a POST /users route with validation. Medium
app.use(express.json()); app.post("/users", (req, res) => { const { name, email } = req.body; if (!name || !email) return res.status(400).json({ error: "Name and email required" }); const user = { id: users.length + 1, name, email }; users.push(user); res.status(201).json(user); });
Q74 Write an error-handling middleware. Medium
app.use((err, req, res, next) => { console.error(err.stack); res.status(err.status || 500).json({ error: err.message || "Internal Server Error" }); });
Q75 Fix an async route without error handling. Medium
Problem: If fetchData() throws, Express won't catch it. Fix: wrap in try/catch or asyncHandler.
// ❌ Bad: app.get("/data", async (req, res) => { const data = await fetchData(); // may throw — NOT caught! res.json(data); }); // ✅ Good: app.get("/data", async (req, res) => { try { const data = await fetchData(); res.json(data); } catch (err) { res.status(500).json({ error: err.message }); } });
§5 Database Basics Q76–Q85
Q76 SQL vs NoSQL? Easy
SQL (Relational Databases) — MySQL, PostgreSQL, SQLite: • Data stored in structured tables with rows and columns (like spreadsheets). • Fixed schema — you define columns and data types upfront. • Uses SQL (Structured Query Language) for queries. • Supports ACID transactions (Atomicity, Consistency, Isolation, Durability). • Can JOIN data from multiple tables. • Better for complex queries, reporting, and data with relationships. NoSQL (Non-Relational Databases) — MongoDB, Redis, Cassandra: • Data stored as flexible documents, key-value pairs, or column families. • Dynamic schema — each document can have different fields. • No standard query language (each DB has its own API). • Eventual consistency (eventually all nodes see the same data). • Horizontal scaling — distribute across multiple servers (sharding). • Better for unstructured data, rapid prototyping, and massive scale. When to use which: • SQL: banking, inventory, any data with strict relationships and integrity requirements. • NoSQL: social media feeds, IoT data, real-time analytics, content management.
Q77 What is a primary key? Easy
A primary key is a column (or set of columns) that uniquely identifies each row in a table. No two rows can have the same primary key value, and it can never be 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.
CREATE TABLE users ( id INT PRIMARY KEY AUTO_INCREMENT, email VARCHAR(255) UNIQUE NOT NULL, name VARCHAR(100) ); // Composite primary key: CREATE TABLE order_items ( order_id INT, product_id INT, quantity INT, PRIMARY KEY (order_id, product_id) );
Q78 What is an index in a database? Easy
An index is a data structure (usually a B-tree or hash) that speeds up data retrieval by allowing the database to find rows without scanning every row in a table. Analogy: It's like the index at the back of a textbook — instead of reading every page to find a topic, you look it up in the index and jump directly to the right page. How it works: Without an index, the database performs a full table scan (reads every row). With an index, it does a lookup — much faster for large tables. Trade-off:Faster reads (SELECT, WHERE, JOIN, ORDER BY). • Slower writes (INSERT, UPDATE, DELETE) — the index must also be updated. • More storage — indexes consume disk space. When to create indexes: • Columns used in WHERE clauses frequently. • Columns used in JOIN conditions. • Columns used in ORDER BY or GROUP BY. • Columns with high cardinality (many unique values).
// Create an index: CREATE INDEX idx_email ON users(email); // Composite index: CREATE INDEX idx_name_age ON users(name, age);
Don't over-index — too many indexes slow down writes significantly.
Q79 What is a JOIN? INNER JOIN vs LEFT JOIN? Medium
JOIN combines rows from two tables based on a related column. INNER JOIN: returns only matching rows in BOTH tables. LEFT JOIN: returns ALL rows from left table + matching rows from right (NULLs if no match).
SELECT * FROM orders INNER JOIN users ON orders.user_id = users.id; -- only orders with matching users SELECT * FROM orders LEFT JOIN users ON orders.user_id = users.id; -- ALL orders, even without a user
Q80 What is a document in MongoDB? Easy
A document in MongoDB is a JSON-like data structure (stored as BSON — Binary JSON) that contains key-value pairs. It's the basic unit of data, equivalent to a row in SQL. Key characteristics: • Each document has a unique _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.
// Example document in a "users" collection: { "_id": ObjectId("507f1f77bcf86cd799439011"), "name": "Alice", "age": 25, "email": "alice@example.com", "hobbies": ["reading", "coding"], "address": { "city": "Mumbai", "pin": 400001 } }
SQL comparison: Document = Row, Collection = Table, Field = Column, Embedded document = JOIN equivalent.
Q81 How do you insert a document in MongoDB? Easy
In MongoDB, you insert documents using: insertOne: inserts a single document.
db.users.insertOne({ name: "Alice", age: 25, email: "alice@example.com" });
insertMany: inserts multiple documents at once (more efficient than multiple insertOne calls).
db.users.insertMany([ { name: "Bob", age: 30 }, { name: "Charlie", age: 35 } ]);
Key points: • MongoDB auto-generates an _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().
Q82 What is an ORM/ODM? Easy
ORM (Object-Relational Mapper) for SQL databases: maps JavaScript/TypeScript objects to database tables and rows. You interact with objects instead of writing raw SQL. • Examples: Sequelize, TypeORM, Prisma, Knex.js. ODM (Object-Document Mapper) for NoSQL/document databases: maps JavaScript/TypeScript objects to documents in collections. • Example: Mongoose (MongoDB).
// Without ORM (raw SQL): const result = await db.query("SELECT * FROM users WHERE age > ?", [18]); // With ORM (Prisma example): const users = await prisma.user.findMany({ where: { age: { gt: 18 } } });
Benefits: type safety, reduced boilerplate, automatic migrations, easier to switch databases. Drawbacks: abstraction overhead, complex queries can be harder to optimize, learning curve. Most production apps use an ORM/ODM for CRUD operations and drop to raw queries for complex/optimized operations.
Q83 What is a connection pool? Easy
A connection pool is a cache of pre-established database connections that are reused instead of creating a new connection for every request and closing it afterward. Why it matters: • Creating a new DB connection is expensive (TCP handshake, authentication, SSL setup). • A pool keeps N connections open and hands them out as needed. • When a connection is returned, it goes back to the pool for reuse. • Prevents exhausting the database's connection limit. How it works: 1. App starts → pool creates initial connections (e.g., 5). 2. Request comes in → pool gives an available connection. 3. Request finishes → connection is returned to the pool. 4. If all connections are busy → new ones are created (up to max limit). 5. Idle connections are closed after a timeout.
// MySQL with mysql2: const pool = mysql.createPool({ host: "localhost", user: "root", database: "mydb", connectionLimit: 10 // max connections in pool }); // Usage — connection is automatically managed: pool.query("SELECT * FROM users", (err, results) => { ... });
ORMs like Prisma and Mongoose manage connection pooling automatically.
Q84 What is a transaction? Easy
A transaction is a group of database operations that are treated as a single unit — ALL succeed or ALL fail. If any operation fails, all previous operations in the transaction are rolled back to their original state. Why it matters: Ensures data consistency when multiple related operations depend on each other. Without transactions, partial failures leave the database in an inconsistent state. Classic example — bank transfer:
// Transfer ₹500 from Account A to Account B BEGIN TRANSACTION; UPDATE accounts SET balance = balance - 500 WHERE id = 1; UPDATE accounts SET balance = balance + 500 WHERE id = 2; COMMIT; -- If either update fails → ROLLBACK (no money lost/created)
ACID properties:Atomicity — all or nothing. • Consistency — valid state before and after. • Isolation — concurrent transactions don't interfere. • Durability — committed changes persist even on crash. In Node.js: use session.beginTransaction() with Mongoose, or Prisma's $transaction().
Q85 What is the N+1 query problem? Medium
The N+1 query problem occurs when your code makes 1 query to fetch a list of N items, then makes N additional individual queries to fetch related data for each item — resulting in N+1 total queries. Example:
// ❌ N+1 problem: const users = await User.find(); // 1 query — fetches 100 users for (const user of users) { user.posts = await Post.find({ userId: user.id }); // 100 more queries — one per user! } // Total: 101 queries! // ✅ Fixed with eager loading: const users = await User.find().populate("posts"); // Total: 1 or 2 queries (depending on DB) // ✅ Fixed with JOIN (SQL): SELECT users.*, posts.* FROM users LEFT JOIN posts ON users.id = posts.user_id;
Fixes: • Use 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.
§6 Coding Problems Q86–Q95
Q86 Reverse a string. Easy
Approach: Split the string into an array of characters, reverse the array, then join it back.
const reverse = s => s.split("").reverse().join(""); // "hello" → ["h","e","l","l","o"] → ["o","l","l","e","h"] → "olleh" // Alternative using spread operator: [...s].reverse().join("") // Using reduce (building reversed string): s.split("").reduce((rev, c) => c + rev, "") // Two-pointer approach (most efficient, O(n) time, O(1) space): function reverseStr(s) { const arr = s.split(""); let left = 0, right = arr.length - 1; while (left < right) { [arr[left], arr[right]] = [arr[right], arr[left]]; left++; right--; } return arr.join(""); }
Time complexity: O(n) for all approaches. Space: O(n) (strings are immutable in JS).
Q87 Check if a string is a palindrome. Easy
Approach 1 — Simple (reverse and compare):
const isPalindrome = s => s === s.split("").reverse().join(""); // "racecar" → "racecar" === "racecar" → true
Approach 2 — Two pointers (more efficient):
function isPal(s) { let i = 0, j = s.length - 1; while (i < j) { if (s[i++] !== s[j--]) return false; } return true; }
Approach 3 — Case-insensitive with special chars removed:
const isPalindrome = s => { const clean = s.toLowerCase().replace(/[^a-z0-9]/g, ""); return clean === clean.split("").reverse().join(""); }; // "A man, a plan, a canal: Panama" → true
Time complexity: O(n) for all approaches. Two pointers is O(1) extra space (ignoring string immutability).
Q88 Find the most frequent element in an array. Medium
Approach: Count frequencies using an object/map, then find the element with the highest count.
function mostFrequent(arr) { const freq = {}; // Count occurrences: arr.forEach(n => freq[n] = (freq[n] || 0) + 1); // Find max: return Object.entries(freq) .sort((a, b) => b[1] - a[1]) [0][0]; } // mostFrequent([1,2,2,3,3,3]) → "3"
Step-by-step: 1. Iterate through the array, counting how many times each element appears. 2. Convert the frequency object to entries: [["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):
function mostFrequent(arr) { const map = new Map(); let maxCount = 0, result; arr.forEach(n => { const count = (map.get(n) || 0) + 1; map.set(n, count); if (count > maxCount) { maxCount = count; result = n; } }); return result; }
Q89 Remove duplicates from an array. Easy
Approach 1 — Using Set (simplest):
const unique = arr => [...new Set(arr)]; // Set only stores unique values → spread back to array // [1,2,2,3,3] → Set{1,2,3} → [1,2,3]
Approach 2 — Using filter + indexOf:
arr.filter((v, i) => arr.indexOf(v) === i) // Keep only elements where current index equals first index
Approach 3 — Using reduce:
arr.reduce((acc, v) => acc.includes(v) ? acc : [...acc, v], [])
Time complexity: • Set: O(n) — most efficient. • filter + indexOf: O(n²) — indexOf is O(n) called n times. • reduce + includes: O(n²) — includes is O(n) called n times. Note: these methods don't preserve original order for objects or NaN. For mixed types or edge cases, use Map.
Q90 Group an array of objects by a key. Medium
Approach: Use reduce to accumulate items into groups by the specified key.
function groupByKey(arr, key) { return arr.reduce((acc, item) => { // Get the group name from the item's key property: const groupKey = item[key]; // Initialize group if it doesn't exist, then push the item: (acc[groupKey] = acc[groupKey] || []).push(item); return acc; }, {}); } // Usage: groupByKey([ {name:"A", age:20}, {name:"B", age:20}, {name:"C", age:21} ], "age"); // → { // 20: [{name:"A",age:20}, {name:"B",age:20}], // 21: [{name:"C",age:21}] // }
How it works: 1. Start with an empty object {} 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.
Q91 Implement a debounce function. Medium
What it does: Debounce delays function execution until after a specified pause in calls. If the function is called again within the delay, the timer resets. This prevents a function from firing too frequently. How it works: 1. Call the debounced function → set a timer. 2. Call it again before the timer fires → cancel the old timer, set a new one. 3. Wait long enough (delay ms) → the function finally executes.
function debounce(fn, delay) { let timer; return (...args) => { clearTimeout(timer); timer = setTimeout(() => fn(...args), delay); }; } // Usage — search input (don't search on every keystroke): const debouncedSearch = debounce(fetch, 300); input.addEventListener("input", e => debouncedSearch(e.target.value));
Use cases: search-as-you-type, window resize handlers, form validation, button click prevention. Contrast with throttle: throttle executes at most once every N ms (regular intervals); debounce waits until silence (after the last call).
Q92 Flatten an array (1 level). Easy
Approach 1 — Using flat():
const flatten = arr => arr.flat(); // [1, [2,3], [4,[5]]] → [1,2,3,4,[5]] // flat() only flattens 1 level by default
Approach 2 — Using reduce + concat:
arr.reduce((acc, val) => acc.concat(val), []) // Same result — flattens one level
Deep flatten (all levels):
// flat(Infinity) flattens all nesting levels: arr.flat(Infinity) // [1, [2,3], [4,[5]]] → [1,2,3,4,5] // Or recursively: function deepFlatten(arr) { return arr.reduce((acc, val) => Array.isArray(val) ? [...acc, ...deepFlatten(val)] : [...acc, val], [] ); }
Time: O(n). Note: flat() is ES2019+.
Q93 Sum of even numbers in an array. Easy
Approach 1 — filter then reduce:
const sumEven = arr => arr.filter(n => n % 2 === 0) .reduce((a, b) => a + b, 0); // [1,2,3,4,5] → filter → [2,4] → reduce → 6
Approach 2 — Single-pass reduce (more efficient):
arr.reduce((sum, n) => n % 2 === 0 ? sum + n : sum, 0) // Single pass — no intermediate array created
How it works: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).
Q94 Two-sum problem (find two indices that add to a target). Medium
Problem: Given an array and a target sum, find two numbers that add up to the target and return their indices. Approach 1 — Brute force O(n²): check every pair. Works but slow. Approach 2 — Hashmap O(n) (optimal):
function twoSum(arr, target) { const map = new Map(); for (let i = 0; i < arr.length; i++) { const complement = target - arr[i]; if (map.has(complement)) return [map.get(complement), i]; map.set(arr[i], i); } return null; }
How it works: 1. For each element, calculate its complement: 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.
Q95 Implement a simple Promise. Easy
A Promise represents a value that may be available now, later, or never. How to create one:
const delayedPromise = new Promise((resolve, reject) => { // Do some async work: setTimeout(() => resolve("Done"), 1000); }); // Consume it: delayedPromise.then(result => console.log(result) ); // "Done" after 1 second
The Promise constructor takes a callback with two parameters: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.
§7 Aptitude & Reasoning Q96–Q110
Q96 Salary problem: Person spends 40% rent, 20% food, saves rest. If savings = ₹12,000, find salary. Medium
Solution: ₹30,000 Step-by-step: 1. Rent = 40%, Food = 20%. Total spent = 40% + 20% = 60%. 2. Savings = 100% - 60% = 40% of salary. 3. Given: Savings = ₹12,000 = 40% of salary. 4. Salary = 12,000 / 0.40 = ₹30,000. Verification: 40% of 30,000 = 12,000 ✓, 20% of 30,000 = 6,000. Total spent = 18,000. Savings = 30,000 - 18,000 = 12,000 ✓. Formula: Savings = Salary × Savings%. Therefore: Salary = Savings / Savings%.
Q97 Workers problem: 15 workers × 20 days. How many days for 10 workers? Medium
Solution: 30 days Step-by-step: 1. Total work = Workers × Days = 15 × 20 = 300 worker-days. 2. (1 worker-day = the amount of work 1 person does in 1 day.) 3. With 10 workers: Days = Total work / Workers = 300 / 10 = 30 days. Formula: Workers₁ × Days₁ = Workers₂ × Days₂ (work is constant). Key insight: More workers → fewer days. Fewer workers → more days. The total work stays the same — it's an inverse proportion between workers and days.
Q98 Discount problem: Sold at ₹1,200 after 20% discount. Find the marked price. Medium
Solution: ₹1,500 Step-by-step: 1. A 20% discount means the selling price is 80% of the marked price (MP). 2. Selling Price = 80% × MP → 1,200 = 0.80 × MP. 3. MP = 1,200 / 0.80 = ₹1,500. Verification: 20% of 1,500 = 300. Selling price = 1,500 - 300 = 1,200 ✓. Formula: SP = MP × (100 - discount%)/100. Therefore: MP = SP × 100 / (100 - discount%).
Q99 Train speed: 120 km in 2 hours. Find speed in m/s. Medium
Solution: 16.67 m/s Step-by-step: 1. Speed = Distance / Time = 120 km / 2 h = 60 km/h. 2. To convert km/h to m/s: multiply by 1000/3600 (or divide by 3.6). 3. 60 × (1000/3600) = 60 × (5/18) = 16.67 m/s. Why divide by 3.6? • 1 km = 1000 m, 1 h = 3600 s. • 1 km/h = 1000m / 3600s = 5/18 m/s ≈ 0.2778 m/s. • So to convert: multiply by 5/18 (or equivalently divide by 3.6). Quick trick: To convert m/s to km/h, multiply by 3.6.
Q100 Simple interest: P = ₹10,000, R = 8%, T = 3 years. Find SI. Medium
Solution: ₹2,400 Step-by-step: 1. Simple Interest formula: SI = (P × R × T) / 100 2. P = ₹10,000, R = 8% per annum, T = 3 years. 3. SI = (10,000 × 8 × 3) / 100 = 240,000 / 100 = ₹2,400. Verification: • Total amount = P + SI = 10,000 + 2,400 = ₹12,400. • Interest per year = 10,000 × 8% = ₹800. Over 3 years = ₹2,400 ✓. Note: In Simple Interest, the interest is calculated only on the original principal. In Compound Interest, interest is calculated on principal + accumulated interest, giving a higher amount.
Q101 Number series: 2, 6, 12, 20, 30, ? Medium
Solution: 42 Step-by-step: The series is: 2, 6, 12, 20, 30, ? Let's look at the pattern: • 1×2 = 2 • 2×3 = 6 • 3×4 = 12 • 4×5 = 20 • 5×6 = 30 • 6×7 = 42 The pattern is n(n+1) where n = 1, 2, 3, 4, 5, 6... Alternatively, look at the differences: • 6-2 = 4, 12-6 = 6, 20-12 = 8, 30-20 = 10 • Differences increase by 2 each time: 4, 6, 8, 10, 12 • Next term = 30 + 12 = 42 Both approaches give the same answer.
Q102 Code language: CAT → FDW. How is DOG written? Medium
Solution: GRJ Step-by-step: The pattern is: each letter is shifted forward by 3 positions in the alphabet (+3). Decoding CAT → FDW: • C → C+3 = F • A → A+3 = D • T → T+3 = W Applying the same pattern to DOG: • D → D+3 = G • O → O+3 = R • G → G+3 = J Answer: GRJ Note: This is a Caesar cipher with a shift of 3. After Z, the alphabet wraps around to A (e.g., Y+3 = B).
Q103 A is B's brother, B is C's sister. How is A related to C? Easy
Solution: A is C's brother. Step-by-step: 1. "B is C's sister" → B is female, and B and C are siblings. 2. "A is B's brother" → A is male, and A and B are siblings. 3. Since A and B are siblings, and B and C are siblings, A and C are also siblings. 4. Since A is male, A is C's brother. Key inference: the word "sister" tells us B's gender (female), and "brother" tells us A's gender (male). The relationship chain connects A to C through B.
Q104 Ravi is 12th from left, 15th from right. Total people? Easy
Solution: 26 people Step-by-step: 1. Ravi is 12th from the left, meaning there are 11 people to his left. 2. Ravi is 15th from the right, meaning there are 14 people to his right. 3. Total people = Left position + Right position - 1. 4. Total = 12 + 15 - 1 = 26. Why subtract 1? Because Ravi is counted twice — once in the left count and once in the right count. Subtracting 1 removes the duplicate. Formula: Total = Position from left + Position from right - 1. Example: If someone is 1st from left and 1st from right, total = 1+1-1 = 1 person (just themselves).
Q105 Syllogism: All engineers are smart, some smart are creative. What must be true? Medium
Solution: "Some engineers may be creative" is the only valid conclusion. Step-by-step analysis: Given statements: 1. "All engineers are smart" → Every engineer belongs to the set of smart people. 2. "Some smart are creative" → There's overlap between smart people and creative people. Why "Some engineers may be creative" is correct: • Some smart people are creative (given). • All engineers are smart (given). • Therefore, it's possible that some engineers fall into the "creative" subset of smart people. But we can't say it must be true. Why other conclusions fail: • "All engineers are creative" — NO. We only know some smart are creative, not all. • "All smart are engineers" — NO. All engineers are smart doesn't mean all smart are engineers. • "Some creative are engineers" — NOT necessarily. The creative smart people and engineers might not overlap. Syllogism rule: from "All A are B" and "Some B are C", you can only conclude "Some A may be C".
Q106 Verbal ability: Subject-verb agreement and tense consistency — practice area. Easy
Focus areas for TCS NQT verbal ability: • Subject-verb agreement: singular subjects take singular verbs, plural subjects take plural verbs. • Tense consistency: keep the same tense throughout a sentence or paragraph. • Article usage: a/an/the — use "a" before consonant sounds, "an" before vowel sounds, "the" for specific nouns. • Preposition errors: common confusions like "between/among", "accept/except". • Pronoun references: ensure pronouns clearly refer to the correct noun. Practice with TCS NQT verbal ability sets regularly.
Q107 Verbal ability: Error spotting — practice area. Easy
Error spotting strategy: 1. Read the entire sentence first. 2. Check subject-verb agreement. 3. Check tense consistency. 4. Check preposition usage. 5. Check article usage (a/an/the). 6. Check pronoun-antecedent agreement. 7. Check for parallel structure. Common errors: "each of the students have" → should be "has". "Between you and I" → should be "me".
Q108 Verbal ability: Sentence improvement — practice area. Easy
Sentence improvement strategy: • Look for grammatical errors first, then word choice. • Check if the improved sentence maintains the original meaning. • Pay attention to: - Active vs passive voice - Word order - Redundancy - Idiomatic expressions Example: "He did a mistake" → "He made a mistake" (correct collocation).
Q109 Verbal ability: Reading comprehension — practice area. Easy
Reading comprehension tips: 1. Read the passage carefully (once is enough if you're careful). 2. Underline key points while reading. 3. Go to the questions — identify keywords. 4. Go back to the passage to find the answer. 5. Eliminate obviously wrong options. 6. Watch for: synonyms/paraphrasing in answers, absolute words ("always", "never" are usually wrong), inference vs explicit statement. Time management: spend ~2 minutes per passage+questions.
Q110 Verbal ability: General tips for TCS NQT verbal section. Easy
TCS NQT Verbal Ability Tips:Time: ~18 minutes for ~20 questions — ~50 seconds each. • Synonyms/Antonyms: build vocabulary, read regularly. • Sentence completion: look for context clues, collocations. • Error spotting: grammar rules — subject-verb, tense, articles, prepositions. • Sentence improvement: find the grammatically correct alternative. • Reading comprehension: practice skimming and scanning. • Common topics: technology, environment, business, science. Practice sources: IndiaBix, Prepinsta, TCS-specific mock tests, previous year papers.
Progress: 0 / 110 revealed