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