File: package.json
{
"name": "javascript-interview-questions",
"version": "1.0.0",
"description": "List of 1000 JavaScript Interview Questions",
"author": "Sudheer Jonna",
"scripts": {
"gen": "node scripts/toc.mjs"
},
"dependencies": {
"github-slugger": "^2.0.0"
}
}
File: coding-exercise/debounce-function/README.md
Debounce Function
Challenge
Implement a debounce function that delays the execution of a callback until after a specified delay period has elapsed since the last time it was invoked.
Problem Description
Debouncing is a programming practice used to ensure that time-consuming tasks do not fire so often. It limits the rate at which a function can fire.
Real-World Use Cases
- Search Input: Wait for the user to stop typing before making an API call
- Window Resize: Wait for resize to finish before recalculating layout
- Scroll Events: Reduce the number of scroll event handlers fired
- Button Clicks: Prevent multiple form submissions
Example
Input
function handleSearch(query) {
console.log(`Searching for: ${query}`);
}
const debouncedSearch = debounce(handleSearch, 500);
// User types rapidly
debouncedSearch('J');
debouncedSearch('Ja');
debouncedSearch('Jav');
debouncedSearch('JavaScript');Output
// Only executes once after 500ms of the last call
Searching for: JavaScriptRequirements
- The debounce function should accept a function and a delay time
- It should return a new function that delays invoking the original function
- Each new call should reset the delay timer
- Only the last call should execute after the delay period
- The function should preserve the correct
thiscontext and arguments
Key Concepts
- Closures: Maintaining state (timeoutId) across function calls
- Higher-Order Functions: Returning a function from a function
- setTimeout/clearTimeout: Managing asynchronous delays
- Function Context: Using
apply()to preservethisbinding
Difference from Throttling
- Debounce: Executes the function only after the calls have stopped for a specified period
- Throttle: Executes the function at most once per specified time interval
Benefits
- Improves performance by reducing unnecessary function calls
- Reduces API calls and server load
- Provides better user experience by preventing excessive updates
File: coding-exercise/deep-merge-nested-objects/README.md
Deep-Merge Two Nested Objects (with Circular References)
Challenge:
Implement a deep-merge function that combines two nested JavaScript objects while safely handling circular references. The function must be immutable and able to merge objects, arrays, and nested structures.
Problem Description:
Deep merging means recursively combining the properties of two objects. When both inputs contain nested objects, arrays, or shared references, this becomes more complex.
Your task is to:
Merge all properties from both objects
Recursively merge nested objects
Merge arrays by index
Avoid infinite recursion when circular references exist
Return a new, immutable object
Ensure values from the second object override the first when conflicts occur
Real-World Use Case
State management libraries (Redux, Zustand, Immer)
Complex configuration merging
Deep cloning with overrides
Normalizing API responses
Merging schema definitions
Example:
Input
const a = {
x: 1,
y: { z: 2 }
};
a.self = a; // circular reference
const b = {
y: { k: 20 },
m: 100
};
b.loop = b; // circular reference
const result = deepMerge(a, b);
Output
{
x: 1,
y: { z: 2, k: 20 },
m: 100,
self: [circular],
loop: [circular]
}
Requirements:
Must deep-merge nested objects and arrays
Must detect and safely handle circular references
Must not mutate inputs
When both objects contain the same key:
If both values are objects โ recursively merge
If both are arrays โ merge by index
Otherwise โ value from second object overrides
Must create a brand-new object as the output
Key Concepts:
Recursion
WeakMap to track visited nodes
Object and array cloning
Circular reference detection
Immutability principles
File: coding-exercise/deep-merge-nested-objects/question.md
Exercise: Deep-Merge Two Nested Objects (with Circular References)
Problem Statement
Write a function deepMerge(objA, objB) that deeply merges two JavaScript objects without mutating either of them.
The function must correctly merge nested structures and safely handle circular references.
Rules
- If a key exists only in one object โ copy that value.
- If a key exists in both objects:
- If both values are plain objects, recursively merge them.
- If both values are arrays, merge by index (element-wise).
- Otherwise, the value from
objBoverrides the value fromobjA.
- Handle circular references using a tracking mechanism like WeakMap.
- Always return a new, immutable object.
- Must preserve structure even when circular references appear on both inputs.
Example
const a = {
x: { y: 1 },
arr: [1, 2]
};
a.self = a; // circular
const b = {
x: { z: 2 },
arr: [3],
extra: true
};
b.loop = b; // circular
const result = deepMerge(a, b);
/*
Expected structure:
{
x: { y: 1, z: 2 },
arr: [1, 3], // merged by index
extra: true,
self: <circular>,
loop: <circular>
}
*/
---
## File: coding-exercise/superArrayOfObjects/README.md
### Count the occurrence of keys and convert the result into array of objects where each object belongs to one key and it's occurrence (count).
#### Example
```js
[
{ language: 'JavaScript' }, { language: 'JavaScript' }, { language: 'TypeScript' }, { language: 'C++' }
]SHOULD BE CONVERTED TO =
[
{ language: 'JavaScript', count: 2 },
{ language: 'C++', count: 1 },
{ language: 'TypeScript', count: 1 }
]The idea is to count the frequency of each unique key in an array of objects and then instead of making the result look like
{ key1: 2, key2: 1, key3: 7 }The result should be an array of objects so that it can be map over and get rendered in React.JS or something like that.
[ { key1: 2 }, { key2: 1 }, { key3: 7 } ]File: coding-exercise/throttle-function/README.md
Throttle Function
Challenge
Implement a throttle function that limits the rate at which a callback can execute, ensuring it runs at most once per specified time interval.
Problem Description
Throttling is a technique used to control the rate at which a function executes. Unlike debouncing (which delays execution until activity stops), throttling ensures a function executes at regular intervals during continuous activity.
Real-World Use Cases
- Scroll Events: Update UI elements (like progress bars or lazy-loading) at a controlled rate while scrolling
- Mouse Movement: Track cursor position without overwhelming the browser with updates
- Window Resize: Recalculate layout at regular intervals during resize
- API Rate Limiting: Ensure requests don't exceed API rate limits
- Game Development: Limit action frequency (e.g., shooting, jumping) to prevent spam
- Auto-save: Save user input at regular intervals while they're typing
- Infinite Scroll: Load more content at controlled intervals while scrolling
Example
Input
function updateScrollPosition(position) {
console.log(`Scroll position: ${position}px`);
}
const throttledUpdate = throttle(updateScrollPosition, 1000);
// User scrolls continuously
throttledUpdate(100); // t=0ms
throttledUpdate(200); // t=100ms
throttledUpdate(300); // t=200ms
throttledUpdate(400); // t=300ms
throttledUpdate(500); // t=1100ms
throttledUpdate(600); // t=1200msOutput
// Executes at regular 1000ms intervals
Scroll position: 100px // t=0ms (immediate)
Scroll position: 500px // t=1100ms (after 1000ms)
Scroll position: 600px // t=2200ms (after another 1000ms)Requirements
- The throttle function should accept a function and a time limit
- It should return a new function that limits execution rate
- The function should execute immediately on the first call (leading edge)
- Subsequent calls within the time limit should be controlled
- The function should preserve the correct
thiscontext and arguments - Should handle both leading and trailing edge execution options
Key Concepts
- Closures: Maintaining state (lastExecuted, timeoutId) across function calls
- Higher-Order Functions: Returning a function from a function
- setTimeout/clearTimeout: Managing asynchronous delays
- Function Context: Using
apply()to preservethisbinding - Timestamps: Using
Date.now()to track execution timing
Implementation Approaches
1. Leading Edge Throttle
Executes immediately on first call, then ignores calls for the limit duration:
function throttleLeading(func, limit) {
let inThrottle = false;
return function(...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}2. Leading + Trailing Edge Throttle
Executes immediately and also schedules a final execution:
function throttle(func, limit) {
let lastExecuted = 0;
let timeoutId = null;
return function(...args) {
const now = Date.now();
const timeSinceLastExecution = now - lastExecuted;
if (timeSinceLastExecution >= limit) {
lastExecuted = now;
func.apply(this, args);
} else {
clearTimeout(timeoutId);
const remainingTime = limit - timeSinceLastExecution;
timeoutId = setTimeout(() => {
lastExecuted = Date.now();
func.apply(this, args);
}, remainingTime);
}
};
}Throttle vs Debounce
| Feature | Throttle | Debounce |
|---|---|---|
| Execution Pattern | At regular intervals during activity | Only after activity stops |
| Frequency | Guaranteed execution every X ms | Single execution after silence |
| Use Case | Continuous updates (scroll, resize) | Wait for completion (search input) |
| Example | Update scroll position every 100ms | Search API call 500ms after typing stops |
| Behavior | Executes periodically while active | Executes once when idle |
Visual Comparison
User Activity: โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ โ โ โ โ โ โ โ
Throttle: โ โ โ โ
(executes at regular intervals)
Debounce: โ
(executes only after activity stops)Benefits
- Performance: Reduces unnecessary function calls during high-frequency events
- Consistency: Ensures predictable execution intervals
- User Experience: Provides smooth, responsive updates without lag
- Resource Management: Prevents overwhelming the browser or server
- Battery Life: Reduces CPU usage on mobile devices
Common Pitfalls
1. Losing `this` Context
// โ Wrong: Arrow function loses context
function throttle(func, limit) {
return () => func(); // 'this' is lost
}
// โ Correct: Use regular function and apply
function throttle(func, limit) {
return function(...args) {
func.apply(this, args); // Preserves 'this'
};
}2. Not Clearing Previous Timeouts
// โ Wrong: Multiple timeouts can stack up
function throttle(func, limit) {
return function() {
setTimeout(() => func(), limit); // Creates new timeout every call
};
}
// โ Correct: Clear previous timeout
function throttle(func, limit) {
let timeoutId;
return function() {
clearTimeout(timeoutId); // Clear previous
timeoutId = setTimeout(() => func(), limit);
};
}3. Forgetting to Pass Arguments
// โ Wrong: Arguments are lost
function throttle(func, limit) {
return function() {
func(); // No arguments passed
};
}
// โ Correct: Collect and pass arguments
function throttle(func, limit) {
return function(...args) {
func.apply(this, args); // Pass all arguments
};
}Interview Tips
Questions You Might Be Asked
"What's the difference between throttle and debounce?"
- Throttle: Regular intervals during activity
- Debounce: Single execution after activity stops
"When would you use throttle over debounce?"
- Use throttle for continuous updates (scroll, resize, mouse move)
- Use debounce for completion-based actions (search, form validation)
"How would you implement leading vs trailing edge?"
- Leading: Execute immediately, ignore subsequent calls
- Trailing: Schedule execution for end of interval
- Both: Execute immediately and schedule final call
"What are the performance benefits?"
- Reduces function calls (e.g., from 1000/sec to 10/sec)
- Prevents browser lag and jank
- Reduces API calls and server load
"How do you preserve the
thiscontext?"- Use regular function (not arrow function)
- Use
func.apply(this, args)to call original function
Code Review Points
- โ Preserves
thiscontext withapply() - โ Passes all arguments with rest/spread operators
- โ Clears previous timeouts to prevent memory leaks
- โ Uses closures correctly to maintain state
- โ Handles edge cases (first call, rapid calls, etc.)
Advanced Variations
Throttle with Options
function throttle(func, limit, options = {}) {
const { leading = true, trailing = true } = options;
let lastExecuted = 0;
let timeoutId = null;
return function(...args) {
const now = Date.now();
if (!lastExecuted && !leading) {
lastExecuted = now;
}
const remaining = limit - (now - lastExecuted);
if (remaining <= 0) {
if (timeoutId) {
clearTimeout(timeoutId);
timeoutId = null;
}
lastExecuted = now;
func.apply(this, args);
} else if (!timeoutId && trailing) {
timeoutId = setTimeout(() => {
lastExecuted = leading ? Date.now() : 0;
timeoutId = null;
func.apply(this, args);
}, remaining);
}
};
}Throttle with Cancel Method
function throttle(func, limit) {
let timeoutId = null;
let lastExecuted = 0;
const throttled = function(...args) {
const now = Date.now();
const remaining = limit - (now - lastExecuted);
if (remaining <= 0) {
lastExecuted = now;
func.apply(this, args);
}
};
// Add cancel method
throttled.cancel = function() {
clearTimeout(timeoutId);
timeoutId = null;
lastExecuted = 0;
};
return throttled;
}Testing Considerations
// Test basic throttling
const mockFn = jest.fn();
const throttled = throttle(mockFn, 1000);
throttled(); // Should execute
throttled(); // Should be ignored
jest.advanceTimersByTime(1000);
throttled(); // Should execute
expect(mockFn).toHaveBeenCalledTimes(2);Related Patterns
- Debounce: Delays execution until activity stops
- Rate Limiting: Restricts number of calls in a time window
- Request Animation Frame: Browser-optimized throttling for animations
- Web Workers: Offload heavy computations to background threads
Further Reading
File: .github/FUNDING.yml
github: [sudheerj]
custom: https://buymeacoffee.com/sudheerj
File: .github/workflows/gen-toc.yml
name: Generate table of contents
on:
pull_request:
push:
branches:
- master
env:
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
jobs:
gen-toc:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
with:
ref: ${{ github.head_ref }}
- name: Install dependencies
run: npm install
- name: Generate table of contents
run: npm run gen
- name: 'Commit changes if required'
run: |
if ! git diff --quiet README.md; then
git config user.email "github-actions[bot]@users.noreply.github.com"
git config user.name "GitHub Actions"
git add README.md
git commit -m "[auto] regenerate table of contents"
git push
echo "[info] Table of contents updated and committed."
else
echo "[info] No changes to table of contents."
fi
working-directory: ${{ github.workspace }}