Technical Interview - JavaScript, Node.js & Full Stack (4 Years Experience)
Technical Interview - JavaScript, Node.js & Full Stack (4 Years Experience)
Hey everyone! Welcome back!
This file covers technical interview questions — JavaScript fundamentals, Node.js concepts, React, databases, and system design. These are real questions with polished answers for a Senior Full Stack Developer role.
Let's dive in!
JavaScript Fundamentals
Q: What is the difference between let, const, and var in JavaScript?
Answer:
In JavaScript, there are three ways to declare variables: var, let, and const.
var is function-scoped. It supports hoisting, meaning the declaration is moved to the top, but the value is undefined until the assignment line. Also, var allows both reassignment and redeclaration.
let is block-scoped. It can be reassigned but cannot be redeclared in the same scope. It's hoisted but stays in the Temporal Dead Zone until initialized.
const is also block-scoped. It cannot be reassigned or redeclared. However, if it holds an object or array, the properties inside can still be modified — only the reference is constant.
// var - function scoped, hoisted, can redeclare
var x = 10;
var x = 20; // ✅ No error (redeclaration allowed)
x = 30; // ✅ No error (reassignment allowed)
// let - block scoped, no redeclaration
let y = 10;
// let y = 20; // ❌ SyntaxError (cannot redeclare)
y = 30; // ✅ Reassignment allowed
// const - block scoped, no reassignment, no redeclaration
const z = 10;
// z = 20; // ❌ TypeError (cannot reassign)
// const z = 30; // ❌ SyntaxError (cannot redeclare)
// ⚠️ IMPORTANT: const with objects/arrays
const user = { name: "Shreyesh" };
user.name = "John"; // ✅ This works! (modifying property)
// user = {}; // ❌ TypeError (cannot reassign reference)
const arr = [1, 2, 3];
arr.push(4); // ✅ This works! (modifying contents)
// arr = []; // ❌ TypeError (cannot reassign reference)
| Feature | var | let | const |
|---|---|---|---|
| Scope | Function | Block | Block |
| Hoisting | Yes (undefined) | Yes (TDZ) | Yes (TDZ) |
| Reassign | ✅ Yes | ✅ Yes | ❌ No |
| Redeclare | ✅ Yes | ❌ No | ❌ No |
| Best For | Avoid using | Variables that change | Constants & references |
Key Points: =========== - var → Function-scoped, hoisted as undefined, allows redeclaration - let → Block-scoped, Temporal Dead Zone, no redeclaration - const → Block-scoped, no reassignment, but object properties CAN change - TDZ = Temporal Dead Zone (variable exists but can't be accessed yet) - Always prefer const > let > var in modern JavaScript
Q: How do setCount(count + 1) vs setCount(prev => prev + 1) behave inside useCallback?
This is a React interview question about closures + hooks!
Answer:
There's an important difference between these two when used inside useCallback. Let me explain with examples:
Example 1 — Direct value update (Stale Closure Problem!):
const handleClick = useCallback(() => {
setCount(count + 1);
}, []);
The function is created only once (because of empty dependency array []). It captures the initial value of count. If initial count = 0, every click runs → setCount(0 + 1).
Output: count becomes 1 and stays 1 forever! No matter how many times you click.
Why? ===== useCallback with [] = function created ONCE at mount time It captures count = 0 in its closure Every click: setCount(0 + 1) → always 1! Click 1 → setCount(0 + 1) → count = 1 Click 2 → setCount(0 + 1) → count = 1 (still 0 + 1!) Click 3 → setCount(0 + 1) → count = 1 (still 0 + 1!) This is called STALE CLOSURE — the function is stuck with an old/stale value of count!
Example 2 — Functional update (Correct Way!):
const handleClick = useCallback(() => {
setCount(prev => prev + 1);
}, []);
Function is still created only once. But prev always gets the latest state from React — it doesn't depend on the closure!
Output: count increments normally → 0 → 1 → 2 → 3
Why? ===== prev => prev + 1 is a FUNCTIONAL UPDATE React internally passes the LATEST state as 'prev' It doesn't matter when the function was created! Click 1 → setCount(prev => 0 + 1) → count = 1 Click 2 → setCount(prev => 1 + 1) → count = 2 Click 3 → setCount(prev => 2 + 1) → count = 3 'prev' is always the CURRENT state, not the captured one!
Example 3 — Another way to fix it (but has a trade-off):
const handleClick = useCallback(() => {
setCount(count + 1);
}, [count]);
This works because we added count in the dependency array. But now the function gets recreated whenever count changes — which defeats the purpose of useCallback!
Trade-off:
===========
useCallback(() => { setCount(count + 1) }, [count])
→ ✅ Works correctly
→ ❌ Function recreates on every count change
→ ❌ Defeats the purpose of useCallback (memoization)
useCallback(() => { setCount(prev => prev + 1) }, [])
→ ✅ Works correctly
→ ✅ Function created only ONCE
→ ✅ Best approach!
Key Takeaway:
| Approach | Inside useCallback([], []) | Function Recreated? |
|---|---|---|
| setCount(count + 1) | ❌ Stale closure — always uses old value | No (but broken!) |
| setCount(prev => prev + 1) | ✅ Always uses latest state | No (and works!) |
| setCount(count + 1) with [count] | ✅ Works but recreates function | Yes (defeats purpose) |
Rule: ====== Inside useCallback / useEffect / any memoized function: • count + 1 → depends on the value CAPTURED when function was created • prev => prev + 1 → ALWAYS uses the LATEST state from React So if you're updating based on previous state, FUNCTIONAL UPDATES (prev => prev + 1) are the SAFER choice! This is essentially a CLOSURE problem in React: - useCallback creates a closure - The closure captures variables at creation time - Functional updates bypass this by asking React for the latest value
Post a Comment