If you're serious about becoming a strong JavaScript developer (especially as a full-stack dev), this module is your foundation. These are not just interview topics β they shape how JavaScript thinks.
Letβs break them down clearly, deeply, and in a practical human way.
β 1.1 Language Fundamentals
πΉ 1. var, let, const
These are used to declare variables β but they behave very differently.
var
- Function scoped
- Can be redeclared
- Hoisted (initialized as
undefined) - Causes bugs in modern code
var x = 10;
var x = 20; // Allowed
β οΈ Avoid in modern JS unless you understand legacy behavior.
let
- Block scoped
- Cannot be redeclared in same scope
- Hoisted but in Temporal Dead Zone
let count = 5;
count = 6; // Allowed
const
- Block scoped
- Cannot be reassigned
- Must be initialized
const PI = 3.14;
β οΈ Important:
const does NOT make objects immutable β it only prevents reassignment.
const user = { name: "Nadim" };
user.name = "John"; // Allowed
πΉ 2. Scope (Block vs Function)
Scope determines where variables are accessible.
Function Scope
Created by functions.
var follows this.
function test() {
var x = 10;
}
console.log(x); // β Error
Block Scope
Created by { }
let and const follow this.
{
let a = 5;
}
console.log(a); // β Error
πΉ 3. Hoisting
JavaScript moves declarations to the top during compilation.
console.log(a); // undefined
var a = 10;
Behind the scenes:
var a;
console.log(a);
a = 10;
But with let and const:
console.log(b); // β ReferenceError
let b = 20;
πΉ 4. Temporal Dead Zone (TDZ)
The time between entering scope and variable declaration.
{
console.log(x); // β ReferenceError
let x = 5;
}
This protected zone prevents accidental access before initialization.
πΉ 5. Closures (Very Important)
A closure is when a function remembers variables from its outer scope even after the outer function has finished executing.
function outer() {
let counter = 0;
return function inner() {
counter++;
console.log(counter);
}
}
const increment = outer();
increment(); // 1
increment(); // 2
Even though outer() is done, inner() still remembers counter.
π‘ Used in:
- Data privacy
- Factory functions
- React hooks
πΉ 6. Lexical Scope
Functions access variables based on where they are defined, not where they are called.
function outer() {
let name = "Nadim";
function inner() {
console.log(name);
}
inner();
}
Scope is determined at writing time, not runtime.
πΉ 7. Prototypes
JavaScript uses prototype-based inheritance.
Every object has a hidden [[Prototype]].
function Person(name) {
this.name = name;
}
Person.prototype.greet = function() {
console.log("Hello " + this.name);
}
const p1 = new Person("Nadim");
p1.greet();
All instances share prototype methods (memory efficient).
πΉ 8. this Keyword (Interview Favorite)
this depends on HOW a function is called.
In object method:
const user = {
name: "Nadim",
greet() {
console.log(this.name);
}
}
this = object
In normal function:
function test() {
console.log(this);
}
In browser β window
In strict mode β undefined
In constructor:
this = new object
πΉ 9. Arrow Functions
Arrow functions behave differently with this.
const greet = () => {
console.log(this);
}
Arrow functions:
- Do NOT have their own
this - Inherit
thisfrom surrounding scope - Cannot be used as constructors
Good for callbacks.
Bad for object methods (sometimes).
πΉ 10. Call, Apply, Bind
Used to control this.
function greet() {
console.log("Hi " + this.name);
}
const user = { name: "Nadim" };
greet.call(user);
call()
Arguments passed separately.
greet.call(user, arg1, arg2);
apply()
Arguments passed as array.
greet.apply(user, [arg1, arg2]);
bind()
Returns new function.
const newFunc = greet.bind(user);
newFunc();
πΉ 11. Destructuring
Extract values from objects/arrays easily.
Object:
const user = { name: "Nadim", age: 25 };
const { name, age } = user;
Array:
const arr = [1, 2, 3];
const [a, b] = arr;
Cleaner and readable.
πΉ 12. Spread & Rest Operators (...)
Same symbol. Different purpose.
Spread (expands)
const arr1 = [1,2];
const arr2 = [...arr1, 3,4];
Used for:
- Copying arrays
- Merging objects
Rest (collects)
function sum(...numbers) {
return numbers.reduce((a,b) => a+b);
}
Collects remaining arguments into array.
π― Final Thoughts
If you deeply understand:
- Scope
- Closures
this- Prototypes
- Hoisting
Youβre already above 70% of JavaScript developers.
Most developers memorize syntax.
Strong developers understand behavior.
And interviews?
They test behavior.
β 1.2 Asynchronous JavaScript (Deep Dive β Interview Ready)
JavaScript is single-threaded.
That means it can do one thing at a time.
But then how does it handle:
- API calls π
- Timers β³
- File reading π
- Database queries πΎ
Thatβs where Asynchronous JavaScript comes in.
Letβs break this down clearly and deeply.
π 1. Event Loop (The Heart of Async JS)
The Event Loop is the mechanism that allows JavaScript to handle async operations without blocking the main thread.
Think of it like a manager:
- Checks if Call Stack is empty
- If empty β pushes tasks from the queue
- Keeps everything running smoothly
Without the event loop, JS would freeze every time it waits for data.
π 2. Call Stack
The Call Stack is where JavaScript executes functions.
It works like a stack (LIFO β Last In First Out).
function one() {
two();
}
function two() {
console.log("Hello");
}
one();
Execution order:
-
one()pushed -
two()pushed -
console.log()pushed - Then everything pops out
If the stack is busy β nothing else runs.
Thatβs why synchronous heavy code blocks the UI.
βοΈ 3. Microtask vs Macrotask (Very Important for Interviews)
JavaScript has two main queues:
π‘ Macrotask Queue
Examples:
setTimeoutsetIntervalsetImmediate- DOM events
setTimeout(() => {
console.log("Timeout");
}, 0);
π΅ Microtask Queue
Higher priority than macrotasks.
Examples:
Promise.thencatchfinally-
queueMicrotask
Promise.resolve().then(() => {
console.log("Promise");
});
π₯ Execution Priority
Order:
- Call Stack
- Microtask Queue
- Macrotask Queue
Example:
console.log("Start");
setTimeout(() => console.log("Timeout"), 0);
Promise.resolve().then(() => console.log("Promise"));
console.log("End");
Output:
Start
End
Promise
Timeout
Even though timeout is 0ms, promises run first.
Why?
Because microtasks are processed before macrotasks.
This is a common interview trap question.
π€ 4. Promises
A Promise represents a value that may be available now, later, or never.
States:
- Pending
- Fulfilled
- Rejected
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Data received");
}, 1000);
});
Consume it:
promise
.then(data => console.log(data))
.catch(err => console.log(err));
Promise Chaining
fetchData()
.then(data => process(data))
.then(result => console.log(result))
.catch(err => console.log(err));
Each .then() returns a new promise.
π 5. Async / Await
Cleaner syntax for promises.
async function getData() {
const response = await fetch(url);
const data = await response.json();
console.log(data);
}
Important:
-
asyncmakes function return a promise -
awaitpauses execution inside async function - It does NOT block the main thread
It only pauses that function.
β 6. Error Handling in Async Code
With Promises
fetchData()
.then(res => console.log(res))
.catch(err => console.log("Error:", err));
With Async/Await (Recommended)
async function getData() {
try {
const data = await fetchData();
console.log(data);
} catch (error) {
console.log("Error:", error);
}
}
Always use try/catch with async-await.
β οΈ Important Interview Question:
What if you forget await?
const data = fetchData();
console.log(data);
Youβll get a Promise, not actual data.
β‘ 7. Promise Utility Methods
These are powerful.
πΉ Promise.all()
Runs multiple promises in parallel.
If ONE fails β entire thing fails.
Promise.all([promise1, promise2])
.then(results => console.log(results))
.catch(err => console.log(err));
Best when:
- All results required
- Fast parallel execution needed
πΉ Promise.race()
Returns first settled promise (resolve OR reject).
Promise.race([p1, p2])
.then(result => console.log(result));
Used for:
- Timeouts
- First response wins
πΉ Promise.allSettled()
Waits for ALL promises.
Does NOT fail if one rejects.
Promise.allSettled([p1, p2])
.then(results => console.log(results));
Returns:
[
{ status: "fulfilled", value: ... },
{ status: "rejected", reason: ... }
]
Best when:
- You want all results regardless of failure
π§ Deep Concept Understanding
When async code runs:
- Async operation goes to Web APIs (browser/Node)
- After completion β callback goes to Queue
- Event loop checks stack
- Moves task to Call Stack
JavaScript is single-threaded.
Concurrency is managed by the event loop system.
π― Final Thoughts
If you deeply understand:
- Event Loop
- Microtask vs Macrotask
- Promise chaining
- Async/Await behavior
- Error handling patterns
You are already thinking like a senior developer.
Most devs use async.
Few understand how it actually works internally.
β 1.2 Asynchronous JavaScript (Deep Dive β Interview Ready)
JavaScript is single-threaded.
That means it can do one thing at a time.
But then how does it handle:
- API calls π
- Timers β³
- File reading π
- Database queries πΎ
Thatβs where Asynchronous JavaScript comes in.
Letβs break this down clearly and deeply.
π 1. Event Loop (The Heart of Async JS)
The Event Loop is the mechanism that allows JavaScript to handle async operations without blocking the main thread.
Think of it like a manager:
- Checks if Call Stack is empty
- If empty β pushes tasks from the queue
- Keeps everything running smoothly
Without the event loop, JS would freeze every time it waits for data.
π 2. Call Stack
The Call Stack is where JavaScript executes functions.
It works like a stack (LIFO β Last In First Out).
function one() {
two();
}
function two() {
console.log("Hello");
}
one();
Execution order:
-
one()pushed -
two()pushed -
console.log()pushed - Then everything pops out
If the stack is busy β nothing else runs.
Thatβs why synchronous heavy code blocks the UI.
βοΈ 3. Microtask vs Macrotask (Very Important for Interviews)
JavaScript has two main queues:
π‘ Macrotask Queue
Examples:
setTimeoutsetIntervalsetImmediate- DOM events
setTimeout(() => {
console.log("Timeout");
}, 0);
π΅ Microtask Queue
Higher priority than macrotasks.
Examples:
Promise.thencatchfinally-
queueMicrotask
Promise.resolve().then(() => {
console.log("Promise");
});
π₯ Execution Priority
Order:
- Call Stack
- Microtask Queue
- Macrotask Queue
Example:
console.log("Start");
setTimeout(() => console.log("Timeout"), 0);
Promise.resolve().then(() => console.log("Promise"));
console.log("End");
Output:
Start
End
Promise
Timeout
Even though timeout is 0ms, promises run first.
Why?
Because microtasks are processed before macrotasks.
This is a common interview trap question.
π€ 4. Promises
A Promise represents a value that may be available now, later, or never.
States:
- Pending
- Fulfilled
- Rejected
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Data received");
}, 1000);
});
Consume it:
promise
.then(data => console.log(data))
.catch(err => console.log(err));
Promise Chaining
fetchData()
.then(data => process(data))
.then(result => console.log(result))
.catch(err => console.log(err));
Each .then() returns a new promise.
π 5. Async / Await
Cleaner syntax for promises.
async function getData() {
const response = await fetch(url);
const data = await response.json();
console.log(data);
}
Important:
-
asyncmakes function return a promise -
awaitpauses execution inside async function - It does NOT block the main thread
It only pauses that function.
β 6. Error Handling in Async Code
With Promises
fetchData()
.then(res => console.log(res))
.catch(err => console.log("Error:", err));
With Async/Await (Recommended)
async function getData() {
try {
const data = await fetchData();
console.log(data);
} catch (error) {
console.log("Error:", error);
}
}
Always use try/catch with async-await.
β οΈ Important Interview Question:
What if you forget await?
const data = fetchData();
console.log(data);
Youβll get a Promise, not actual data.
β‘ 7. Promise Utility Methods
These are powerful.
πΉ Promise.all()
Runs multiple promises in parallel.
If ONE fails β entire thing fails.
Promise.all([promise1, promise2])
.then(results => console.log(results))
.catch(err => console.log(err));
Best when:
- All results required
- Fast parallel execution needed
πΉ Promise.race()
Returns first settled promise (resolve OR reject).
Promise.race([p1, p2])
.then(result => console.log(result));
Used for:
- Timeouts
- First response wins
πΉ Promise.allSettled()
Waits for ALL promises.
Does NOT fail if one rejects.
Promise.allSettled([p1, p2])
.then(results => console.log(results));
Returns:
[
{ status: "fulfilled", value: ... },
{ status: "rejected", reason: ... }
]
Best when:
- You want all results regardless of failure
π§ Deep Concept Understanding
When async code runs:
- Async operation goes to Web APIs (browser/Node)
- After completion β callback goes to Queue
- Event loop checks stack
- Moves task to Call Stack
JavaScript is single-threaded.
Concurrency is managed by the event loop system.
π― Final Thoughts
If you deeply understand:
- Event Loop
- Microtask vs Macrotask
- Promise chaining
- Async/Await behavior
- Error handling patterns
You are already thinking like a senior developer.
Most devs use async.
Few understand how it actually works internally.
β 1.3 Advanced JavaScript Concepts (Deep + Interview Ready)
Now weβre entering the zone where developers become strong engineers.
These topics are heavily asked in:
- Frontend interviews
- React interviews
- Performance optimization rounds
- Senior-level discussions
Letβs break them down clearly, practically, and deeply.
πΉ 1. Debounce
Debounce ensures a function runs only after a certain delay, and only if no new event occurs during that delay.
π‘ Think: βWait until user stops typing.β
Real-world use:
- Search input API calls
- Resize events
- Auto-save features
Example:
function debounce(fn, delay) {
let timer;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}
Usage:
const search = debounce(() => {
console.log("API Call");
}, 500);
If user types quickly β API runs only once after typing stops.
πΉ 2. Throttle
Throttle ensures a function runs at most once in a given time interval.
π‘ Think: βLimit how often it runs.β
Real-world use:
- Scroll events
- Button spam prevention
- Game input controls
Example:
function throttle(fn, limit) {
let inThrottle;
return function (...args) {
if (!inThrottle) {
fn.apply(this, args);
inThrottle = true;
setTimeout(() => (inThrottle = false), limit);
}
};
}
π₯ Debounce vs Throttle (Interview Question)
| Debounce | Throttle |
|---|---|
| Waits for pause | Runs at fixed interval |
| Best for search | Best for scroll |
| Executes after delay | Executes immediately (usually) |
πΉ 3. Currying
Currying transforms a function with multiple arguments into multiple functions each taking one argument.
Instead of:
function add(a, b) {
return a + b;
}
We write:
function curryAdd(a) {
return function (b) {
return a + b;
};
}
curryAdd(2)(3); // 5
Modern Arrow Version:
const add = a => b => a + b;
Why is Currying useful?
- Function reusability
- Functional programming
- Partial application
Example:
const multiply = a => b => a * b;
const double = multiply(2);
double(5); // 10
πΉ 4. Memoization
Memoization caches function results to avoid recalculating expensive operations.
π‘ Think: βIf Iβve already solved this, donβt calculate again.β
Example:
function memoize(fn) {
const cache = {};
return function (...args) {
const key = JSON.stringify(args);
if (cache[key]) {
return cache[key];
}
const result = fn(...args);
cache[key] = result;
return result;
};
}
Usage:
const slowAdd = (a, b) => {
console.log("Calculating...");
return a + b;
};
const fastAdd = memoize(slowAdd);
fastAdd(2,3); // Calculating...
fastAdd(2,3); // Cached
Used heavily in:
- React performance optimization
- Expensive computations
- Dynamic programming
πΉ 5. Deep Clone
Deep cloning creates a completely independent copy of an object.
Simple way (not perfect):
const copy = JSON.parse(JSON.stringify(obj));
β οΈ But this fails for:
- Dates
- Functions
- Undefined
- Circular references
Better modern way:
const copy = structuredClone(obj);
Best approach depends on use case.
πΉ 6. Shallow vs Deep Copy
This is VERY important.
Shallow Copy
Copies only first level.
const obj = { a: 1, b: { c: 2 } };
const copy = { ...obj };
copy.b.c = 100;
console.log(obj.b.c); // 100 β
Nested objects still share reference.
Deep Copy
Copies everything including nested objects.
Now modifying copy wonβt affect original.
Interview Trick Question:
Spread operator creates?
π Shallow copy.
πΉ 7. Garbage Collection
JavaScript automatically manages memory.
It uses Mark-and-Sweep algorithm.
How it works:
- Mark all reachable objects
- Remove unreachable ones
You donβt manually free memory like C/C++.
Butβ¦
Just because GC exists doesnβt mean memory problems disappear.
πΉ 8. Memory Leaks
Memory leak happens when memory is allocated but never released.
Over time β app slows β crashes.
Common Causes
1. Unused global variables
var data = new Array(1000000);
Global variables stay in memory.
2. Forgotten timers
setInterval(() => {
console.log("Running...");
}, 1000);
If not cleared β keeps running.
3. Event listeners not removed
element.addEventListener("click", handler);
If element removed but listener not cleaned β memory leak.
4. Closures holding references
Closures can accidentally keep large objects in memory.
How to Prevent Memory Leaks
- Remove event listeners
- Clear intervals/timeouts
- Avoid unnecessary globals
- Use weak references when needed (
WeakMap,WeakSet)
π§ Final Understanding
If you truly understand:
- Debounce & Throttle β performance control
- Currying β functional mastery
- Memoization β optimization
- Deep vs Shallow copy β reference control
- Garbage collection β memory lifecycle
- Memory leaks β production stability
Youβre no longer a beginner.
Youβre thinking like someone who understands how JavaScript behaves internally.
β 1.4 Coding Practice Topics (Interview Implementation Guide)
Now we move from theory β implementation.
In interviews, they donβt just ask:
βWhat is map?β
They ask:
βCan you implement map without using the built-in method?β
This section will prepare you for real coding rounds.
πΉ 1. Custom map()
Native map() transforms each element and returns a new array.
Custom Implementation:
Array.prototype.myMap = function (callback) {
const result = [];
for (let i = 0; i < this.length; i++) {
result.push(callback(this[i], i, this));
}
return result;
};
Usage:
const arr = [1, 2, 3];
const doubled = arr.myMap(num => num * 2);
console.log(doubled); // [2, 4, 6]
πΉ 2. Custom filter()
Returns elements that satisfy condition.
Array.prototype.myFilter = function (callback) {
const result = [];
for (let i = 0; i < this.length; i++) {
if (callback(this[i], i, this)) {
result.push(this[i]);
}
}
return result;
};
πΉ 3. Custom reduce()
Reduces array to single value.
Array.prototype.myReduce = function (callback, initialValue) {
let accumulator = initialValue ?? this[0];
let startIndex = initialValue ? 0 : 1;
for (let i = startIndex; i < this.length; i++) {
accumulator = callback(accumulator, this[i], i, this);
}
return accumulator;
};
πΉ 4. Flatten Nested Array
Input:
[1, [2, [3, 4]], 5]
Output:
[1, 2, 3, 4, 5]
Recursive Solution:
function flatten(arr) {
let result = [];
for (let item of arr) {
if (Array.isArray(item)) {
result = result.concat(flatten(item));
} else {
result.push(item);
}
}
return result;
}
Modern Shortcut:
arr.flat(Infinity);
But interviewers prefer manual logic.
πΉ 5. Group By Function
Group items by a property.
Input:
[
{ name: "A", age: 20 },
{ name: "B", age: 20 },
{ name: "C", age: 25 }
]
Output:
{
20: [{...}, {...}],
25: [{...}]
}
Implementation:
function groupBy(arr, key) {
return arr.reduce((acc, item) => {
const groupKey = item[key];
if (!acc[groupKey]) {
acc[groupKey] = [];
}
acc[groupKey].push(item);
return acc;
}, {});
}
πΉ 6. Remove Duplicates
Using Set:
const unique = arr => [...new Set(arr)];
Manual Way:
function removeDuplicates(arr) {
const seen = {};
const result = [];
for (let item of arr) {
if (!seen[item]) {
seen[item] = true;
result.push(item);
}
}
return result;
}
πΉ 7. Deep Clone
Basic version:
function deepClone(obj) {
if (obj === null || typeof obj !== "object") return obj;
if (Array.isArray(obj)) {
return obj.map(item => deepClone(item));
}
const cloned = {};
for (let key in obj) {
cloned[key] = deepClone(obj[key]);
}
return cloned;
}
πΉ 8. LRU Cache (Very Popular Interview Question)
LRU = Least Recently Used
When capacity is full β remove least recently used item.
Implementation using Map:
class LRUCache {
constructor(limit) {
this.limit = limit;
this.cache = new Map();
}
get(key) {
if (!this.cache.has(key)) return -1;
const value = this.cache.get(key);
this.cache.delete(key);
this.cache.set(key, value);
return value;
}
put(key, value) {
if (this.cache.has(key)) {
this.cache.delete(key);
}
this.cache.set(key, value);
if (this.cache.size > this.limit) {
const firstKey = this.cache.keys().next().value;
this.cache.delete(firstKey);
}
}
}
Why Map?
Because it preserves insertion order.
πΉ 9. Debounce (Reimplementation)
function debounce(fn, delay) {
let timer;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}
πΉ 10. Throttle (Reimplementation)
function throttle(fn, limit) {
let inThrottle;
return function (...args) {
if (!inThrottle) {
fn.apply(this, args);
inThrottle = true;
setTimeout(() => {
inThrottle = false;
}, limit);
}
};
}
π§ How Interviewers Judge You
They check:
- Do you handle edge cases?
- Do you understand time complexity?
- Can you explain your logic clearly?
- Do you know why this works?
π― Pro Interview Tips
When implementing:
- Start with brute force
- Then optimize
- Mention time complexity
- Discuss edge cases
Example:
- Flatten β O(n)
- GroupBy β O(n)
- LRU β O(1) for get/put
If you master these implementations,
you are ready for:
- FAANG-style frontend interviews
- Senior JS rounds
- React system design discussions
Top comments (0)