From 7cedbc85241d1dd420f0e4c628b2576a3ee4f429 Mon Sep 17 00:00:00 2001 From: Sneha Bayan Date: Sat, 25 Oct 2025 09:13:53 +0000 Subject: [PATCH] Add challenging JS interview questions topic and runnable examples/tests --- README.md | 4 + examples/README.md | 14 + examples/example-closures-loop-var-let.js | 27 ++ examples/example-event-loop-micro-macro.js | 22 ++ examples/example-hoisting-tdz.js | 31 ++ examples/example-this-binding.js | 22 ++ examples/run-tests.js | 59 ++++ package-lock.json | 13 + topics/challenging-js-interview-questions.md | 343 +++++++++++++++++++ 9 files changed, 535 insertions(+) create mode 100644 examples/README.md create mode 100644 examples/example-closures-loop-var-let.js create mode 100644 examples/example-event-loop-micro-macro.js create mode 100644 examples/example-hoisting-tdz.js create mode 100644 examples/example-this-binding.js create mode 100644 examples/run-tests.js create mode 100644 package-lock.json create mode 100644 topics/challenging-js-interview-questions.md diff --git a/README.md b/README.md index 924a02f7..024dbb23 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,10 @@ This repository was created with the intention of helping developers master their concepts in JavaScript. It is not a requirement, but a guide for future studies. It is based on an article written by Stephen Curtis and you can read it [here](https://medium.com/@stephenthecurt/33-fundamentals-every-javascript-developer-should-know-13dd720a90d1). +### Extra topics in this fork + +- `topics/challenging-js-interview-questions.md` — challenging JavaScript interview questions with solutions and explanations. + ## Community Feel free to submit a PR by adding a link to your own recaps or reviews. If you want to translate the repo into your native language, please feel free to do so. diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 00000000..17a9154b --- /dev/null +++ b/examples/README.md @@ -0,0 +1,14 @@ +This folder contains runnable examples and small tests for selected deterministic interview questions from `topics/challenging-js-interview-questions.md`. + +How to run (requires Node.js >= 12): + +- Run an example: + + node example-closures-loop-var-let.js + +- Run tests: + + node run-tests.js + +Notes: +- Tests use Node's built-in `assert` and do not require extra dependencies. diff --git a/examples/example-closures-loop-var-let.js b/examples/example-closures-loop-var-let.js new file mode 100644 index 00000000..47ca1092 --- /dev/null +++ b/examples/example-closures-loop-var-let.js @@ -0,0 +1,27 @@ +// Example: closures + loop differences (var vs let) +// This script prints expected outputs and exposes behavior for tests. + +function runVarLoop() { + const results = []; + for (var i = 0; i < 3; i++) { + // emulate async callbacks by pushing functions and calling later + results.push(() => i); + } + // call callbacks + return results.map(fn => fn()); +} + +function runLetLoop() { + const results = []; + for (let j = 0; j < 3; j++) { + results.push(() => j); + } + return results.map(fn => fn()); +} + +if (require.main === module) { + console.log('var loop:', runVarLoop()); + console.log('let loop:', runLetLoop()); +} + +module.exports = { runVarLoop, runLetLoop }; \ No newline at end of file diff --git a/examples/example-event-loop-micro-macro.js b/examples/example-event-loop-micro-macro.js new file mode 100644 index 00000000..0e09c5cd --- /dev/null +++ b/examples/example-event-loop-micro-macro.js @@ -0,0 +1,22 @@ +// Example: event loop order (microtasks vs macrotasks) +// This example returns the order of operations by instrumentation. + +function recordOrder() { + const out = []; + out.push('script start'); + setTimeout(() => out.push('timeout'), 0); + Promise.resolve().then(() => out.push('promise')); + out.push('script end'); + // we need to return a promise that resolves after macrotask runs + return new Promise(resolve => { + setTimeout(() => { + resolve(out); + }, 10); + }); +} + +if (require.main === module) { + recordOrder().then(order => console.log(order)); +} + +module.exports = { recordOrder }; \ No newline at end of file diff --git a/examples/example-hoisting-tdz.js b/examples/example-hoisting-tdz.js new file mode 100644 index 00000000..467dc91d --- /dev/null +++ b/examples/example-hoisting-tdz.js @@ -0,0 +1,31 @@ +// Example: hoisting with var vs temporal dead zone (let) + +function hoistingVar() { + // var is hoisted and initialized to undefined + try { + // accessing 'a' before declaration returns undefined + const before = a; // eslint-disable-line no-undef + var a = 1; + return { before, after: a }; + } catch (err) { + return { error: err.message }; + } +} + +function hoistingLet() { + try { + // accessing b before declaration throws ReferenceError + const before = b; // eslint-disable-line no-undef + let b = 2; + return { before, after: b }; + } catch (err) { + return { error: err.name }; + } +} + +if (require.main === module) { + console.log('hoistingVar:', hoistingVar()); + console.log('hoistingLet:', hoistingLet()); +} + +module.exports = { hoistingVar, hoistingLet }; \ No newline at end of file diff --git a/examples/example-this-binding.js b/examples/example-this-binding.js new file mode 100644 index 00000000..22f44051 --- /dev/null +++ b/examples/example-this-binding.js @@ -0,0 +1,22 @@ +// Example: this binding variations + +const obj = { + x: 10, + getX() { return this.x; } +}; + +function plainGetX() { + return obj.getX; +} + +function boundGetX() { + return obj.getX.bind(obj); +} + +if (require.main === module) { + console.log('obj.getX():', obj.getX()); + console.log('plain call:', plainGetX()()); + console.log('bound call:', boundGetX()()); +} + +module.exports = { obj, plainGetX, boundGetX }; \ No newline at end of file diff --git a/examples/run-tests.js b/examples/run-tests.js new file mode 100644 index 00000000..f79e6a7d --- /dev/null +++ b/examples/run-tests.js @@ -0,0 +1,59 @@ +// Simple test runner using Node's assert +const assert = require('assert'); +const { runVarLoop, runLetLoop } = require('./example-closures-loop-var-let'); +const { hoistingVar, hoistingLet } = require('./example-hoisting-tdz'); +const { recordOrder } = require('./example-event-loop-micro-macro'); +const { obj, plainGetX, boundGetX } = require('./example-this-binding'); + +function testClosures() { + const varResults = runVarLoop(); + // since functions return current i which after loop is 3 + assert.deepStrictEqual(varResults, [3,3,3], 'var loop should give [3,3,3]'); + + const letResults = runLetLoop(); + assert.deepStrictEqual(letResults, [0,1,2], 'let loop should give [0,1,2]'); +} + +function testHoisting() { + const v = hoistingVar(); + assert.strictEqual(v.before, undefined, 'var before should be undefined'); + assert.strictEqual(v.after, 1, 'var after should be 1'); + + const l = hoistingLet(); + // we expect a ReferenceError name + assert.strictEqual(l.error, 'ReferenceError', 'let before access should throw ReferenceError'); +} + +async function testEventLoop() { + const order = await recordOrder(); + // expected: script start, script end, promise, timeout + assert.deepStrictEqual(order, ['script start','script end','promise','timeout']); +} + +function testThis() { + assert.strictEqual(obj.getX(), 10, 'method call returns 10'); + assert.strictEqual(plainGetX()(), undefined, 'plain function call returns undefined (strict mode)'); + assert.strictEqual(boundGetX()(), 10, 'bound function returns 10'); +} + +async function runAll() { + console.log('Running tests...'); + try { + testClosures(); + console.log('Closures tests passed'); + testHoisting(); + console.log('Hoisting tests passed'); + await testEventLoop(); + console.log('Event loop test passed'); + testThis(); + console.log('this binding tests passed'); + console.log('All tests passed ✅'); + } catch (err) { + console.error('Test failed:', err.message); + process.exitCode = 1; + } +} + +if (require.main === module) runAll(); + +module.exports = { runAll }; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..0c5c6d6d --- /dev/null +++ b/package-lock.json @@ -0,0 +1,13 @@ +{ + "name": "33-js-concepts", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "33-js-concepts", + "version": "1.0.0", + "license": "MIT" + } + } +} diff --git a/topics/challenging-js-interview-questions.md b/topics/challenging-js-interview-questions.md new file mode 100644 index 00000000..cd14bcb0 --- /dev/null +++ b/topics/challenging-js-interview-questions.md @@ -0,0 +1,343 @@ +## Challenging JavaScript Interview Questions (with solutions) + +This topic collects often-asked, tricky JavaScript questions with short solutions and clear explanations. Each entry shows the code, expected output, and a brief explanation of why the result occurs. + +--- + +### 1) Closures & loops (var vs let) +Code: + +```js +for (var i = 0; i < 3; i++) { + setTimeout(() => console.log(i), 0); +} + +for (let j = 0; j < 3; j++) { + setTimeout(() => console.log(j), 0); +} +``` + +Expected output: + +- First loop (var): 3, 3, 3 +- Second loop (let): 0, 1, 2 + +Explanation: + +- `var` is function-scoped; the same `i` is shared and after the loop finishes it's 3. The callbacks run later and see `i === 3`. +- `let` is block-scoped and creates a fresh binding per iteration, so each callback captures its own value. + +Solution / tip: + +- Use `let` for per-iteration bindings or create a closure: `((i) => setTimeout(() => console.log(i),0))(i)`. + +--- + +### 2) Hoisting and temporal dead zone +Code: + +```js +console.log(a); +var a = 1; + +console.log(b); +let b = 2; +``` + +Expected output: + +- `undefined` then a ReferenceError for `b` (access before initialization) + +Explanation: + +- `var` declarations are hoisted and initialized to `undefined` at top of their scope. +- `let`/`const` are hoisted but not initialized — they live in the Temporal Dead Zone (TDZ) until their declaration is evaluated, so accessing them before throws. + +--- + +### 3) `this` in different contexts +Code: + +```js +const obj = { + x: 10, + getX() { return this.x; } +}; + +const fn = obj.getX; +console.log(obj.getX()); // ? +console.log(fn()); // ? + +const bound = fn.bind(obj); +console.log(bound()); // ? +``` + +Expected output: + +- `10` +- `undefined` (or global/window.x in non-strict browsers) +- `10` + +Explanation: + +- Method call `obj.getX()` sets `this` to `obj`. +- `fn()` is a plain function call; `this` is `undefined` in strict mode or global object in sloppy mode. +- `bind` permanently sets `this` to the provided object. + +Tip: + +- Use arrow functions when you want lexical `this` (they don't rebind `this`). Use `bind`/`call`/`apply` to set `this` explicitly. + +--- + +### 4) Event loop — microtasks vs macrotasks +Code: + +```js +console.log('script start'); + +setTimeout(() => console.log('timeout'), 0); + +Promise.resolve().then(() => console.log('promise')); + +console.log('script end'); +``` + +Expected output: + +- script start +- script end +- promise +- timeout + +Explanation: + +- Synchronous code runs first. +- Microtasks (Promises `.then`) run after the current stack completes but before macrotasks (setTimeout callbacks). + +Tip: + +- Use microtasks for high-priority async follow-ups (Promises, queueMicrotask); macrotasks for lower priority scheduling. + +--- + +### 5) Promise chaining and error propagation +Code: + +```js +Promise.resolve(1) + .then(x => x + 1) + .then(x => { throw new Error('fail'); }) + .then(x => console.log('will not run', x)) + .catch(err => console.log('caught', err.message)); +``` + +Expected output: + +- caught fail + +Explanation: + +- An error thrown in a `.then` handler turns into a rejected promise and flows down the chain until a `.catch` (or second argument to `.then`) handles it. + +Tip: + +- Always return or throw explicitly inside `.then` handlers; use a final `.catch` for top-level handling. + +--- + +### 6) Async/await and try/catch +Code: + +```js +async function f() { + try { + const r = await Promise.reject('bad'); + console.log('won't run', r); + } catch (e) { + console.log('caught', e); + } +} +f(); +``` + +Expected output: + +- caught bad + +Explanation: + +- `await` will reject if the awaited promise rejects. Use try/catch inside the async function to handle it. + +Tip: + +- Alternatively handle errors with `.catch` on the awaited promise or at the caller side by catching the returned promise. + +--- + +### 7) Prototypes and property shadowing +Code: + +```js +function A() { this.x = 1 } +A.prototype.x = 2; +const a = new A(); +console.log(a.x); // ? +delete a.x; +console.log(a.x); // ? +``` + +Expected output: + +- `1` +- `2` + +Explanation: + +- Instance property `x` shadows prototype `x`. Deleting the instance property reveals the prototype property. + +Tip: + +- Use prototypes for shared behavior, instance properties for per-object state. + +--- + +### 8) Function currying and partial application +Code: + +```js +function add(a) { + return function(b) { return a + b; }; +} +console.log(add(2)(3)); // ? +``` + +Expected output: + +- `5` + +Explanation: + +- Currying transforms a function of multiple args into nested single-arg functions. Each inner function keeps the previous args in closure. + +Tip: + +- Useful for partial application and composing utilities. + +--- + +### 9) `==` vs `===` and surprising coercions +Code: + +```js +console.log(0 == ''); // ? +console.log(0 == '\n'); // ? +console.log([] == false);// ? +``` + +Expected output: + +- true +- true +- true + +Explanation: + +- `==` performs type coercion with complex rules (ToPrimitive, ToNumber, etc.). `''` and `'\n'` coerce to 0 when compared to number 0. `[]` coerces to `''` then to 0 so `[] == false` is true. + +Tip: + +- Prefer `===` and avoid relying on `==` coercion rules in production code. + +--- + +### 10) Immediately-invoked function expressions (IIFE) & module pattern +Code: + +```js +const counter = (function () { + let count = 0; + return { inc: () => ++count, get: () => count }; +})(); + +console.log(counter.get()); +counter.inc(); +console.log(counter.get()); +``` + +Expected output: + +- 0 +- 1 + +Explanation: + +- The IIFE creates a private scope. `count` is private and accessible only via returned methods — a simple module pattern. + +--- + +### 11) `new` operator basics +Code: + +```js +function Person(name) { this.name = name; } +Person.prototype.say = function() { return this.name; }; +const p = Person('Sam'); +console.log(typeof p, globalThis.name); // ? + +const q = new Person('Amy'); +console.log(q.name); // ? +``` + +Expected output (strict-mode dependent): + +- `undefined` (or thrown) and `Sam` on the global object in non-strict mode +- `Amy` + +Explanation: + +- Calling a constructor without `new` executes the function as a normal call. `this` then refers to global object in sloppy mode (or `undefined` in strict mode) — bug-prone. `new` creates a fresh object and sets `this` to it. + +Tip: + +- Always use `new` with constructors or write factories that don't require `new`. + +--- + +### 12) Short-circuit evaluation & default values +Code: + +```js +function greet(name) { + name = name || 'Guest'; + console.log('Hello', name); +} + +greet('Alice'); +greet(0); +``` + +Expected output: + +- Hello Alice +- Hello Guest + +Explanation: + +- `||` treats falsy values (0, '', null, undefined, false) the same. If a valid falsy value like `0` is valid in your domain, using `??` (nullish coalescing) is safer: `name = name ?? 'Guest'`. + +--- + +## Next steps / practice +- Turn several examples into runnable Node scripts or interactive HTML pages. +- Add unit tests for functions (where applicable) to lock expected behavior. +- Add more advanced topics: generators, WeakMap for private state, Symbol, proxies, and performance traps. + +--- + +If you'd like, I can: +- add runnable examples under `examples/` and small tests, +- convert these to interactive snippets (HTML + playground), or +- generate flashcards from these Q/A pairs for practice. + +Happy to proceed with any of those.