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
SOCIAL SHARE CARD GENERATOR