JavaScript Interview Guide
JavaScript Interview Guide
com 1
Introduction
I am neither an expert book writer nor a great frontend engineer, I am
someone who is curious and trying to fill a few gaps.
There are many awesome books in the market that help you to get ready
and crack the general Software Engineering or Backend interviews, but a
very few with the focus on the Frontend. This is where the idea to create
this book has hit.
Over 3 years I managed to get a decent exposure to my blog and had around
400 articles written. After getting some good writing experience I decided
to consolidate things in the book format so that It will be easy to refer and
use as a “JavaScript Interview Guide”.
I don’t own the questions, along with the solution many things in the book
you will find are referred from different sources.
ISBN - 978-93-5777-237-2
The information contained in this eBook is for informational purposes only. I am not a
certified teacher. Any legal or educational advice that I give is my opinion based on my
own experience. You should always seek the advice of a certified professional before
acting on something that I have published or recommended.
First Edition.
Other than the code, No part of this book may be reproduced or used
in any manner without the written permission of the copyright
owner except for the use of quotations in a book review.
FIRST EDITION.
JavaScript Questions
1. Promise.all() polyfill
2. Promise.any() polyfill
3. Promise.race() polyfill
4. Promise.finally() polyfill
5. Promise.allSettled() polyfill
6. Custom Promise
7. Execute async functions in Series
8. Execute async functions in Parallel
9. Retry promises N number of times
10. Implement mapSeries async function
11. Implement mapLimit async function
12. Implement async filter function
13. Implement async reject function
14. Execute promises with priority
15. Dependent async tasks
16. Create pausable auto incrementor
17. Implement queue using stack
18. Implement stack using queue
19. Implement stack with min and max method
20. Implement two stacks with an array
21. Implement Priority Queue
22. Implement LRU cache
React Questions
1. usePrevious() hook
2. useIdle() hook
3. useAsync() hook
4. useDebounce() hook
5. useThrottle() hook
6. useResponsive() hook
7. useWhyDidYouUpdate() hook
8. useOnScreen() hook
9. useScript() hook
10. useOnClickOutside() hook
11. useHasFocus() hook
12. useToggle() hook
13. useCopy() hook
14. useLockedBody() hook
15. Number Increment counter
16. Capture product visible in viewport
17. Highlight text on selection
18. Batch API calls in sequence
function example() {
let blog = "learnersbucket";
function displayBlog() {
console.log(blog);
}
displayBlog();
}
example();
// "learnersbucket"
This is because variables are lexical scope in JavaScript and then can
be accessed anywhere inside that scope unless and until they are
overridden. In the above example, the variable is function scoped and
it can be accessed anywhere within the function body (even in the
inner functions).
function example() {
// outer scoped
let blog = "learnersbucket";
function displayBlog() {
displayBlog();
}
example();
// hello
function sum() {
let a = 10;
function add(b) {
return a + b
}
return add;
}
console.log(total);
// 30
function x(a) {
function y(b) {
function z(c) {
return a + b + c;
};
return z;
}
return y;
}
// the inner function also accepts an argument and returns the total of
both
// outer and inner argument
let b = a(20);
let c = b(30);
console.log(c);
// 60
If you notice, in this, the inner function has access to the outer
function’s arguments and can use it for each instance it is created.
Anatomy of Array.reduce()
// verbose
arr.reduce(callbackfn, initialValue);
// simplified
// callback function with parameters
arr.reduce((previousValue, currentValue, currentIndex, array) => {
const nextValue = previousValue + currentValue;
return nextValue;
}, initialValue);
console.log(sum);
// 10
console.log(product);
// 24
Segregation
We can group a certain set of values depending on our requirements.
console.log(segregate);
/*
{
1: [1.1, 1.2, 1.3],
2: [2.2, 2.3, 2.4]
}
*/
Run in sequence
Let’s say we have an array of functions and a value, the value has to be
passed through these functions in a pipe. Like the initial value has to
be passed to the first function and then the returned value from the
first function has to be passed to the next function and so on.
// functions
const upperCase = (str) => {
return str.toUpperCase();
};
// initial value
const initialValue = "learnersbucket";
}, initialValue);
console.log(finalValue);
// "Hello TEKCUBSRENRAEL"
asyncSeriesExecuter(promises);
"Completing 3"
"Completing 1"
"Completing 7"
"Completing 2"
"Completing 5"
Let us try to get this thing clear and understand promises and how it
works.
And promises are to help to get out of “callback hell” while dealing
with the asynchronous code and do much more.
Anatomy of promise
const promise = new Promise((resolve, reject) => {
// resolve or reject
});
Promise has three methods available to it (then, catch, & finally) that
can be used once it is settled (resolved or rejected). Each method
accepts a callback function that is invoked depending on the state of
the promise.
Promise.prototype.then(onResolvedFn, onRejectedFn);
Promise.prototype.finally(onFinallyFn);
Working of promise
Create a promise that will resolve after 5 seconds.
const promise = new Promise((resolve, reject) => {
// a promise that will resolve after
// 5 second
setTimeout(() => {
resolve("Hello World!");
}, 5000);
});
/*
Promise { : "pending" }
: "pending"
: Promise.prototype { ... }
*/
/*
Promise { : "fulfilled", : "Hello World!" }
: "fulfilled"
: "Hello World!"
: Promise.prototype { ... }
*/
promise.catch((error) => {
console.error("Called from catch method", error);
});
promise.catch((error) => {
return error;
}).then((val) => {
console.log("I am chained from catch", val);
});
// "I am chained from catch" "Error 404"
promise.catch((error) => {
return error;
}).then((val) => {
console.log("I am chained from catch", val);
}).finally(() => {
console.log(" Catch block finally done");
});
Notice the order of execution, the first error is handled in .then and
then in .catch and then finally blocks of both are called in order.
Helper methods
The promise object has many static methods. Some are helper’s
methods while others help to process the promise better.
// "I am resolved"
Process methods
These methods help to process async task concurrency. We have
covered each of them in the problems section.
● Promise.all()
● Promise.allSettled()
● Promise.any()
● Promise.race()
example(promise);
// "I am resolved"
// "Task done"
To use it we have to mark the function with the async keyword and
then we can use the await keyword inside the async function.
// fat arrow
const example = async (promise) => {
// promise is wrapped in a try-catch block
// to handle it better
try{
const resp = await promise;
return resp;
}catch(error){
console.error(error);
}finally{
console.log("Task done");
}
};
console.log(example(promise));
// Promise { : "fulfilled", : "I am resolved" }
// "Task done"
example(promise).then((val) => {
console.log(val);
});
//"Task done"
//"I am resolved"
Notice the order of execution here, the try and finally block will be
executed, thus content in the finally block is printed and the value
returned is accessed in the .then that is why "Task done" is printed
before "I am resolved".
1. As a normal function.
function example(){
console.log("Hello World!");
};
example();
// "Hello World!"
2. As a method.
const obj = {
blog: "learnersbucket",
displayBlog: function (){
console.log(this.blog);
}
};
obj.displayBlog();
// "learnersbucket"
3. As a constructor.
const number = new Number("10");
console.log(number);
// 10
The value of this is decided upon how the function is invoked, each
invocation creates its own context and the context decides the value of
this. Also the “strict mode” affects the behavior of this too.
example();
// true
example();
console.log(this.blog);
// "learnersbucket"
this.displayBlog();
Strict mode
If you invoke the function with the strict mode the value of this will be
undefined.
function example(){
"use strict"
// in strict mode this refers to undefined
console.log(this === undefined);
}
example();
// true
It also affects all the inner functions that are defined in the function
which is declared in strict mode.
function example(){
"use strict"
// in strict mode this refers to undefined
console.log(this === undefined);
// inner function
function inner(){
// in strict mode this refers to undefined
console.log(this === undefined);
}
example();
// true
// true
// strict mode
(function example(){
"use strict"
// in strict mode this refers to undefined
console.log(this === undefined);
})();
// true
example.displayBlog();
// true
//"learnersbucket"
The context is set at the time of invocation, thus if we update the value
of the object property value, it will be reflected.
const example = {
example.blog = "MDN";
example.displayBlog();
// true
// "MDN"
const example = {
blog: 'learnersbucket',
displayBlog: function(){
// this refers to the current object
console.log(this === example);
console.log(this === example2);
console.log(this.blog);
}
};
example.displayBlog();
// true
// true
// "MDN"
example2.displayBlog();
// true
// true
// "MDN"
The same happens when you pass the methods to the timers i.e
setTimeout and setInterval. Timers invoke the function as a normal
function or throw errors in strict mode.
const example = {
blog: 'learnersbucket',
displayBlog: function(){
// this refers to the window object
console.log(this === window);
setTimeout(example.displayBlog, 200);
// true
// undefined
If there are any inner functions inside the methods, the value of this
inside them depends upon how the inner function is invoked.
const example = {
blog: 'learnersbucket',
displayBlog: function(){
function inner(){
// this refers to the window object
console.log(this === window);
console.log(this.blog);
};
inner();
}
};
example.displayBlog();
// true
// undefined
To access the value of the parent we can use either the Fat arrow
function or indirect invocation technique using call & apply.
inner();
}
};
example.displayBlog();
// true
// "learnersbucket"
inner.call(this);
}
};
example.displayBlog();
// true
// "learnersbucket"
function example(name) {
console.log(`${name} runs ${this.blog}`);
};
example.call(exampleObj, 'Prashant');
// "Prashant runs learnersbucket"
example.apply(exampleObj, ['Prashant']);
// "Prashant runs learnersbucket"
The difference between call and apply is that apply accepts arguments
in an array, while call accepts it normally.
Permanent binding
When using bind, we can create a new function with the new values
and store it in a variable, and then use it further. It creates fresh
permanent binding without affecting the original function.
function example(blog) {
console.log(`${this.name} runs ${blog}`);
};
bounded('learnersbucket');
// "Prashant runs learnersbucket"
bounded('MDN');
// "Prashant runs MDN"
Implementation
const myPromiseAll = function(taskList) {
//to store results
const results = [];
Test case 1
Input:
function task(time) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve(time);
}, time);
});
}
//run promise.all
myPromiseAll(taskList)
.then(results => {
console.log("got results", results)
})
.catch(console.error);
Output:
//"got results" [1000,5000,3000]
Test case 2
Input:
function task(time) {
return new Promise(function (resolve, reject) {
//run promise.all
myPromiseAll(taskList)
.then(results => {
console.log("got results", results)
})
.catch(console.error);
Output:
"Rejected"
Implementation
const any = function(promisesArray) {
const promiseErrors = new Array(promisesArray.length);
let counter = 0;
Test case 1
Input:
const test1 = new Promise(function (resolve, reject) {
setTimeout(reject, 500, 'one');
});
Output:
"two"
Test case 2
Input:
const test1 = new Promise(function (resolve, reject) {
Output:
["three","one","two"]
● It returns a promise.
● The returned promise fulfills or rejects as soon as any one of
the input promises fulfills or rejects.
● Returned promise resolves with the value of the input
promise or rejects with the reason of the input promise.
Implementation
const race = function(promisesArray) {
return new Promise((resolve, reject) => {
promisesArray.forEach((promise) => {
Promise.resolve(promise)
// resolve, when any of the input promise resolves
.then(resolve, reject)
// reject, when any of the input promise rejects
.catch(reject);
});
});
};
Output:
"two"
Test case 2
Input:
const test1 = new Promise(function (resolve, reject) {
setTimeout(resolve, 500, 'one');
});
Output:
"three"
The same way for Promises we have .then() for when promise resolves
and .catch() for when promise rejects and .finally() block which will
always run after any of those.
Example
Input:
function checkMail() {
return new Promise((resolve, reject) => {
if (Math.random() > 0.5) {
resolve('Mail has arrived');
} else {
reject(new Error('Failed to arrive'));
}
});
}
checkMail()
.then((mail) => {
console.log(mail);
})
.catch((err) => {
console.error(err);
})
Output:
Error: Failed to arrive
"Experiment completed"
Implementation
Promise.prototype.finally = function(callback) {
if (typeof callback !== 'function') {
return this.then(callback, callback);
}
// get the current promise or a new one
const P = this.constructor || Promise;
test('polyfill');
Output:
"first polyfill settled" "after 1005ms"
"first polyfill fulfilled" 2 "after 1007ms"
"second polyfill settled" "after 2006ms"
"second polyfill rejected" 3 "after 2008ms"
"third polyfill settled" "after 3006ms"
"third polyfill fulfilled" 4 "after 3512ms"
"fourth polyfill settled" "after 4000ms"
"fourth polyfill rejected" 7 "after 4506ms"
Edge Case
//This will be resolved with undefined
Promise.resolve(2).then(() => {}, () => {}).then((val) =>
{console.log(val)});
// undefined
//A throw (or returning a rejected promise) in the finally callback will
reject
//the new promise with the rejection reason specified when calling throw()
Promise.reject(2).finally(() => { throw 'Parameter is not a number!'
}).then((val) => {console.log(val)});
// 'Parameter is not a number!'
Implementation
const allSettled = (promises) => {
// map the promises to return a custom response.
const mappedPromises = promises.map((p) => Promise.resolve(p)
.then(
val => ({ status: 'fulfilled', value: val }),
err => ({ status: 'rejected', reason: err })
)
);
Output:
[
{
"status": "fulfilled",
"value": 3
},
{
"status": "rejected",
"reason": 9
},
{
"status": "fulfilled",
"value": 5
}
]
Anatomy of Promise
const promise = new Promise((resolve, reject) => {
// time-consuming async operation
// initial state will be pending
// any one of the below operations can occur at any given time
});
});
});
Working of Promise
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("hello");
}, 1000);
});
promise.then((value) => {
console.log(value);
});
Use two handlers onSuccess and onError and assign this them to the
.then, .catch, .finally methods.
Whenever the resolve or reject methods are invoked, run all the
handlers in sequence and pass down the values to the next.
// enum of states
const states = {
PENDING: 0,
FULFILLED: 1,
REJECTED: 2
}
class MyPromise {
// initialize the promise
constructor(callback) {
this.state = states.PENDING;
try {
callback(this._resolve, this._reject);
} catch (error) {
this._reject(error);
}
}
setTimeout(() => {
if (value instanceof MyPromise) {
value.then(this._resolve, this._reject);
}
this.state = state;
this.value = value;
this._executeHandlers();
}, 0)
}
this.handlers.forEach((handler) => {
if (this.state === states.FULFILLED) {
return handler.onSuccess(this.value);
}
return handler.onFailure(this.value);
})
this.handlers = [];
}
// add handlers
// execute all if any new handler is added
_addHandler = (handler) => {
this.handlers.push(handler);
this._executeHandlers();
}
// then handler
// creates a new promise
// assigned the handler
then = (onSuccess, onFailure) => {
// invoke the constructor
// and new handler
return new MyPromise((resolve, reject) => {
this._addHandler({
onSuccess: (value) => {
if (!onSuccess) {
return resolve(value);
}
try {
return resolve(onSuccess(value));
} catch (error) {
reject(error);
}
},
onFailure: (value) => {
if (!onFailure) {
return reject(value);
try {
return reject(onFailure(value));
} catch (error) {
return reject(error);
}
}
})
})
};
this.then((val) => {
value = val;
wasResolved = true;
return callback();
}).catch((err) => {
value = err;
wasResolved = false;
return callback();
})
if (wasResolved) {
resolve(value);
} else {
reject(value);
}
})
Test Case
Input:
const promise = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve("hello");
}, 1000);
});
promise.then((value) => {
console.log(value);
});
Output:
"hello"
Example
Input:
[
asyncTask(3),
asyncTask(1),
asyncTask(2)
]
Output:
3
1
2
For…of loop allows using await keyword performing the next iteration
only when the previous one is finished.
Test Case
Input:
const asyncTask = function(i) {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(`Completing ${i}`), 100*i)
});
}
const promises = [
asyncTask(3),
asyncTask(1),
asyncTask(7),
asyncTask(2),
asyncTask(5),
];
asyncSeriesExecuter(promises);
Output:
"Completing 3"
"Completing 1"
"Completing 7"
"Completing 2"
"Completing 5"
Implementation
const asyncSeriesExecuter = function(promises) {
// get the top task
let promise = promises.shift();
Test Case
Input:
const asyncTask = function(i) {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(`Completing ${i}`), 100*i)
});
}
const promises = [
asyncTask(3),
asyncTask(1),
asyncTask(7),
asyncTask(2),
asyncTask(5),
];
asyncSeriesExecuter(promises);
Implementation
const asyncSeriesExecuter = function(promises) {
promises.reduce((acc, curr) => {
return acc.then(() => {
return curr.then(val => {console.log(val)});
});
}, Promise.resolve());
}
Test Case
Input:
const asyncTask = function(i) {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(`Completing ${i}`), 100*i)
});
}
const promises = [
asyncTask(3),
asyncTask(1),
asyncTask(7),
asyncTask(2),
asyncTask(5),
];
Output:
"Completing 3"
"Completing 1"
"Completing 7"
"Completing 2"
"Completing 5"
Example
Input:
executeParallel(
[asyncTask(3), asyncTask(1), asyncTask(2)],
(result) => { console.log(result);});
Output:
// output in the order of execution
[2, 1, 3]
We can use the simple forEach loop on each task and execute them
parallel. We just need to use a tracker to determine the number of
tasks that have been finished so that when each task is executed, we
can invoke the callback function.
Invoking the callback function at the end of the loop won’t work as the
async tasks may finish at different intervals.
Test Case
To create an async task, we have created a function that accepts a
callback and runs a setTimeout for a random time and invokes this
callback inside the timeout.
function createAsyncTask() {
const value = Math.floor(Math.random() * 10);
return function(callback) {
setTimeout(() => {
callback(value);
}, value * 1000);
};
}
Using this we can create multiple tasks and then test the async
parallel.
Input:
const taskList = [
createAsyncTask(),
createAsyncTask(),
Output:
"results" // [object Array] (6)
[1,6,7,7,9,9]
Example
Input:
retry(asyncFn, retries = 3, delay = 50, finalError = 'Failed');
Output:
... attempt 1 -> failed
... attempt 2 -> retry after 50ms -> failed
... attempt 3 -> retry after 50ms -> failed
... Failed.
We will see two code examples one with thenable promise and the
second with async…await.
Let us first create the delay function so that we can take a pause for a
specified amount of time.
Delay function
We can create a delay function by creating a new promise and resolve
it after given time using setTimeout.
//delay func
const wait = ms => new Promise((resolve) => {
setTimeout(() => resolve(), ms);
});
Implementation
const retryWithDelay = (
operation,
retries = 3,
delay = 50,
finalErr = 'Retry failed'
) => new Promise((resolve, reject)
=> {
return operation()
.then(resolve)
.catch((reason) => {
//if retries are left
if (retries > 0) {
//delay the next call
return wait(delay)
//recursively call the same function
//to retry with max retries - 1
.then(
retryWithDelay.bind(
null,
operation,
retries - 1,
delay,
finalErr)
)
.then(resolve)
.catch(reject);
}
Test Case
To test we can create a test function that keeps throwing errors if
called less than 5 times. So if we call it 6 or more times, it will pass.
Input:
// Test function
const getTestFunc = () => {
let callCounter = 0;
Output:
"success" // 1st test
"Retry failed" //2nd test
Implementation
const retryWithDelay = async (
fn, retries = 3, interval = 50,
finalErr = 'Retry failed'
) => {
try {
// try
await fn();
} catch (err) {
// if no retries left
// throw error
if (retries <= 0) {
return Promise.reject(finalErr);
}
Test Case
Input:
// Test function
const getTestFunc = () => {
let callCounter = 0;
return async () => {
callCounter += 1;
// if called less than 5 times
// throw error
if (callCounter < 5) {
throw new Error('Not yet');
}
Output:
"success" // 1st test
"Retry failed" //2nd test
Alternatively, you can also update the code and keep on retrying the
API call based on some test.
Example
Input:
let numPromise = mapSeries([1, 2, 3, 4, 5], function (num, callback) {
// i am async iteratee function
// do async operations here
setTimeout(function () {
num = num * 2;
console.log(num);
// throw error
if(num === 12){
callback(true);
}else{
callback(null, num);
}
}, 1000);
});
numPromise
.then((result) => console.log("success:" + result))
.catch(() => console.log("no success"));
We will return a new promise and inside this promise, iterate each
input value in the series using the Array.reduce().
// throw error
if(num === 12){
}, 1000);
});
numPromise
.then((result) => console.log("success:" + result))
.catch(() => console.log("no success"));
Output:
// each number will be printed after a delay of 2 seconds
2
4
6
8
10
"success:2,4,6,8,10" // this will be printed immediately after last
// throw error
if(num === 8){
callback(true);
}else{
callback(null, num);
}
}, 2000);
});
numPromise
.then((result) => console.log("success:" + result))
.catch(() => console.log("no success"));
Example
Input:
let numPromise = mapLimit([1, 2, 3, 4, 5], 3, function (num, callback) {
numPromise
.then((result) => console.log("success:" + result))
.catch(() => console.log("no success"));
Output:
/// first batch
2
4
6
/// second batch
● First chop the input array into the subarrays of the given limit.
This will return us an array of arrays like [[1, 2, 3], [4, 5]].
● The parent array will run in series, that is the next subarray will
execute only after the current subarray is done.
● All the elements of each sub-array will run in parallel.
● Accumulate all the results of each sub-array element and resolve
the promise with this.
● If there is any error, reject.
//output
const output = [];
let i = 0;
return output;
};
if(error){
reject(error);
}else{
results.push(value);
});
}, Promise.resolve([]));
numPromise
.then((result) => console.log("success:" + result))
.catch(() => console.log("no success"));
Output:
// first batch
2
// throw error
if(num === 6){
callback(true);
}else{
callback(null, num);
}
}, 2000);
});
numPromise
.then((result) => console.log("success:" + result))
.catch(() => console.log("no success"));
Output:
// first batch
2
4
6
"no success"
The inputs will run in parallel, but the output will be in the same order
as the original.
Example
Input:
let numPromise = filter([1, 2, 3, 4, 5], function (num, callback) {
setTimeout(function () {
num = num * 2;
console.log(num);
// throw error
if(num === 7){
callback(true);
}else{
callback(null, num !== 4);
}
}, 2000);
});
numPromise
.then((result) => console.log("success:" + result))
.catch(() => console.log("no success"));
Output:
To implement this we will create a new promise and return it, inside
this promise, run all the input values in parallel using the
Array.forEach() and inside the forEach pass each value to the iteratee
function.
Inside the iteratee function’s callback if the result is true which means
the input has passed the test, then add that input to the result at the
current index.
In the end, if we are at the last element of the input array, resolve the
promise with the result.
Filter the final output to remove the gaps as we have to maintain the
order for the passed values. There will be no value at the indexes of
unpassed values in the output array.
arr.forEach((e, i) => {
fn(e, (error, result) => {
// reject on error
if(error){
});
};
Test Case
Input:
let numPromise = filter([1, 2, 3, 4, 5], function (num, callback) {
setTimeout(function () {
num = num * 2;
console.log(num);
// throw error
if(num === 7){
callback(true);
}else{
callback(null, num !== 4);
}
}, 2000);
});
Output:
2
4
6
8
10
"success:1,3,4,5"
The inputs will run in parallel, but the output will be in the same order
as the original.
Example
Input:
let numPromise = reject([1, 2, 3, 4, 5], function (num, callback) {
setTimeout(function () {
num = num * 2;
console.log(num);
// throw error
if(num === 7){
callback(true);
}else{
callback(null, num !== 4);
}
}, 2000);
});
numPromise
.then((result) => console.log("success:" + result))
.catch(() => console.log("no success"));
We can use the exact same implementation of the Async filter, only
changing the logic to add the value to the output array only when it
fails the test.
arr.forEach((e, i) => {
fn(e, (error, result) => {
// reject on error
if(error){
reject(error);
}
});
};
Test Case
let numPromise = reject([1, 2, 3, 4, 5], function (num, callback) {
setTimeout(function () {
num = num * 2;
console.log(num);
// throw error
if(num === 7){
callback(true);
}else{
callback(null, num !== 4);
}
}, 2000);
});
numPromise
.then((result) => console.log("success:" + result))
.catch(() => console.log("no success"));
Output:
2
4
6
8
10
"success:2"
Example
const promises = [
{status: 'resolve', priority: 4},
{status: 'reject', priority: 1},
{status: 'resolve', priority: 2},
{status: 'reject', priority: 3}
];
resolvePromisesWithPriority(promises);
// {status: 'resolve', priority: 2}
In the end, if any promise resolves and it is of most priority, resolve it,
else if all the promises fail then reject with the custom error.
To track if all the promises are finished, we will use another variable
named taskCompleted.
// if all the tasks are finished and none of them have been
resolved
// reject with custom error
if(taskCompleted === promises.length){
reject("All Apis Failed");
}
});
});
});
};
Test Case
Input:
// create a promise that rejects or resolves
// randomly after some time
function createAsyncTask() {
const value = Math.floor(Math.random() * 10);
return new Promise((resolve, reject) => {
setTimeout(() => {
if(value < 7){
reject();
}else{
resolve(value);
}
}, value * 100);
});
};
const promises = [
{task: createAsyncTask(), priority: 1},
{task: createAsyncTask(), priority: 4},
{task: createAsyncTask(), priority: 3},
{task: createAsyncTask(), priority: 2}
];
Output:
/*
// log
// rejected promise as per their priority
{
"1": true,
"3": true,
"4": true
}
// log
// resolve promise as per their priority
2
*/
In the dependencies list, we will check if all the tasks in the list are
completed or not, if it is completed then filter them out as we no
longer need to run them.
Use a flag to determine the state of the task, i.e completed or not.
class Task {
// accept the dependencies list
// and the callback
constructor(dependencies = [], job) {
// filter the dependencies that are not yet completed
this.dependencies = dependencies ? dependencies.filter(dependency
=> dependency instanceof Task && !dependency.isCompleted) : [];
this.currentDependencyCount = this.dependencies.length;
// the callback
this.job = job;
processJob() {
// if there is dependency
// subscribe to each of them
if (this.dependencies && this.dependencies.length) {
for (let dependency of this.dependencies) {
dependency.subscribe(this.trackDependency.bind(this));
}
}
// else invoke the callback directly
else {
this.job(this.done.bind(this));
}
}
Test Case
Input:
const processA = new Task(null, (done) => {
setTimeout(() => {
console.log('Process A');
done();
}, 100);
});
createAllDoneInstance((done) => {
console.log('All is done!');
done();
});
Output:
"Process A"
"Process C"
"Process B"
"Process D"
"Process E"
"All is done!"
It is one of the classic problems which use two of the trickiest concepts
of JavaScript.
1. Timers.
2. Closure.
Stop function
Inside the stop function we stop the increment by invoking the
clearInterval by passing the intervalId to it and also updating the
intervalId to null.
const stopTimer = () => {
clearInterval(intervalId);
intervalId = null;
}
return {
startTimer,
stopTimer,
};
}
Test Case
Input:
const timerObj = timer(10, 10);
//start
timerObj.startTimer();
//stop
setTimeout(() => {
timerObj.stopTimer();
}, 5000);
Output:
10
20
30
40
But as we are using stack rather than array or linked list, either one of
the operations will be running in O(n) time. It is up to us to decide
which way we want to proceed.
Then again copy all the items back from stack2 to stack1.
This way we keep FIFO principal in place by keeping the first element
at front and last element at end.
this.stack2.push(x);
while(!this.stack2.isEmpty()){
this.stack1.push(this.stack2.pop());
}
}
dequeue = () => {
return this.stack1.pop();
}
peek = () => {
return this.stack1.peek();
}
size = () => {
return this.stack1.size();
}
isEmpty = () => {
return this.stack1.isEmpty();
}
clear = () => {
this.stack1 = new Stack();
this.stack2 = new Stack();
}
}
Output:
10
10
20
20
30
This is the most efficient way because once we have stored the data in
an organized way all other operations work perfectly, we don’t need to
change them.
And then copy all the elements back to the first stack.
The only problem with this way is that for the peek operation we have
to do the same process, this makes two operations run in O(n) time
which makes it highly inefficient.
Implementation
class QueueUsingStack {
constructor() {
this.stack1 = new Stack();
this.stack2 = new Stack();
}
dequeue = () => {
while(!this.stack1.isEmpty()){
this.stack2.push(this.stack1.pop());
}
while(!this.stack2.isEmpty()){
this.stack1.push(this.stack2.pop());
}
return item;
}
peek = () => {
while(!this.stack1.isEmpty()){
this.stack2.push(this.stack1.pop());
}
while(!this.stack2.isEmpty()){
this.stack1.push(this.stack2.pop());
}
return item;
}
isEmpty = () => {
return this.stack1.isEmpty();
}
clear = () => {
this.stack1 = new Stack();
this.stack2 = new Stack();
}
}
Test Case
Input:
const queue = new QueueUsingStack();
queue.enqueue(10);
queue.enqueue(20);
queue.enqueue(30);
queue.enqueue(40);
queue.enqueue(50);
console.log(queue.peek());
console.log(queue.dequeue());
console.log(queue.peek());
console.log(queue.dequeue());
console.log(queue.peek());
Output:
10
10
20
20
30
Approach
● Every time we will add new data into the queue, we will move the
existing data behind new data by repeatedly removing the first
data and pushing it after at the end of the queue.
● This way we will be able to mimic the stack implementation
using the queue operations.
Implementation
function Stack() {
let queue = new Queue();
//Push
this.push = function(elm){
let size = queue.size();
queue.enqueue(elm);
//Pop
this.pop = function(){
if(queue.isEmpty()){
return null;
}
return queue.dequeue();
}
return queue.front();
}
//Size
this.size = function(){
return queue.size();
}
//IsEmpty
this.isEmpty = function(){
return queue.isEmpty();
}
//Clear
this.clear = function(){
queue.clear();
return true;
}
//ToArray
this.toArray = function(){
return queue.toArray();
}
}
Test Case
Input:
let stack = new Stack(); //creating new instance of Stack
stack.push(1);
stack.push(2);
stack.push(3);
console.log(stack.peek());
console.log(stack.isEmpty());
Output:
3
false
3
3
[2, 1]
2
true
Example
Input:
2 5 17 23 88 54 1 22
Output:
max: 88
min: 1
Approach
● Instead of storing a single value in the stack we will store an
object with current, max and min values.
● While adding the new value in the stack we will check if the stack
is empty or not.
● If it is empty then add the current value as current, max and min
values.
● Else get the previous value and compare it with the current item,
if it is greater than the existing then replace the max, If it is less
than the existing then replace the min.
Implementation
function stackWithMinMax(){
let items = [];
let length = 0;
Test Case
Input:
let SM = new stackWithMinMax();
SM.push(4);
SM.push(7);
SM.push(11);
SM.push(23);
SM.push(77);
SM.push(3);
SM.push(1);
SM.pop();
console.log(`max: ${SM.max()}`, `min: ${SM.min()}`);
Output:
"max: 77" "min: 3"
Example
Input:
let stack = new twoStacks(10);
Output:
"stack1"
"stack2"
The concept we use here is we store the data on the two different ends
in the array (from start and from end).
The first stack stores the data from the front that is at the index 0 and
the second stack stores the data from the end that is the index size-1.
Both stack push and pop data from opposite ends and to prevent the
overflow we just need to check if there is space in the array.
Implementation
class twoStacks {
//Push in stack1
push1 = (elm) => {
//Check if there is space in array
//Push at the start of the array
if(this.top1 < this.top2 - 1){
this.arr[++this.top1] = elm;
}else{
console.log('Stack overflow');
return false;
}
}
//Push in stack2
push2 = (elm) => {
//Check if there is space in array
//Push at the end of the array
if(this.top1 < this.top2 - 1){
this.arr[--this.top2] = elm;
}else{
console.log('Stack overflow');
return false;
}
}
Test Case
Input:
let stack = new twoStacks(10);
//push in first stack
stack.push1('stack1');
Output:
"stack1"
"stack2"
Implementation
function PriorityQueue(){
let items = [];
//Container
function QueueElement(element, priority){
this.element = element;
this.priority = priority;
}
Test Case
Input:
let pQ = new PriorityQueue();
pQ.enqueue(1, 3);
pQ.enqueue(5, 2);
pQ.enqueue(6, 1);
pQ.enqueue(11, 1);
pQ.enqueue(13, 1);
pQ.enqueue(10, 3);
pQ.dequeue();
pQ.print();
Output:
"10 - 3"
"5 - 2"
"6 - 1"
"11 - 1"
"13 - 1"
In order to add a new element to this (say 5), we will have to first
remove the least recently used which in this case is 1.
● get(key): Returns the cache value for the given item / page number.
● put(key, val): Adds a new value in the cache.
● use(key): Uses one of the existing values and re-arranges the cache by
marking the used one as most recently one.
● evict(): Removes a value from the cache.
● Insert(key, val): A helper function to add value in cache while
performing put
Base Function
class Node {
constructor(key, val) {
this.key = key;
this.val = val;
this.prev = null;
this.next = null;
}
}
Use(Key)
● First get the queue node of the key from the hashmap.
● Then rearrange the doubly linked list to push the current node at the
end or tail marking it as most recently used.
//Uses the cache with given key and marks it as most recently used
node.next = this.head;
node.prev = null;
this.head.prev = node;
this.head = node;
}
};
Evict
● First get the queue node from the hashmap and then remove this
node from it and re-arrange all the nodes.
● Then delete it from the hashmap as well.
if (!this.tail) {
return;
} else if (this.head === this.tail) {
if (keyToEvict) {
this.count--;
this.cache.delete(keyToEvict);
}
};
Insert(key, val)
Adds a new element at the appropriate position in the queue and also
inserts it in the hashmap.
//Helper function to add new cache in the queue
this.insert = function(key, val) {
const node = new Node(key, val);
this.count++;
this.cache.set(key, node);
if (!this.head) {
this.head = node;
this.tail = node;
} else {
this.head.prev = node;
node.next = this.head;
this.head = node;
}
};
Put(key, val)
● If the key already exists then use it and mark it as the most recent
one.
● If the capacity is exceeded then remove the least recent one and then
insert the new key.
this.insert(key, val);
this.use(key); // may not be needed
}
};
Get(key)
Returns the queue node of the associated key.
//Returns the value of the given key
this.get = function(key) {
if (!this.cache.has(key)) {
return -1;
}
Display()
Prints all the items of the queue in the least to most order along with its
value.
this.insert(key, val);
this.use(key); // may not be needed
}
};
//Uses the cache with given key and marks it as most recently used
this.use = function(key) {
const node = this.cache.get(key);
node.next = this.head;
node.prev = null;
this.head.prev = node;
this.head = node;
}
};
if (!this.tail) {
if (keyToEvict) {
this.count--;
this.cache.delete(keyToEvict);
}
};
if (!this.head) {
this.head = node;
this.tail = node;
} else {
this.head.prev = node;
node.next = this.head;
this.head = node;
}
};
Output:
//LRU
4 "d"
3 "c"
2 "b"
1 "a"
//After using 2
2 "b"
4 "d"
3 "c"
1 "a"
Implementation
const debounce = (func, delay) => {
// 'private' variable to store the instance
// in closure each timer will be assigned to it
let inDebounce;
// base case
// clear the timeout to assign the new timeout to it.
// when event is fired repeatedly then this helps to reset
clearTimeout(inDebounce);
// set the new timeout and call the original function with apply
inDebounce = setTimeout(() => func.apply(context, args), delay);
Explanation
We created a function that will return a function. The outer function
uses a variable to keep track of timerId for the execution of the inner
function.
The inner function will be called only after a specified window of time,
to achieve this we use setTimeout function.
If we are invoking for the first time, our function will execute at the
end of our delay. If we invoke and then reinvoke again before the end
of our delay, the delay restarts.
Test Case
Input:
// print the mouse position
const onMouseMove = (e) => {
console.clear();
console.log(e.x, e.y);
}
Output:
300 400
This flag will be optional which means if it is set to false then the
debounce function will behave normally.
Implementation
const debounce = (func, wait, immediate) => {
// 'private' variable to store the instance
// in closure each timer will be assigned to it
let timeout;
// base case
// clear the timeout to assign the new timeout to it.
// when event is fired repeatedly then this helps to reset
clearTimeout(timeout);
Explanation
We use a private variable to store the timerId, and return a closure
from it which will execute the callback function after debouncing.
Inside the closure, store the context of the current function and its
arguments and pass it later to the callback function while executing it
with the help of apply method.
Then assign the timer and execute the function only when the
immediate flag is false.
Test Case
Input:
// print the mouse position
const onMouseMove = (e) => {
console.clear();
console.log(e.x, e.y);
}
Output:
314 419
If the user spam’s the click then this will make an API call on each
click. This is not what we want, we want to restrict the number of API
calls that can be made. The other call will be made only after a
specified interval of time.
Implementation
const throttle = (func, limit) => {
// track the timerid and time
let lastFunc;
let lastRan;
return function() {
// capture the context and arguments
const context = this;
const args = arguments;
// start it again
lastFunc = setTimeout(function() {
if ((Date.now() - lastRan) >= limit) {
func.apply(context, args);
lastRan = Date.now();
}
}, limit - (Date.now() - lastRan));
}
}
}
Test Case
Input:
const print = () => {
console.log("hello");
}
Output:
// "hello" at the beginning
// "hello" after 2500 millisecond
// "hello" after 2500 millisecond
return false;
}
function R() {}
console.log(instanceOf(q, R)) // false
R.prototype = Q.prototype
console.log(instanceOf(q, R)) // true
R.prototype = {}
console.log(instanceOf(q, R)) // false
Test Case
Input:
class P {}
class Q extends P {}
function R() {}
function A() {
if ( (this instanceof arguments.callee) ) {
console.log("OK, new");
} else {
console.log("OK, function");
}
}
Test Case
Input:
var Z = new A();
Z.lolol = A;
Z.lolol();
Output:
// OK, new
To solve this we will need some additional checks along with the
instanceOf method.
Test Case
Input:
A(); // Ok, function
new A(); // OK, new
new A(); // Ok, new
var Y = A;
Y(); // OK, function
var y = new Y(); // OK, new
y.lolol = Y;
y.lolol(); // OK, function
Output:
"OK, function"
"OK, new"
"OK, new"
"OK, function"
"OK, new"
"OK, new"
"OK, function"
As per MDN –
The new.target pseudo-property lets you detect whether a function or
constructor was called using the new operator. In constructors and
functions invoked using the new operator, new.target returns a
reference to the constructor or function. In normal function calls,
new.target is undefined.
function A(B) {
if( new.target ) {
console.log('OK, new');
} else {
console.log('OK, function');
}
}
Test Case
Input:
A(); // Ok, function
new A(); // OK, new
new A(); // Ok, new
var Y = A;
Y(); // OK, function
Output:
"OK, function"
"OK, new"
"OK, new"
"OK, function"
"OK, new"
"OK, new"
"OK, function"
"OK, function"
"OK, new"
"OK, function"
Example
const store = new Store();
store.set('a', 10);
store.set('b', 20);
store.set('c', 30);
store.get('b'); // 20
store.has('c'); // true
Test Case
Input:
const store = new Store();
store.set('a', 10);
store.set('b', 20);
store.set('c', 30);
console.log(store.get('b'));
console.log(store.has('c'));
console.log(store.get('d'));
console.log(store.has('e'));
Output:
20
true
undefined
false
Example
let hello = toggle("hello");
hello() // "hello";
hello() // "hello";
The toggle function returns each value clockwise on each call of the
function and the same can be done by returning a function from the
toggle function forming a closure over the values to track the cycle.
Implementation
const toggle = (...list) => {
// to track the cycle
let current = -1;
const length = list.length;
return function(){
//moves to next element, resets to 0 when current > length
current = (current + 1) % length;
return list[current];
}
}
Test Case
const hello = toggle("1", "2");
console.log(hello()); // "1"
console.log(hello()); // "2"
console.log(hello()); // "1"
Example
function message(){
console.log("hello");
}
Implementation
function sampler(fn, count, context){
let counter = 0;
return function(...args){
fn.apply(context, args);
counter = 0;
};
}
Test Case
function message(){
console.log("hello");
}
● C or PHP: sleep(2)
● JAVA: Thread.sleep(2000)
● Python: time.sleep(2)
● Go: time.Sleep(2 * time.Second)
Javascript does not have any inbuilt function for this, But thanks to
the introduction of promises and async-await in ES2018, we can now
implement such a feature without any hassle and use it seamlessly.
Implementation
const sleep = (milliseconds) => {
return new Promise(resolve => setTimeout(resolve, milliseconds))
};
This will create a wrapper function which will resolve the promise
after the given milliseconds.
performAction();
This works well, however due to how javascript works this does not
stop the entire program execution like it does in other programming
languages, instead it will just make our current function sleep.
Example
Input:
const List = function(val){
this.next = null;
this.val = val;
};
item1.next = item2;
item2.next = item3;
item3.next = item1;
// this form a cycle, if you console.log this you will see a circular
object,
// like, item1 -> item2 -> item3 -> item1 -> so on.
Output:
// removes cycle
// item1 -> item2 -> item3
If you see the above example, we have created a list object, that accepts
a value and pointer to the next item in the list, similar to a linked list,
and using this we have created the circular object.
We have to create a function that will break this cycle, in this example
to break the cycle we will have to delete the next pointer of the item3.
Normal use
We can use WeakSet which is used to store only unique object
references and detect if the given object was previously detected or
not, if it was detected then delete it.
item1.next = item2;
item2.next = item3;
item3.next = item1;
removeCycle(item1);
console.log(item1);
Output:
/*
{val: 10, next: {val: 20, next: {val: 30}}}
*/
We can use the same function to detect and remove the cycle from the
object.
const getCircularReplacer = () => {
//form a closure and use this
//weakset to monitor object reference.
const seen = new WeakSet();
Test Case
Input:
const List = function(val){
this.next = null;
this.val = val;
};
item1.next = item2;
item2.next = item3;
item3.next = item1;
console.log(JSON.stringify(item1, getCircularReplacer()));
Output:
"{'next':{'next':{'val':30},'val':20},'val':10}"
Example
Input:
const arr = [[1, [2, [3, 'foo', {'a': 1, 'b': 2}]], 'bar']];
const filtered = filter(arr, (e) => typeof e === 'string');
console.log(JSON.stringify(filtered));
Output:
[[[["foo"]],"bar"]]'
Test Case
Input:
const arr = [[1, [2, [3, "foo", { a: 1, b: 2 }]], "bar"]];
const filtered = filter(arr, (e) => typeof e === "number");
console.log(JSON.stringify(filtered));
Output:
[[1,[2,[3]]]]
//original array;
const originalArray = this;
Test Case
Input:
const arr = [[1, [2, [3, "foo", { a: 1, b: 2 }]], "bar"]];
const filtered = arr.multiFilter((e) => typeof e === "number");
console.log(JSON.stringify(filtered));
Output:
[[1,[2,[3]]]]
Example
Input:
const arr = [[1, [2, [3, 4, "foo", { a: 1, b: 2 }]], "bar"]];
const count = countInArray(arr, (e) => typeof e === "number");
console.log(count);
Output:
4
//if sub-array
if (Array.isArray(a)) {
//recursively filter the sub-array
search(a, test);
}
}
};
//search
search(inputArr, test);
//return
return count;
};
Test Case
Input:
const arr = [[1, [2, [3, 4, "foo", { a: 1, b: 2 }]], "bar"]];
const count = countInArray(arr, (e) => typeof e === "number");
console.log(count);
Output:
4
Example
Input:
"#ff33ff"
Output:
{
"r": 255,
"g": 51,
"b": 255
}
There are multiple ways in which we can convert the HEXA color
codes to RGB numbers.
// return {r, g, b}
return { r, g, b };
}
console.log(hex2rgb("#ff33ff"));
/*
{
"r": 255,
"g": 51,
"b": 255
}
*/
In case we are given a short form of Hexa code like #f3f, we will have
to convert it to the original form.
r = parseInt(r+r, 16);
g = parseInt(g+g, 16);
b = parseInt(b+b, 16);
// return {r, g, b}
return { r, g, b };
}
// return {r, g, b}
return { r, g, b };
}
console.log(hex2rgb("#f3f"));
/*
{
"r": 255,
"g": 51,
"b": 255
}
*/
We can then use this array to parse the HEXA values to RGB.
return null;
}
console.log(hex2rgb("#ff33ff"));
/*
{
"r": 255,
"g": 51,
"b": 255
}
*/
console.log(hex2rgb("#f3f"));
/*
{
"r": 255,
"g": 51,
"b": 255
}
*/
Example
Input:
255, 51, 255
Output:
"#ff33ff"
//"#ff33ff"
//"#ff33ff"
//"#ff33ff"
Boilerplate
const FileSystem = function(){
this.directory = {"root": {}};
this.currentDir = this.directory["root"];
this.currentDirPath = "root";
};
this._changeDirectoryHelper = function(path) {
const paths = path.split("-");
let current = this.directory;
for(let key of paths){
current = current[key];
}
return current;
}
Add file
At the current path the files will be added to the key “files” as an array
making it easy to add and remove them.
this.addFile = function(fileName){
if(this.currentDir.files){
this.currentDir.files.push(fileName);
}else{
this.currentDir["files"] = [fileName];
}
return true;
}
Remove file
To remove files simply filter the files array at the current directory.
this.deleteFile = function(fileName){
this.currentDir.files = this.currentDir.files.filter((e) => e !==
fileName);
return true;
}
Complete code
const FileSystem = function(){
this.directory = {"root": {}};
this.currentDir = this.directory["root"];
this.currentDirPath = "root";
this.createDirectory = function(name){
this.currentDir[name] = {};
}
this.changeDirectory = function(path) {
this.currentDir = this._changeDirectoryHelper(path);
this.currentDirPath = path;
}
this._changeDirectoryHelper = function(path) {
const paths = path.split("-");
let current = this.directory;
for(let key of paths){
current = current[key];
}
return current;
}
this.getCurDirectoryPath = function(){
return this.currentDirPath;
}
this.addFile = function(fileName){
if(this.currentDir.files){
this.currentDir.files.push(fileName);
}else{
this.currentDir["files"] = [fileName];
}
return true;
}
this.deleteFile = function(fileName){
this.currentDir.files = this.currentDir.files.filter((e) => e !==
fileName);
return true;
}
this.deleteDirectory = function(name){
delete this.currentDir[name];
}
this.getRootDirectory = function(){
return this.directory;
}
}
Test Case
Input:
const dir = new FileSystem();
dir.createDirectory('prashant');
dir.changeDirectory('root-prashant');
dir.addFile('index.html');
dir.addFile('app.js');
dir.changeDirectory('root');
dir.createDirectory('practice');
dir.changeDirectory('root-practice');
dir.addFile('index.html');
dir.addFile('app.js');
dir.createDirectory('build');
Output:
{
"root": {
"practice": {
"files": [
"index.html",
"app.js"
],
"build": {
"files": [
"b.jpg"
]
}
}
}
}
Example
Input:
const z = new Stream();
z.subscribe((value) => console.log(value));
z.subscribe((value) => console.log(value * 2));
z.subscribe((value) => console.log(value * 3));
z.push(2);
Output:
2
4
6
subscribe will cache all the methods passed to it and in the push
method we will call all the subscription methods with the value
received as an argument.
class Stream {
constructor(){
this.subscriptions = [];
}
subscribe(method){
if(typeof method !== 'function' ){
throw new Error('Invalid method!.');
}
this.subscriptions.push(method);
}
push(val){
this.subscriptions.forEach((method) => {
method.call(this, val);
});
}
}
Test Case
Input:
const z = new Stream();
z.subscribe((value) => console.log(value));
z.subscribe((value) => console.log(value * 2));
z.subscribe((value) => console.log(value * 3));
z.push(2);
Output:
2
4
6
subscriptions.push(method);
}
Test Case
Input:
const z = new Stream();
z.subscribe((value) => console.log(value));
z.subscribe((value) => console.log(value * 2));
z.subscribe((value) => console.log(value * 3));
z.push(2);
Output:
2
4
6
Example
slowFunc(params) // normal call, slow output
We will create a closure with the higher-order function that will cache
the result for the given input. If a call is made and the result for the
input is stored in the cache then we will return it, otherwise, execute
the function and cache its result.
//else compute and store the result and return the result
const evaluatedValue = fn(...arguments);
cache[KEY] = evaluatedValue;
return evaluatedValue;
}
};
Test Case
Input:
function factorial(n) {
if(n === 0 || n === 1) {
return 1
}
return factorial(n-1) * n;
};
Output:
9.33262154439441e+157 // slow
9.33262154439441e+157 // faster
Example
calculator.add(10).subtract(2).divide(2).multiply(5);
console.log(calculator.total);
//20
const calculator = {
total: 0,
add: function(val){
this.total += val;
return this;
},
subtract: function(val){
this.total -= val;
return this;
Test Case
Input:
calculator.add(10).subtract(2).divide(2).multiply(5);
console.log(calculator.total);
Output:
20
We just have to return this keyword from each method, so that next
chaining can be done on the current function.
Test Case
Input:
const calculator = new CALC();
calculator.add(10).subtract(2).divide(2).multiply(5);
console.log(calculator.total);
Output:
20
Example
Input:
computeAmount().lacs(15).crore(5).crore(2).lacs(20).thousand(45).crore(7).v
alue();
Output:
143545000
this.crore = function(val){
this.store += val * Math.pow(10, 7);
return this;
};
this.lacs = function(val){
this.store += val * Math.pow(10, 5);
return this;
}
this.hundred = function(val){
this.store += val * Math.pow(10, 2);
return this;
}
this.ten = function(val){
this.store += val * 10;
return this;
}
this.unit = function(val){
this.store += val;
return this;
}
this.value = function(){
return this.store;
}
}
Test Case
Input:
const computeAmount = new ComputeAmount();
const amount =
computeAmount.lacs(15).crore(5).crore(2).lacs(20).thousand(45).crore(7).val
ue();
Output:
true
return {
store: 0,
crore: function(val){
this.store += val * Math.pow(10, 7);
return this;
},
lacs: function(val){
this.store += val * Math.pow(10, 5);
return this;
},
thousand: function(val){
this.store += val * Math.pow(10, 3);
return this;
},
hundred: function(val){
this.store += val * Math.pow(10, 2);
return this;
},
ten: function(val){
this.store += val * 10;
return this;
},
unit: function(val){
this.store += val;
return this;
},
Test Case
Input:
const amount =
ComputeAmount().lacs(9).lacs(1).thousand(10).ten(1).unit(1).value();
console.log(amount === 1010011);
const amount2 =
ComputeAmount().lacs(15).crore(5).crore(2).lacs(20).thousand(45).crore(7).v
alue();
console.log(amount2 === 143545000);
Output:
true
true
Example
Input:
setTimeout(() => {console.log("hello")}, 2000);
setTimeout(() => {console.log("hello1")}, 3000);
setTimeout(() => {console.log("hello2")}, 4000);
setTimeout(() => {console.log("hello3")}, 5000);
clearAllTimeout();
setTimeout(() => {console.log("hello4")}, 5000);
Output:
"hello4"
window.clearAllTimeout = function(){
//clear all timeouts
while(timeoutIds.length){
clearTimeout(timeoutIds.pop());
}
}
window.timeoutIds = [];
Complete code
window.timeoutIds = [];
window.clearAllTimeout = function(){
//clear all timeouts
while(timeoutIds.length){
clearTimeout(timeoutIds.pop());
}
}
clearAllTimeout();
If we test this, this runs as expected. It will clear all the timeouts, as
setTimeout is an Asynchronous function, meaning that the timer
function will not pause execution of other functions in the functions
stack, thus clearAllTimeout runs and cancels them before they can be
executed.
This way we won’t be interfering with existing methods and can still
get our work done.
const MY_TIMER = {
timeoutIds : [],//global timeout id arrays
//create a MY_TIMER's timeout
setTimeout : function(fn,delay){
let id = setTimeout(fn,delay);
this.timeoutIds.push(id);
return id;
},
Test Case
Input:
const id = MY_TIMER.setTimeout(() => {console.log("hello")}, 1000);
console.log(id);
MY_TIMER.clearAllTimeout();
Output:
13 //timeoutId
Example
Input:
setInterval(() => {
console.log("Hello");
}, 2000);
setInterval(() => {
console.log("Hello2");
}, 500);
setInterval(() => {
console.log("Hello3");
}, 1000)
Output:
"Hello3" // last one, after every ~1 sec
First, to clear all the intervals at once, we need to store all of their ids
so that they can be cleared one by one using the clearInterval method.
Test Case
Input:
setInterval(() => {
console.log("Hello");
}, 2000);
setInterval(() => {
console.log("Hello2");
}, 500);
clearAllInterval();
setInterval(() => {
console.log("Hello3");
Output:
"Hello3" // after every ~1 sec
One thing you could do over here is to wrap these inside a closure or
higher-order function or an Object to keep it restricted.
This way we won’t be interfering with existing methods and can still
get our work done.
const MY_TIMER = {
intervalIds : [],//global interval id's arrays
//create a MY_TIMER's interval
setInterval : function(fn,delay){
let id = setInterval(fn,delay);
this.intervalIds.push(id);
return id;
},
//MY_TIMER's clearAllTimeout
clearAllInterval : function(){
while(this.intervalIds.length){
clearTimeout(this.intervalIds.pop());
}
}
};
Test Case
Input:
MY_TIMER.setInterval(() => {
console.log("Hello");
}, 2000);
MY_TIMER.clearAllInterval();
MY_TIMER.setInterval(() => {
console.log("Hello3");
}, 1000);
Output:
"Hello3" // last one, after every ~1 sec
Example
MY_TIMER.setTimeout(() => {
console.log(1)
}, 2500);
MY_TIMER.setTimeout(() => {
console.log(2)
}, 2000);
MY_TIMER.run();
Output:
2 // will be printed after 2 seconds
1 // will be printed 500 milliseconds after the 1st
In the setTimeout, store each entry in the queue, for the delay, add the
input to the current time to determine when it should be invoked. Also
after each entry, sort the queue in ascending order based on the time.
Return a unique id at the end.
Using the timer id, we can remove the entry from the queue in
clearTimeout.
Do it for all the entries in the queue. Add a condition to check if there
are no more timers (the queue is empty) to run then break the loop.
const MY_TIMER = {
timerId: 1,
queue: [],
// create a new timer
setTimeout: function(cb, time, ...args){
const id = this.timerId++;
// return the id
return id;
},
Test Case
Input:
MY_TIMER.setTimeout(() => {
console.log(1)
}, 2500);
MY_TIMER.setTimeout(() => {
console.log(2)
}, 2000);
MY_TIMER.setTimeout(() => {
console.log(3)
}, 2500);
MY_TIMER.run();
Output:
2
1
3
4
Example
//normal function
sum(1, 2, 3)
//should return 6
//currying function
sum(1)(2)(3)
//should return 6
If you notice in the currying function for each function call sum(1) we
are returning a function which accepts the next argument sum(1)(2)
and it again returns a function which accepts argument sum(1)(2)(3)
and so on.
//variations of currying
sum(1)(2)(3)
sum(1, 2)(3)
Now you may be wondering that each function call returns a new
function then how the value is returned from it?. Well for that we have
to decide a base condition that should return the output.
sum(1)(2)(3)()
sum(1, 2)(3)()
sum(1)(2, 3)()
sum(1, 2, 3)()
//OR
//when we reach 5 arguments then return the value rather than new function
sum(1, 2, 3, 4, 5)
sum(1, 2)(3, 4, 5)
sum(1)(2, 3, 4, 5)
sum(1, 2, 3)(4, 5)
sum(1)(2)(3)(4)(5)
sum(1, 2, 3, 4)(5)
Problem Statement -
Implement a currying function for 4 arguments. When we have
reached the limit, return the value.
When the function is invoked in normal style sum(1, 2, 3, 4). In this all
we have to do is check the number of arguments passed, if it is the
same as the limit provided return the sum of them.
//base case
if(storage.length === 4){
return storage.reduce((a, b) => a + b, 0);
}
}
...args is the rest operator which aggregates all the passed arguments
as an array.
The purpose of using the variable is that, when the arguments passed
is less than the limit then we will use this further to store the next
argument in the closure.
//base case
//if we have reached the limit
if(storage.length === 4){
return storage.reduce((a, b) => a + b, 0);
}
//otherwise return a function
else{
//create an inner function
const temp = function(...args2){
//get the arguments of inner function
//merge them in existing storage
storage.push(...args2);
If we have reached the limit return the sum of them otherwise return
the same function again.
Test Case
Input:
const res = sum(1, 2, 3, 4);
const res2 = sum(1)(2)(3)(4);
const res3 = sum(1, 2)(3, 4);
const res4 = sum(1, 2, 3)(4);
const res5 = sum(1)(2, 3, 4);
Output:
10 10 10 10 10
//base case
Test Case
Input:
const res = sum(1, 2, 3, 4)();
const res2 = sum(1)(2)(3)(4)();
const res3 = sum(1, 2)(3, 4)();
const res4 = sum(1, 2, 3)(4)();
const res5 = sum(1)(2, 3, 4)();
const res6 = sum();
Output:
10 10 10 10 10 0
Example
sum(5); // 5
sum(3); // 8
sum(4); // 12
sum(0); // 12
So to create a function that will return the sum of the previous values
in javascript we will use this technique of closure.
Test Case
//Returns and stores the inner function.
let sum = curry();
console.log(sum(5)); //5
console.log(sum(3)); //8
console.log(sum(4)); //12
console.log(sum(0)); //12
console.log(sum()); //12
This is a little tricky question and requires us to use and modify the
valueOf() method.
Example
function MyNumberType(number) {
this.number = number;
}
MyNumberType.prototype.valueOf = function () {
return this.number + 1;
};
Thus we can form a closure and track the arguments in an Array and
return a new function everytime that will accept new arguments.
We will also override the valueOf() method and return the sum of all
the arguments for each primitive action, also add a new method
function add(...current){
// store the current arguments
let sum = current;
function resultFn(...rest){
// merge the new arguments
sum = [...sum, ...rest];
return resultFn;
}
Test Case
Input:
console.log(add(1)(2).value() == 3);
console.log(add(1, 2)(3).value() == 6);
console.log(add(1)(2)(3).value() == 6);
console.log(add(1)(2) + 3);
Output:
true
true
true
6
Example
Input:
"12:10AM"
"12:33PM"
Output:
00:10
12:33
Split the time into hours and minutes to easily convert them
accordingly based on which period they belong to.
// Special case
Test Case
Input:
console.log(formatTime("12:10AM"));
console.log(formatTime("12:33PM"));
Output:
00:10
12:33
Example
Input:
"00:00"
"12:33"
Output:
"12:00 AM"
"12:33 PM"
We will split the input string on “:” and get the hour and minutes.
Check if the hour is greater than 12 then reset the hour minusing 12
from it and set time Meridiem to “PM”.
// default is AM
let ampm = 'AM';
Test Case
Input:
console.log(formatTime("12:33"));
console.log(formatTime("00:33"));
Output:
"12:33 PM"
"12:33 AM"
Example
10:57:23
10:57:24
10:57:25
10:57:26
10:57:27
We will create a function which will be using these methods to get the
current time in HH:MM:SS format.
const clock = () => {
const time = new Date(),
hours = time.getHours(),
minutes = time.getMinutes(),
seconds = time.getSeconds();
return pad(hours) + ':' + pad(minutes) + ':' + pad(seconds);
We are using this pad() helper function which will format the input by
appending 0 if the number is a single digit.
const pad = (inp) => {
return String(inp).length == 1 ? '0' + inp : inp;
};
Now when we call the clock function it will return the single instance
of time at the moment it was called.
console.log(clock());
//10:59:23
But to make it work like a clock we will need to call this function
repeatedly after 1 second. For this we will use the setInterval function
which repeatedly calls the function after a given interval of time.
setInterval(function() {
console.log(clock());
}, 1000);
//10:59:23
//10:59:24
//10:59:25
//10:59:26
Example
Input:
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
3
Output:
[[1,2,3], [4,5,6], [7,8,9], [10]]
1. A normal function that will take the input and return the output.
2. We will extend the JavaScript array and add a new method chop,
which will do the same.
Normal function
We will traverse till there is an element in the array, in each iteration
slice the sub-array of the given size and push them to the output array.
//output
const output = [];
let i = 0;
return output;
}
Test Case
Input:
console.log(chop([1,2,3,4,5,6,7,8,9,10], 3));
Output:
[[1,2,3], [4,5,6], [7,8,9], [10]]
Deep copy all the elements of the array so that we don’t mutate the
original array and if the size is not defined then return this deep copy,
else return the array of chunks.
Array.prototype.chop = function(size){
//temp array
const temp = [...this];
//output
const output = [];
let i = 0;
return output;
}
Test Case
Input:
const arr = [1,2,3,4,5,6,7,8,9,10];
const output = arr.chop(3);
console.log(output);
Output:
[[1,2,3], [4,5,6], [7,8,9], [10]]
Example
Input:
'javascript'
3
Output:
['jav','asc','rip','t']
1. Bruteforce.
2. Regex.
Bruteforce approach
It is extremely straightforward, iterate each character of the string and
keep on slicing the characters of the given size and pushing it into the
output array.
Test Case
Input:
console.log(chop('javascript', 3));
Output:
["jav","asc","rip","t"]
Using Regex
We often tend to ignore the Regex based solution as it is not easy to
remember the expressions, but the Regex method accepts the size
which can be used to extract at-most n-sized sub strings.
Regex expression
str.match(/.{1,n}/g); // Replace n with the size of the substring
If the string contains any newlines or carriage returns, then use this
expression.
Test Case
Input:
Output:
["jav","asc","rip","t"]
Example
Input:
{
A: "12",
B: 23,
C: {
P: 23,
O: {
L: 56
},
Q: [1, 2]
}
}
Output:
{
"A": "12"
"B": 23,
"C.O.L": 56,
"C.P": 23,
"C.Q.0": 1,
"C.Q.1": 2,
}
In the output if you notice, when we have nested objects, the key is
concatenated till there is a non-object value, similar for the array, the
key is concatenated on the index.
//new key
const newKey = prefix ? prefix + "." + k : k;
return output;
}
Test Case
Input:
const nested = {
A: "12",
B: 23,
C: {
P: 23,
O: {
L: 56
},
Q: [1, 2]
}
};
console.log(flatten(nested));
Output:
{
"A": "12"
"B": 23,
"C.O.L": 56,
"C.P": 23,
"C.Q.0": 1,
"C.Q.1": 2,
}
We will see two different ways in which we can achieve the same.
const obj = {
prop: 42
};
Object.seal(obj);
obj.prop = 33;
console.log(obj.prop);
// 33
const obj = {
prop: 42,
nested: {
a: 1,
b: 2
}
};
obj.nested.a = 2;
delete obj.nested.a;
console.log(obj.nested.a);
// undefined
However, we can create another helper function which will deep seal
or seal the nested objects as well.
function deepSeal(object) {
// Retrieve the property names defined on object
let propNames = Object.getOwnPropertyNames(object);
return Object.seal(object);
}
const obj = {
prop: 42,
nested: {
a: 1,
b: 2
}
};
obj.nested.a = 2;
delete obj.nested.a;
console.log(obj.nested.a);
// 2
const obj = {
prop: 42,
nested: {
a: 1,
b: 2
}
};
console.log(Object.isSealed(obj));
//true
const obj = {
prop: 42
};
Object.freeze(obj);
obj.prop = 33;
// Throws an error in strict mode
console.log(obj.prop);
// 42
But this also only shallowly freezes the nested object properties.
const obj = {
prop: 42,
nested: {
a: 1,
b: 2
}
};
Object.freeze(obj);
obj.nested.a = 33;
// Updates the value
console.log(obj.nested.a);
// 33
return Object.freeze(object);
}
const obj = {
prop: 42,
nested: {
a: 1,
b: 2
}
};
deepFreeze(obj);
obj.nested.a = 33;
// Updates the value
console.log(obj.nested.a);
// 1
const obj = {
prop: 42,
nested: {
a: 1,
b: 2
}
console.log(Object.isFrozen(obj));
//true
There are two different types of merge that can be performed on the
objects.
1. Shallow : In shallow merge only the properties owned by the
object will be merged, it will not merge the extended properties
or methods.
2. Deep : In deep merge properties and extended properties of the
objects are merged as well, if it exists.
Shallow merging
We will iterate all the keys of the source object and check if the
property belongs to the source object then only copy it to the target.
return target;
}
Test Case 1
Input:
let obj1 = {
name: 'prashant',
age: 23,
}
let obj2 = {
qualification: 'BSC CS',
loves: 'Javascript'
}
console.log(merged);
Output:
/*
Object {
age: 23,
loves: "Javascript",
name: "prashant",
qualification: "BSC CS"
}
*/
Test Case 2
In this if you notice the skills value is overridden by the last object.
Input:
let obj1 = {
name: 'prashant',
age: 23,
skills : {programming: 'JavaScript'}
let obj2 = {
qualification: 'BSC CS',
loves: 'Javascript',
skills : {sports: 'swimming'}
}
console.log(merged);
Output:
/*
Object {
age: 23,
loves: "Javascript",
name: "prashant",
qualification: "BSC CS",
skills: { sports: "swimming"}
}
*/
Deep merging
To deep merge an object we have to copy the own properties and
extended properties as well.
// Variables
let target = {};
return target;
};
Test Case
In this if you notice the nested objects with different values are
merged.
let obj1 = {
name: 'prashant',
age: 23,
nature: {
"helping": true,
"shy": false
}
}
let obj2 = {
qualification: 'BSC CS',
loves: 'Javascript',
nature: {
"angry": false,
"shy": true
}
}
console.log(merge(obj1, obj2));
/*
If we will pass true as first argument then it will perform deep merge
else it will perform shallow merge.
// Variables
let target = {};
let deep = false;
let i = 0;
return target;
};
Test Case
let obj1 = {
name: 'prashant',
age: 23,
nature: {
"helping": true,
"shy": false
}
}
let obj2 = {
qualification: 'BSC CS',
loves: 'Javascript',
nature: {
"angry": false,
"shy": true
}
}
//Shallow merge
console.log(merge(obj1, obj2));
//Deep merge
console.log(merge(true, obj1, obj2));
/*
Object {
age: 23,
loves: "Javascript",
name: "prashant",
nature: Object {
angry: false,
helping: true,
shy: true
},
qualification: "BSC CS"
}
*/
Example
Input:
const bh = new BrowserHistory();
bh.visit('A');
console.log(bh.current());
bh.visit('B');
console.log(bh.current());
bh.visit('C');
console.log(bh.current());
bh.goBack();
console.log(bh.current());
bh.visit('D');
console.log(bh.current());
Output:
"A"
"B"
"C"
"B"
"D"
We can implement this with the help of an array and index tracker for
navigation.
function BrowserHistory() {
// track history
this.history = [];
this.index = -1;
// go to previous entry
this.backward = function() {
this.index = Math.max(0, --this.index);
}
// go to next entry
this.forward = function() {
this.index = Math.min(this.history.length - 1, ++this.index);
}
}
Test Case
Input:
const bh = new BrowserHistory();
bh.visit('A');
console.log(bh.current());
bh.visit('C');
console.log(bh.current());
bh.backward();
console.log(bh.current());
bh.visit('D');
console.log(bh.current());
bh.backward();
console.log(bh.current());
bh.forward();
console.log(bh.current());
Output:
"A"
"B"
"C"
"B"
"D"
"B"
"D"
Example
const object1 = singleton.getInstance();
const object2 = singleton.getInstance();
function createInstance() {
const object = new Object("I am the instance");
return object;
}
return {
getInstance: function () {
if (!instance) {
instance = createInstance();
}
return instance;
Test Case
const object1 = singleton.getInstance();
const object2 = singleton.getInstance();
For example, when a click event is triggered you can access the event
object to get all the event details about the click like its position on the
screen, etc.
You can also remove the listener (unsubscribe) to stop listening if you
want.
2. Observer
Test Case
Input:
// 1st observer
const moveHandler = function (item) {
console.log("fired: " + item);
// 2nd observer
const moveHandler2 = function (item) {
console.log("Moved: " + item);
};
Output:
"fired: event #1"
Example
Input:
groupBy([6.1, 4.2, 6.3], Math.floor);
groupBy(["one", "two", "three"], "length");
Output:
// { 6: [6.1, 6.3], 4: [4.2] }
// { 3: ['one', 'two'], 5: ['three'] }
return a;
}, {});
};
Test Case
Input:
console.log(groupBy([6.1, 4.2, 6.3], Math.floor));
console.log(groupBy(["one", "two", "three"], "length"));
Output:
// { 6: [6.1, 6.3], 4: [4.2] }
// { 3: ['one', 'two'], 5: ['three'] }
//"{'a':1,'b':2,'c':3}" "{'a':1,'b':2,'c':3}"
console.log(JSON.stringify(a) === JSON.stringify(b));
//true
This may seem to be working fine, but this approach fails when we
change the order of the elements.
let a = {a: 1, b: 2, c: 3};
let b = {b: 2, a: 1, c: 3};
//"{'a':1,'b':2,'c':3}" "{'b':2,'a':1,'c':3}"
console.log(JSON.stringify(a) === JSON.stringify(b));
//false
// if mismatched keys
if (keys1.length !== keys2.length) {
return false;
}
// if are objects
if(areObjects){
// deep check again
if(!deepEqual(val1, val2)){
return true;
}
Test Case
Input:
const obj1 = {
name: "learnersbucket",
details: {
x: [1, 2],
y: 2,
},
};
const obj2 = {
name: "learnersbucket",
details: {
y: 2,
x: [1, 2],
},
};
console.log(deepEqual(obj1, obj2));
Output:
true
Example
let iterator = helper([1, 2, "hello"]);
console.log(iterator.next()); // 1
console.log(iterator.next()); // 2
console.log(iterator.done()); // false
console.log(iterator.next()); // "hello"
console.log(iterator.done()); // true
console.log(iterator.next()); // "null"
Create an index tracker in the outer function that will help to return
the next value from the next() method.
In the done() return boolean flag depending upon the index position
on the input array’s size.
Test Case
let iterator = helper([1, 2, "hello"]);
console.log(iterator.next()); // 1
console.log(iterator.next()); // 2
console.log(iterator.done()); // false
console.log(iterator.next()); // "hello"
console.log(iterator.done()); // true
console.log(iterator.next()); // "null"
Example
Input:
const arr = [];
arr.addListener('add', (eventName, items, array) => {
console.log('items were added', items);
});
Output:
"items were added again" // [object Array] (2)
[4,5]
We will extend the array prototype and add the required methods to it
● listeners : This will store the list of event listeners associated with
the event name.
● addListener(eventName, callback) : This will add a callback to
the event.
● pushWithEvent(eventName, items) : Adds all the items in the
array and triggers the event with the given name.
● popWithEvent(eventName) : Removes the last items from the
array and triggers the event with the given name.
Test Case
Input:
const arr = [];
arr.addListener('add', onAdd);
arr.addListener('add', onAddAgain);
console.log(arr);
Output:
"items were added" // [object Array] (5)
[1,2,3,"a","b"]
Example
const arr = [
{ name: "Amir", id: "1" },
{ name: "Samlan", id: "2" },
{ name: "Shahrukh", id: "0" },
];
One way to solve this is by using the Proxy and overriding the get
method, but the problem with using this is it converts the input values
to a string, thus it is not easy to determine if we have to filter on index
or value.
Test Case
Input:
const arr = [
{ name: "Amir", id: "1" },
{ name: "Samlan", id: "2" },
{ name: "Shahrukh", id: "0" },
];
console.log(filterObject(arr, 0));
console.log(filterObject(arr, "Amir"));
console.log(filterObject(arr, "0"));
console.log(filterObject(arr, "-1"));
Output:
// { name: "Amir", id: "1" }
// { name: "Amir", id: "1" }
// { name: "Shahrukh", id: "0" }
// undefined
Example
Input:
const endorsements = [
{ skill: 'css', user: 'Bill' },
{ skill: 'javascript', user: 'Chad' },
{ skill: 'javascript', user: 'Bill' },
{ skill: 'css', user: 'Sue' },
{ skill: 'javascript', user: 'Sue' },
{ skill: 'html', user: 'Sue' }
];
Output:
[
{
"user": "Bill",
"skill": [
"css",
"javascript"
]
},
{
"user": "Chad",
"skill": [
"javascript"
]
},
{
"user": "Sue",
"skill": [
"css",
"javascript",
"html"
● Get the value of the “on” key and aggregate the values of the
“who” key in the format in which we have to return output.
● Then only return the values from the aggregation as we are
expecting an array of objects as output.
Test Case
Input:
const endorsements = [
{ skill: 'css', user: 'Bill' },
{ skill: 'javascript', user: 'Chad' },
{ skill: 'javascript', user: 'Bill' },
{ skill: 'css', user: 'Sue' },
{ skill: 'javascript', user: 'Sue' },
{ skill: 'html', user: 'Sue' }
];
Output:
[{
"skill": "css",
"user": [
"Bill",
"Sue"
]
},
{
"skill": "javascript",
"user": [
"Chad",
"Bill",
"Sue"
]
},
{
"skill": "html",
"user": [
"Sue"
The input array will contain relations for many ancestries in random
order, We must return the array of strings representing different
relationships.
Input:
[
["lion", "cat"],
["cat", "mammal"],
["dog", "mammal"],
["mammal", "animal"],
["fish", "animal"],
["shark", "fish"],
];
Output:
[
"animal -> mammal -> cat -> lion",
"animal -> mammal -> cat",
"animal -> mammal -> dog",
"animal -> mammal",
"animal -> fish",
"animal -> fish -> shark"
]
// aggregating on child
a[child] = parent;
return a;
}, {});
};
const arr = [
["lion", "cat"],
["cat", "mammal"],
["dog", "mammal"],
["mammal", "animal"],
["fish", "animal"],
["shark", "fish"],
];
console.log(aggregate(arr));
/*
{
"lion": "cat",
"cat": "mammal",
"dog": "mammal",
"mammal": "animal",
"fish": "animal",
"shark": "fish"
}
*/
We have aggregated the values on the child as one child will have only
one parent, this way a single key-value pair is formed which will be
faster to process and in forming relationships.
Now, all we have to do is keep on traversing this map and form the
relation string. For this, we will get the value of the current key and
console.log(convert(map));
/*
[
Combining it together.
// aggregating on child
a[child] = parent;
return a;
}, {});
};
};
Test Case
Input:
const arr = [
["lion", "cat"],
["cat", "mammal"],
["dog", "mammal"],
["mammal", "animal"],
["fish", "animal"],
["shark", "fish"],
];
console.log(ancestry(arr));
Output:
[
"animal -> mammal -> cat -> lion",
"animal -> mammal -> cat",
"animal -> mammal -> dog",
"animal -> mammal",
"animal -> fish",
"animal -> fish -> shark"
]
Example
Input:
const obj = {
a: {
b: {
c: [1,2,3]
}
}
};
console.log(get(obj, 'a.b.c'));
console.log(get(obj, 'a.b.c.0'));
console.log(get(obj, 'a.b.c[1]'));
console.log(get(obj, 'a.b.c[3]'));
Output:
// [1,2,3]
// 1
// 2
// undefined
Test Case
Input:
const obj = {
a: {
b: {
c: [1,2,3]
}
}
};
console.log(get(obj, 'a.b.c'));
console.log(get(obj, 'a.b.c.0'));
console.log(get(obj, 'a.b.c[1]'));
console.log(get(obj, ['a', 'b', 'c', '2']));
console.log(get(obj, 'a.b.c[3]'));
Output:
// [1,2,3]
// 1
// 2
// 3
// undefined
// 'bfe'
Example
const object = { 'a': [{ 'b': { 'c': 3 } }] };
If it is a string then filter all the special characters like [, ] and split the
string on dot(.) to get all the path keys in an array.
Then using a helper function we can assign the value to the provided
path.
● Get only the first key from the path array and aggregate the rest
of the keys.
● If there are no more keys left to update, assign the value to the
current key.
Note:- This will override the existing value and assign a new one.
Test Case
Input:
const abc = {
a: {
b: {
c: [1, 2, 3]
},
d: {
a: "hello"
}
}
};
Output:
"learnersbucket"
"learnersbucket"
"learnersbucket"
"learnersbucket"
"learnersbucket"
"learnersbucket"
"learnersbucket"
4
Example
console.log(JSON.stringify([{ x: 5, y: 6 }]));
// expected output: "[{"x":5,"y":6}]"
Second, we will handle some base cases like, if it is a null value, return
'null', if it is an object, get the appropriate value from the above
method. At the end wrap the value inside curly braces and return
them.
// main method
static stringify(obj) {
// if value is not an actual object, but it is undefined or an array
// stringify it directly based on the type of value
if (typeof obj !== 'object' || obj === undefined || obj instanceof
Array) {
return this.value(obj);
}
// if value is null return null
else if(obj === null) {
return `null`;
}
The final case to handle the circular object, for that we will be using
the removeCycle(obj) method to remove the cycle from the objects.
Complete code
class JSON {
// main method
static stringify(obj) {
// if value is not an actual object, but it is undefined or an array
// stringify it directly based on the type of value
if (typeof obj !== 'object' || obj === undefined || obj instanceof
Array) {
return this.value(obj);
}
// if value is null return null
else if(obj === null) {
// helper method
// handle all the value types
// and stringify accordingly
static value(val) {
switch(typeof val) {
case 'boolean':
case 'number':
// if the value is finite number return the number as it is
// else return null
return isFinite(val) ? `${val}` : `null`;
case 'string':
return `"${val}"`;
// return null for anything else
case 'function':
case 'symbol':
case 'undefined':
return 'null';
// for object, check again to determine the object's actual type
case 'object':
// if the value is date, convert date to string
if (val instanceof Date) {
return `"${val.toISOString()}"`;
}
// if value is a string generated as constructor,
// new String(value)
Test Case
Input:
let obj1 = {
a: 1,
b: {
c: 2,
d: -3,
e: {
f: {
g: -4,
},
},
h: {
i: 5,
j: 6,
},
}
}
let obj2 = {
a: 1,
b: {
c: 'Hello World',
d: 2,
e: {
f: {
g: -4,
},
},
h: 'Good Night Moon',
},
}
// cricular object
item1.next = item2;
item2.next = item3;
item3.next = item1;
console.log(JSON.stringify(item1));
console.log(JSON.stringify(obj1));
console.log(JSON.stringify(obj2));
console.log(JSON.stringify([{ x: 5, y: 6 }]));
// expected output: "[{"x":5,"y":6}]"
console.log(JSON.stringify({a: Infinity}));
Output:
"{'next': {'next': {'val': 30},'val': 20},'val': 10}"
"{'a': 1,'b': {'c': 2,'d': -3,'e': {'f': {'g': -4}},'h': {'i': 5,'j': 6}}}"
"{'a': 1,'b': {'c': 'Hello World','d': 2,'e': {'f': {'g': -4}},'h': 'Good
Night Moon'}}"
"[3,'false',false,null]"
"{'a': null}"
Example
const json = '{"result":true, "count":42}';
const obj = JSON.parse(json);
console.log(obj);
// expected output: {"result": true, "count": 42}
static parse(string) {
// if it is array
if (string[0] === '[') {
// if it object
// get the key and value by splitting on :
// parse the key and value individually
return subStrings.reduce((acc, item) => {
if (item.indexOf(':') > -1) {
const index = item.indexOf(':');
const thisKey = item.substring(0, index);
const thisValue = item.substring(index + 1);
acc[this.parse(thisKey)] = this.parse(thisValue);
}
return acc;
}, {});
}
}
}
}
// helper function
// to get the comma separated values of array or objects
static stringSplitByComma(string) {
const allStrs = [];
// lParen tracks the parentheses []
// lCurly tracks the curly braces {}
// if a comma is spotted
if ((rChar === ',' && lParen === 0 && lCurly === 0) ||
right === string.length)
{
// get the value in between and store it
const thisStr = string.substring(left, right);
allStrs.push(thisStr);
left = right + 1;
}
right++;
}
return allStrs;
}
Complete code
class JSON{
static parse(string) {
// remove the space from the string
string = string.replace(/ /g, '');
// if it is array
if (string[0] === '[') {
// parse each value
return subStrings.map(item => this.parse(item));
} else if (string[0] === '{') {
// if it object
// get the key and value by splitting on :
return acc;
}, {});
}
}
}
}
// helper function
// to get the comma separated values of array or objects
static stringSplitByComma(string) {
const allStrs = [];
// lParen tracks the parentheses []
// lCurly tracks the curly braces {}
let lParen = 0, lCurly = 0;
let left = 0, right = 0;
// if a comma is spotted
if ((rChar === ',' && lParen === 0 && lCurly === 0) ||
right === string.length)
{
right++;
}
return allStrs;
}
}
Test Case
console.log(JSON.parse('[{"result":true,"count":42}]'));
/*
[
{
"result": true,
"count": 42
}
]
*/
console.log(JSON.parse('[3,"false",false,null]'));
/*
[
3,
"false",
false,
null
]
*/
console.log(JSON.parse('{"x": [10,null,null,null]}'));
/*
{
"x": [
10,
null,
null,
null
]
}
*/
console.log(JSON.parse('[]'));
/*
[]
*/
Example
Input:
const str = 'Hello, world';
const styleArr = [[0, 2, 'i'], [4, 9, 'b'], [7, 10, 'u']];
Output:
'<i>Hel</i>l<b>o, w<u>orl</u></b><u>d</u>'
Note – <u> tag gets placed before the <b> tag and after it as the
insertion index overlaps it.
Let us first see the second method as most of the times in the interview
you will be asked to implement this way only.
Output:
<i>H<b>el</b></i><b>l</b>o, World
We can solve this with the help of a priority queue and a stack.
Priority queue
We are not going to use the complete implementation of Priority
Queue, rather a helper function that adds a new item in the array and
sorts it in the ascending or descending order depending upon the
order.
// helper function works as Priority queue
// to add tags and sort them in descending order
// based on the difference between the start and end
function addAndSort(track, index, data) {
if (!track[index]) track[index] = [];
track[index] = [...track[index], data];
Stack
A simple implementation of the Stack data structure with the required
methods.
function Stack() {
let items = [];
let top = 0;
Tag method
A helper function to create a new tag for each entry of styles and its
corresponding, also helps to get the range for easier processing in the
priority queue. We push this in the priority queue and sort it.
// helper function to form a tag
// and trace the string
function Tag(start, end, tag) {
this.start = start;
this.end = end;
this.tag = tag;
this.text = "";
// initialize with a new Tag that has max range and empty string
html.push(new Tag(0, Number.MAX_VALUE, ""));
Test Case
Input:
const encoded = parse('Hello, World',
[[0, 2, "i"],
[7, 10, "u"],
[4, 9, "b"],
[2, 7, "i"],
[7, 9, "u"]]);
console.log(encoded);
Output:
"<i>He<i>l</i></i><i>l<b>o,
<u><u>W</u></u></b></i><b><u><u>or</u></u></b><u>l</u>d"
"Hello, World"
Using DOMParser()
DOMParser() parses the HTML source code from the string and the
good thing is it appropriately places the opening and closing tags if it
is missing.
Thus all we have to do is traverse the styles and add tags at the
mentioned opening and closing positions in the input string.
Then pass this string to the DOMParser() and it will generate the
HTML from the string.
Test Case
Input:
const encoded = parse('Hello, World',
[[0, 2, "i"],
[7, 10, "u"],
[4, 9, "b"],
[2, 7, "i"],
[7, 9, "u"]]);
console.log(encoded);
Output:
"<i>He<i>l</i>l<b>o,
<u><u>W</u></u></b></i><b><u><u>or</u></u></b><u>l</u>d"
"Hello, World"
Example
Input:
<div id="root">
<article>Prepare for interview</article>
<section>
on
<p>
<span>
Learnersbucket
<button>click me!</button>
<button id="target">click me!</button>
</span>
</p>
</section>
</div>
Output:
"div[id='root'] > section:nth-child(2) > p:nth-child(1) > span:nth-child(1)
> button:nth-child(2)"
To generate the CSS selector, all we have to do is start from the target
and trace back to the root (parent).
● Use a while loop and keep iterating till we have found the root of
the target.
● In each iteration get the index of the current child in its
immediate parent to decide the nth-child position.
● At the end, add the roots tag name. The selector will begin from
this.
Test Case
Input:
<div id="root">
<article>Prepare for interview</article>
<section>
on
<p>
<span>
Learnersbucket
<button>click me!</button>
<button id="target">click me!</button>
</span>
Output:
"div[id='root'] > section:nth-child(2) > p:nth-child(1) > span:nth-child(1)
> button:nth-child(2)"
Example
Input:
<form id="parent">
<input type="text" name="a.c" value="1"/>
<input type="text" name="a.b.d" value="2"/>
<input type="text" name="a.b.e" value="3"/>
</form>
Output:
{
"a": {
"c": "1",
"b": {
"d": "2",
"e": "3"
}
}
}
The approach is simple to solve this, create a function that will accept
the parent’s id as input and get all the input elements under that
parent.
● Split the name on the dot and get all the keys as an array.
● Check if the key already exists or not, if it does not create an
empty object and assign it to it.
● If the key is the last from the array then assign the value to it.
Test Case
Input:
<form id="parent">
<input type="text" name="a.c" value="1"/>
<input type="text" name="a.b.d" value="2"/>
<input type="text" name="a.b.e" value="3"/>
</form>
console.log(aggregateValues('parent'));
Output:
{
"a": {
"c": "1",
"b": {
"d": "2",
"e": "3"
}
}
}
Example
const requestInterceptor = (requestArguments) => {
console.log("Before request");
}
fetch('https://jsonplaceholder.typicode.com/todos/1')
.then(response => response.json())
.then(json => console.log(json))
// "Before request"
// "After response"
/*
{
"userId": 1,
"id": 1,
"title": "delectus aut autem",
"completed": false
}
*/
Axios, one of the most popular libraries for making network calls,
comes with interceptors axios.interceptor.request and
axios.interceptor.response which can be used to monitor and perform
actions before any request is made and after every response is
received.
// request interceptor
// perform all the pre-request actions
window.requestInterceptor = (args) => {
// your action goes here
return args;
}
// response interceptor
// perform all the post-response actions
window.responseInterceptor = (response) => {
// your actions goes here
return response;
}
// response interceptor
// pass the response to response interceptor
response = responseInterceptor(response);
Test Case
As you can see in the requestInterceptor we are manually setting the
page number and in the responseInterceptor we are returning the
parsed JSON value.
Input:
// request interceptor
// perform all the pre-request actions
window.requestInterceptor = (args) => {
// original request does not contains page info
// assign the pagination in the interceptor
args[0] = args[0] + "2";
return args;
}
// response interceptor
// perform all the post-response actions
window.responseInterceptor = (response) => {
// convert the value to json
// to avoid parsing every time
return response.json();
}
fetch('https://jsonplaceholder.typicode.com/todos/')
.then(json => console.log(json));
Example
const call = cachedApiCall(1500);
// first call
// an API call will be made and its response will be cached
call('https://jsonplaceholder.typicode.com/todos/1', {}).then((a) =>
console.log(a));
//"making new api call"
/*
{
"userId": 1,
"id": 1,
"title": "delectus aut autem",
"completed": false
}
*/
In the inner function, we will create a new unique key from the
arguments to cache value.
Using this key, get the entry from the cache. If there is no entry
present or the time of the entry is expired, make a new API call. Else
return the value of the entry.
To generate the key and make the API call we will be using two helper
functions.
return null;
};
Test Case
const call = cachedApiCall(1500);
// first call
// an API call will be made and its response will be cached
call('https://jsonplaceholder.typicode.com/todos/1', {}).then((a) =>
console.log(a));
//"making new api call"
/*
{
"userId": 1,
"id": 1,
"title": "delectus aut autem",
"completed": false
}
*/
Example
Input:
<div class='a' id="root">
<div class='b' id='b-1'>
<div class='a' id='a-2'>
<div class='d' id='d-1'></div>
</div>
<div class='c' id='c-1'>
<div class='a' id='a-3'>
<div class='d' id='d-2'></div>
</div>
</div>
</div>
</div>
findByClass('a');
Output:
[<div class="a" id="root">
<div class="b" id="b-1">
<div class="a" id="a-2">
<div class="d" id="d-1"></div>
</div>
<div class="c" id="c-1">
<div class="a" id="a-3">
<div class="d" id="d-2"></div>
</div>
</div>
</div>
</div>,
<div class="a" id="a-2">
<div class="d" id="d-1"></div>
</div>,
<div class="a" id="a-3">
If it has it, then push the node in the result. After that traverse all the
children of the node and for each child check if they have the class in
their classList or not.
We can recursively call the same function for each child and it will call
their children and so on and perform the test and return the result.
This traversing pattern is known as DFS (Depth First Search).
function findByClass(class) {
// get the root element,
// you can start from the body
const root = document.body;
Test Case
Input:
<div class='a' id="root">
<div class='b' id='b-1'>
<div class='a' id='a-2'>
<div class='d' id='d-1'></div>
</div>
<div class='c' id='c-1'>
<div class='a' id='a-3'>
<div class='d' id='d-2'></div>
</div>
</div>
</div>
</div>
console.log(findByClass('a'));
Output:
[<div class="a" id="root">
<div class="b" id="b-1">
<div class="a" id="a-2">
<div class="d" id="d-1"></div>
</div>
<div class="c" id="c-1">
<div class="a" id="a-3">
<div class="d" id="d-2"></div>
</div>
Example
Input:
<div class="a" id="a-1">
<div class="b" id="b-1">
<div class="c" id="c-1"/>
<div class="c" id="c-2"/>
</div>
<div class="c" id="c-3"/>
</div>
getByClassNameHierarchy("a>b>c");
Output:
[
<div class="c" id="c-1"></div>,
<div class="c" id="c-2"></div>
]
// helper function
function traverseDOM(element, classNames, index, result){
// if the element is not present
if(!element) {
return;
}
Test Case
Input:
<div class="a" id="root">
<div class="b" id="b-1">
<div class="c" id="c-1">
</div>
<div class="c" id="c-2">
</div>
</div>
<div class="b" id="b-2">
<div class="c" id="c-3">
</div>
</div>
</div>
console.log(getByClassNameHierarchy(document.getElementById('root'),
'a>b>c'));
Output:
[
<div class="c" id="c-1"></div>,
<div class="c" id="c-2"></div>,
<div class="c" id="c-3"></div>
]
Example
Input:
<div id="root">
<span style="color:#fff;">1</span>
<span style="color:#eee;">2</span>
<span style="color:white;">3</span>
<span style="color:rgb(255, 255, 255);">4</span>
</div>
Output:
[
<span style="color:#fff;">1</span>,
<span style="color:white;">3</span>,
<span style="color:rgb(255, 255, 255);">4</span>
]
For this, we can create a function that will temporarily create a new
element and apply the input color as its style, then using the
getComputedStyle() method, we can get the RGB value of the color
and convert it to HEXA code for comparison.
function getHexColor(color){
// create a new element
const div = document.createElement('div');
For finding the elements with the given color value, we will first
convert the input color value to the HEXA using the above function.
Then recursively traverse all the DOM elements and get their color
values and convert them to HEXA and compare it with the input color
value's HEXA.
If they match, push the element to the result array and return it.
Test Case
Input:
<div id="root">
<span style="color:#fff;">1</span>
<span style="color:#eee;">2</span>
<span style="color:white;">3</span>
<span style="color:rgb(255, 255, 255);">4</span>
</div>
console.log(findElementByColor(document.getElementById('root'), 'white'));
Output:
[
<span style="color:#fff;">1</span>,
<span style="color:white;">3</span>,
<span style="color:rgb(255, 255, 255);">4</span>
]
Example
Input:
const task = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const count = 5;
In each call, 10 new tasks are pushed and only 5 are executed,
remaining are stored in the queue.
What is throttling?
Throttling is a way/technique to restrict the number of function
execution/calls.
By default, the count will be of the same size as the array of tasks.
return function() {
// store the context to pass it to the callback function
const context = this;
const args = arguments;
Test Case
Input:
// this will add these tasks at each call
btn.addEventListener('click', throttle([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 2,
(task) => {
console.log(task);
}, 2000));
Output:
// [object Array] (2)
[1,2] // 1st call
Example
Input:
"2[a2[b]]"
"3[b2[ca]]"
output:
"abbabb"
"bcacabcacabcaca"
numStack.push(val);
charStack.push(str[i]);
}else{
}
}else if(val === ']'){
// if close bracket
// reset temp and count
temp = "";
count = 0;
} else{
// if alpha character then add to charStack
charStack.push(val);
}
}
Output:
"abbabb"
"bcacabcacabcaca"
The term “Trie” comes from the word retrieval and is usually
pronounced “try”, to separate it from other “tree” structures.
The main idea behind using Tries as a data structure was that they
could be a nice compromise between running time and memory.
Thus, the size of a trie is directly correlated to the size of all the
possible values that the Trie could represent.
Base structure
We will need a TrieNode object to insert a new word in the Trie, which
would represent an entirely new Trie.
this.getWord = function() {
let output = [];
let node = this;
return output.join('');
};
● Check that the word that needs to be added doesn’t already exist
in this trie.
● Next, if we’ve traversed down the branch where this word ought
to live and the words don’t exist yet, we’d insert a value into the
node’s reference where the word should go.
return output;
};
if(!word) return;
return false
};
this.getWord = function() {
let output = [];
let node = this;
return output.join('');
};
return output;
};
if(!word) return;
return true;
}
return false
};
Test Case
Input:
const trie = new Trie();
Output:
true
true
["pickled", "picked", "piper"]
["peter"]
Example
Input:
[1, 2, 3, 4, 5, 5, 6, 7, 8, 9]
5
Output:
4 //Index with first occurrence
5 //Index with second occurrence
First Occurence
As binary search returns the index of the element as soon as it finds it,
to find the first occurrence, even after finding the element we will keep
looking in the lower range to check if the element has occurred before
it or not, if it is then return the lowest index.
Test Case
Input:
const arr = [1, 2, 3, 4, 5, 5, 6, 6, 7, 8, 9, 10];
console.log(first(arr, 5));
console.log(first(arr, 6));
Output:
4
6
Last Occurence
Just like finding the first occurrence to find the last occurrence we will
have to keep looking for the element in the upper range after it is
found to make sure we get the last index of the element.
Test Case
`Input:
const arr = [1, 2, 3, 4, 5, 5, 6, 6, 7, 8, 9, 10];
console.log(last(arr, 5));
console.log(last(arr, 6));
Output:
5
7
Example
Input:
{
a : {
b : (a,b,c) => a+b+c,
c : (a,b,c) => a+b-c,
},
d : (a,b,c) => a-b-c
}
Fn(obj)(1,1,1);
Output:
{
a : {
b : 3,
c : 1
},
d: -1
}
Inside the inner function. Iterate all the keys of the object, if the value
of the key is a function, pass the arguments to it and store its
computed value on that key. Else recursively call the same function for
nested processing.
© JavaScript Interview Guide | learnersbucket.com 347
At the end return the original input object as we are doing the
processing in-place.
Test Case
Input:
let test = {
a: {
b: (a, b, c) => a + b + c,
c: (a, b, c) => a + b - c,
},
d: (a, b, c) => a - b - c,
e: 1,
f: true
};
Output:
{
"a": {
"b": 3,
"c": 1
},
"d": -1,
"e": 1,
"f": true
}
Example
Input:
const val = { salary: 10000 };
Output:
7700
Create two functions, in the first function accept all the functions as
arguments and inside it create another function and return it. The
inner function will accept the value and it will pass the value through
the functions that are received in the outer function in the sequence.
At the end return the final output from the last function.
Test Case
Input:
const getSalary = (person) => person.salary
const addBonus = (netSalary) => netSalary + 1000;
const deductTax = (grossSalary) => grossSalary - (grossSalary * .3);
console.log(result);
Output:
7700
● Send each event after a delay of 1 second and this logging fails
every n % 5 times.
● Send the next event only after the previous one resolves.
● When the failure occurs, attempt a retry.
Example
Input:
const sdk = new SDK();
sdk.logEvent("event 1");
sdk.logEvent("event 2");
sdk.logEvent("event 3");
sdk.logEvent("event 4");
sdk.logEvent("event 5");
sdk.logEvent("event 6");
sdk.logEvent("event 7");
sdk.logEvent("event 8");
sdk.logEvent("event 9");
sdk.logEvent("event 10");
sdk.send();
Output:
"Analytics sent event 1"
"Analytics sent event 2"
"Analytics sent event 3"
"Analytics sent event 4"
"-----------------------"
"Failed to send event 5"
"Retrying sending event 5"
"-----------------------"
"Analytics sent event 5"
Delay function
The most important part of this function is that the events will be sent
after a delay of 1 second and fails n%5 times.
Create a new Promise and inside that run a setTimeout that will
resolve the promise after the delay.
To the same, add one extra condition that will check if the current
execution is n%5 then reject, else resolve.
// function to delay the execution
wait = () => new Promise((resolve, reject) => {
setTimeout(() => {
// reject every n % 5 time
if(this.count % 5 === 0){
reject();
} else {
resolve();
}
}, 1000);
});
This will be an async function and in each call, get the first element
from the queue and try the wait(), if it resolves then print the log or
perform any other operation, else if it fails, push the event back in the
queue for retry. Finally, recursively call the same function for the next
operation.
Add a base case to stop the execution if there are no more events in the
queue. Also, track the count in each call.
// to send analytics
// recursively send the events
try {
// delay
await this.wait();
// if execution fails
console.log("-----------------------");
console.log("Failed to send " + current);
console.log("Retrying sending " + current);
console.log("-----------------------");
// to send analytics
// recursively send the events
sendAnalytics = async function (){
// if there are no events in the queue
// stop execution
if(this.queue.length === 0){
return;
}
try {
// delay
await this.wait();
// if execution fails
console.log("-----------------------");
console.log("Failed to send " + current);
console.log("Retrying sending " + current);
console.log("-----------------------");
Test Case
Input:
sdk.logEvent("event 1");
sdk.logEvent("event 2");
sdk.logEvent("event 3");
sdk.logEvent("event 4");
sdk.logEvent("event 5");
sdk.logEvent("event 6");
sdk.logEvent("event 7");
sdk.logEvent("event 8");
sdk.logEvent("event 9");
sdk.logEvent("event 10");
sdk.logEvent("event 11");
sdk.logEvent("event 12");
sdk.logEvent("event 13");
sdk.logEvent("event 14");
sdk.logEvent("event 15");
sdk.logEvent("event 16");
sdk.logEvent("event 17");
sdk.logEvent("event 18");
sdk.logEvent("event 19");
sdk.logEvent("event 20");
sdk.send();
Output:
"Analytics sent event 1"
"Analytics sent event 2"
"Analytics sent event 3"
"Analytics sent event 4"
"-----------------------"
"Failed to send event 5"
"Retrying sending event 5"
"-----------------------"
A full B-tree is defined as a binary tree in which all nodes have either
zero or two child nodes.
Example
Input :
1
/ \
2 3
/ \
4 5
Output : Yes
Input :
1
/ \
2 3
/
4
Output :No
Recursive Approach
● Create a function which recursively calls itself to check if each
node has 0 or 2 children or not.
● If the given node is empty then it's a full binary tree.
● If a given node has no child then also it is full.
● If it has left and right both children then it is full.
● Otherwise it is not a full binary tree.
// if leaf node
if(root.left === null && root.right === null ) {
return true;
}
// if none work
return false;
}
Test Case
Input:
function Node(val) {
this.val = val;
this.left = null;
this.right = null;
}
console.log(isFullTreeRecursive(tree));
Output:
false
const q = [];
Test Case
Input:
function Node(val) {
this.val = val;
this.left = null;
this.right = null;
}
console.log(isFullTreeIterative(tree));
Output:
true
Example
Input :
1
/ \
2 3
/ \ / \
4 5 6 7
Output :
Height = 3
Width = 4
To find out the depth of the tree we will have to perform the following
operations.
We will recursively find out the depth of the left subtree and right
subtree. Then get the max of both depths and return it.
Test Case
Input:
function Node(val) {
this.val = val;
this.left = null;
this.right = null;
}
console.log(btHeight(tree));
Output:
3
We will first find the height of the tree and then find the width by
recursively checking the number of nodes at every level. Then we will
return the maximum width of them.
return 0;
};
return maxWidth;
};
Test Case
Input:
function Node(val) {
this.val = val;
this.left = null;
this.right = null;
}
console.log(btWidth(tree));
Output:
4
Example
function Parent() {
this.name = "abc";
};
Parent.prototype.walk = function(){
console.log (this.name + ', I am walking!');
};
function Child() {
this.name = "pqr";
};
Child.prototype.sayHello = function(){
console.log('hi, I am a student');
};
// function to extend
extend(Parent, Child);
Output:
"hi, I am a student"
"pqr, I am walking!"
true
true
And for this, we can either update the reference of the child’s
prototype to point to the parent or use the
Object.setPrototypeOf(child, parent) to create the link.
Here I have used one method for extending and commented on the
second. You can choose between either based on your preference.
// static methods;
SubType.__proto__ = SuperType;
//ES5: Object.setPrototypeOf(SubType, SuperType);
Test Case
Input:
// Parent
function Person() {
this.name = "abc";
}
// static methods
Person.staticSuper = function(){
console.log('static');
};
// child
function Student() {
this.name = "pqr";
}
// sayHello
// this will replace the parent after extending
Student.prototype.sayHello = function(){
console.log('hi, I am a student');
}
// check inheritance
console.log(student1 instanceof Person);
console.log(student1 instanceof Student);
Output:
"hi, I am a student"
"pqr, I am walking!"
true
true
We are done creating the loading bar, just need to add it to the DOM
to animate, for which we will get an entry element and append this
into that.
const entry = document.getElementById("entry");
entry.appendChild(loadingBar);
//apply styles
dynamicAnimation(
"loadingBar",
`
0%{
width: 0%;
}
100%{
Create a global variable count and increment its value by one every
time the button is clicked, vice-versa decrease its value by one, when a
loading bar is animated.
For generating the next loading bar from the queue, we will have to
recursively call the same function (which generates the loading bar)
when the animation of the previous loading bar is done.
if (count > 0) {
//generate the loading bar
generateLoadingBar();
}
});
We will also need to update the code on the button click, invoke the
generateLoadingBar function only when the count is zero as all other
subsequent calls will be invoked recursively.
//update count
updateCount(1);
});
//apply styles
//animation keyframes
dynamicAnimation(
"loadingBar",
`
0%{
width: 0%;
}
100%{
width: 100%;
}`
);
loadingBar.style.height = "10px";
loadingBar.style.backgroundColor = "Red";
loadingBar.style.width = "0";
loadingBar.style.marginBottom = "10px";
loadingBar.style.animation = "loadingBar 3s forwards";
//remove listener
loadingBar.removeEventListener("animationend", () => {});
};
//update count
updateCount(1);
});
</script>
</body>
</html>
For example, the next animation should trigger when the first loading
bar is 50% done, thus let’s say our original loading bar is going to
complete 100% animation in 3 seconds, which means the next
animation should be triggered when it is 50% done in 1.5 seconds (half
time).
We will parallelly animate another element for that duration and on its
animationend trigger the next rendering.
//remove listener
shadowLoadingBar.removeEventListener("animationend", () => {});
};
If you notice, we are creating two loading bars and adding them in
fragments, and hard-coded the duration of the shadow bar 1.5 and
width to 50%. You can make it dynamic by using a simple
mathematical calculation.
Also, we have kept the background of the shadow bar green and it is
visible currently (just to show the working), but you can hide it.
Example
// set 'bar' on 'foo' that will expiry after 1000 milliseconds
myLocalStorage.setItem('foo', 'bar', 1000);
// after 2 seconds
console.log(myLocalStorage.getItem('foo'));
// null
While adding the entry we will accept the expiry date in milliseconds
(30 days by default). Set the expiry date to time from the current date
along with the value and store it in the original local storage.
// add an entry
// default expiry is 30 days in milliseconds
setItem(key, value, maxAge = 30 * 60 * 60 * 1000) {
// store the value as the object
// along with the expiry date
let result = {
data : value
}
if(maxAge){
// set the expiry
// from the current date
result.expireTime = Date.now() + maxAge;
}
Likewise, while getting the value for the given key, check if there is a
value associated with the key, if it exists and is not expired then return
the value, else remove the entry and return null.
getItem(key) {
// get the parsed value of the given key
let result = JSON.parse(window.localStorage.getItem(key));
// add an entry
// default expiry is 30 days in milliseconds
setItem(key, value, maxAge = 30 * 60 * 60 * 1000) {
// store the value as object
// along with expiry date
let result = {
data : value
}
if(maxAge){
// set the expiry
// from the current date
result.expireTime = Date.now() + maxAge;
}
Test Case
Input:
myLocalStorage.setItem('foo', 'bar', 1000);
setTimeout(() => {
console.log(myLocalStorage.getItem('foo'));
}, 1500);
Output:
null
Example
document.myCookie = "blog=learnersbucket";
document.myCookie = "name=prashant;max-age=1"; // this will expire after 1
second
console.log(document.myCookie);
// "blog=learnersbucket; name=prashant"
setTimeout(() => {
console.log(document.myCookie);
}, 1500);
// "blog=learnersbucket"
// "name=prashant" is expired after 1 second
To parse the input and separate the data and options, we will be using
a helper function.
return {
key,
value,
options,
}
}
// helper function
// to separate key and value
function separateKeyValue(str) {
return str.split('=').map(s => s.trim());
}
For the main logic we can extend the document object with
Object.defineProperty().
Object.defineProperty(document, 'myCookie', {
configurable: true,
get() {
set(val) {
// get the key value of the data
// and option from the string
const { key, value, options } = parseCookieString(val);
Test Case
Input:
useCustomCookie();
document.myCookie = "blog=learnersbucket";
console.log(document.myCookie);
setTimeout(() => {
console.log(document.myCookie);
}, 1500);
Output:
"blog=learnersbucket; name=prashant"
"blog=learnersbucket"
Actions
_push_ : Array – Pushes the destination array in the input array.
const inputArr = [1, 2, 3, 4]
const outputArr = update(
inputArr, {_push_: [5, 6, 7]}
);
console.log(outputArr);
// [1,2,3,4,5,6,7]
console.log(outputArr);
// [1,10,3,4]
console.log(newState);
/*
console.log(outputArr);
/*
{
"a": {
"b": 4
}
}
*/
We can create a helper function that will accept two arguments (data,
action), the action is always an Object.
We can recursively deep traverse the object and in each call check if
the current key is any of the actions then perform the action
accordingly.
Wrap this helper function inside another parent function. As the input
object will freezed, we cannot directly update it, thus we will create a
// function to deepfreeze
function deepFreeze(object) {
// get the property names defined on object
let propNames = Object.getOwnPropertyNames(object);
return Object.freeze(object);
};
// if it is an array
if (target instanceof Array) {
// create a copy
const res = [...target];
//return it
console.log(outputArr);
// [1,2,3,4,5,6,7]
console.log(newState);
/*
{
"a": {
"b": {
"c": 3
}
},
"d": 2
}
*/
console.log(newState);
/*
{
"a": {
console.log(newState);
/*
{
"a": {
"b": 4
}
}
*/
Example
const obj = {
a: {
b: {
c: 2
}
}
};
// object is frozen
// its properties cannot be updated
deepFreeze(obj);
console.log(newState);
/*
{
"a": {
"b": {
"c": 3,
"d": 4
}
}
}
*/
/*
{
"a": {
"b": {
"c": 3,
"d": 4
}
}
}
*/
To implement this we will first create a clone of the input obj and then
pass this input object to the callback function of produce, for
processing.
return Object.freeze(object);
// if mismatched keys
if (keys1.length !== keys2.length) {
return false;
}
// if are objects
if(areObjects){
// deep check again
if(!deepEqual(val1, val2)){
return false;
}
}
// if are not objects
// compare the values
else if(!areObjects && val1 !== val2){
return false;
}
}
return true;
};
// deep freeze
deepFreeze(clone);
Test Case
const obj = {
a: {
b: {
c: 2
}
}
};
// object is frozen
// its properties cannot be updated
deepFreeze(obj);
console.log(newState);
/*
{
/*
{
"a": {
"b": {
"c": 3,
"d": 4
}
}
}
*/
There are two ways in which we can prioritize the API calls.
Using Request.Priority
The fetch method in the browser comes with an additional option to
prioritize the API requests.
It can be one of the following values: low, high, and auto. By default
browsers fetch requests on high priority.
Thus while throttling, the calls will be made after some delay using the
timer functions.
Output:
"Main program started"
"Main program exiting"
{
"userId": 1,
"id": 3,
"title": "fugiat veniam minus",
"completed": false
}
{
"userId": 1,
"id": 1,
"title": "delectus aut autem",
"completed": false
}
Note – The calls are made in priority, but the time at which they get
resolved can vary, thus we can see random outputs.
Example
Input:
const json = {
type: 'div',
props: { id: 'hello', class: "foo" },
children: [
{type:'h1', children: 'HELLO' },
{type:'p', children: [{type:'span', props: {class: "bar" }, children:
'World' }] }
]
};
JSONtoHTML(json);
Output:
<div id="hello" class="foo">
<h1>HELLO</h1>
<p>
<span class="bar">World</span>
</p>
</div>
if(Array.isArray(json)){
// if props available
// set them
if(entry.props){
for(let key in entry.props){
element.setAttribute(key, entry.props[key]);
}
}
// if array of children
if(Array.isArray(entry.children)){
// recursively convert the children to DOM
// and assign them
for(let child of entry.children){
element.appendChild(JSONtoHTML(child));
}
}
// if children is string / text
else{
element.innerText = entry.children;
}
return fragment;
};
Test Case
Input:
const JSON = [
{
type: 'div',
props: { id: 'hello', class: "foo" },
children: [
{type:'h1', children: 'HELLO' },
{type:'p', children: [{type:'span', props: {class: "bar" }, children:
'World' }] }
]
},
{
type: 'section',
props: { id: 'hello-2', class: "foo-2" },
children: [
{type:'h1', children: 'HELLO-2' },
{type:'p', children: [{type:'span', props: {class: "bar-2" }, children:
'World' }] }
]
}];
console.log(JSONtoHTML(json));
Output:
<div id="hello" class="foo">
<h1>HELLO</h1>
<p>
Example
Input:
<div id="foo">
<h1>Hello</h1>
<p class="bar">
<span>World!</span>
</p>
</div>
Output:
{
"props": {
"id": "foo"
},
"children": [
{
"children": "Hello",
"type": "h1"
},
{
"props": {
"class": "bar"
},
"children": [
{
"children": "World!",
"type": "span"
}
],
"type": "p"
}
],
There are three things that we need to generally take care of to create
this function.
We will be using a helper function that will give us all the attributes of
the node.
return obj;
};
return output;
};
Test Case
Input:
<div id="foo">
<h1>Hello</h1>
<p class="bar">
<span>World!</span>
</p>
</div>
Output:
{
"props": {
"id": "foo"
},
Example
const historyTracking = HistoryTracking();
historyTracking.registerEntity("document");
historyTracking.registerService("document", 'JavaScript Ultimate Guide');
historyTracking.track("document", 'JavaScript Ultimate Guide', "Problem
1");
historyTracking.track("document", 'JavaScript Ultimate Guide', "Problem 1,
Problem 2");
historyTracking.track("document", 'JavaScript Ultimate Guide', "Problem
3");
console.log(historyTracking.getHistory("document", 'JavaScript Ultimate
Guide'));
We will use a map to store the unique entities. Each entity can have
multiple services and its history stored as an object and this object will
be stored as a value next to the entity in the map.
class HistoryTrackingHelper {
constructor() {
// to track unique entries
this.entities = new Map();
}
return function(){
if(!instance){
instance = new HistoryTrackingHelper();
}
return instance;
};
})();
Test Case
Input:
const historyTracking = HistoryTracking();
historyTracking.registerEntity("document");
Output:
["Problem 1","Problem 1, Problem 2","Problem 3"]
Example
const searchEngine = new InMemorySearch();
searchEngine.addDocuments('Movies',
{name: 'Avenger', rating: 8.5, year: 2017},
{name: 'Black Adam', rating: 8.7, year: 2022},
{name: 'Jhon Wick 4', rating: 8.2, year: 2023},
{name: 'Black Panther', rating: 9.0, year: 2022}
);
console.log(searchEngine.search('Movies', (e) => e.rating > 8.5, {key:
'rating', asc: false}));
/*
[
{
"name": "Black Panther",
"rating": 9,
"year": 2022
},
{
"name": "Black Adam",
"rating": 8.7,
"year": 2022
}
]
*/
For searching, we will take the namespace and a callback function that
will filter the value based on the output of this callback function.
class InMemorySearch {
constructor() {
// to track namespace and its document
this.entities = new Map();
}
// get it filtered
const filtered = docs.filter((e) => filterFN(e));
// if orderby is requestd
if(orderByFN){
return filtered;
}
};
Test Case
Input:
const searchEngine = new InMemorySearch();
searchEngine.addDocuments('Movies',
{name: 'Avenger', rating: 8.5, year: 2017},
{name: 'Black Adam', rating: 8.7, year: 2022},
{name: 'Jhon Wick 4', rating: 8.2, year: 2023},
{name: 'Black Panther', rating: 9.0, year: 2022}
);
console.log(searchEngine.search('Movies', (e) => e.rating > 8.5, {key:
'rating', asc: false}));
Output:
/*
[
{
"name": "Black Panther",
"rating": 9,
"year": 2022
},
{
"name": "Black Adam",
"rating": 8.7,
"year": 2022
Example
const strArr = [
'Doomsayer',
'Doomguard',
'Doomhamer',
'Bane of Doom',
'Fearsome Doomguard',
'Dr. Boom',
'Majordomo',
'Shadowbomber',
'Shadowform',
'Goldshire footman'
];
fuzzySearch(strArr, query);
The most complex part of this function is creating the fuzzy search
algorithm.
current = query[++i];
};
Filter all the strings of the array through this function and return the
list with the passed ones.
const search = function(arr, query){
return arr.filter((e) => fuzzySearch(e, query));
};
console.log(search(arr, 'sh'));
Output:
["Shadowbomber","Shadowform","Goldshire footman"]
Explanation
JavaScript web API’s have a method called AbortController. This
AbortController has a property called signal that allows us to create an
AbortSignal that can be associated with the Fetch API which provides
an option to abort the API request.
HTML:
<div>
<button class="download">Download</button>
<button class="abort">Abort</button>
</div>
JavaScript:
// create abort controller
const controller = new AbortController();
const signal = controller.signal;
// download event
downloadBtn.addEventListener("click", makeCall);
// abort event
abortBtn.addEventListener("click", () => {
controller.abort();
console.log("Download aborted");
});
Example
const str = "Ultimate JavaScript / FrontEnd Guide";
const words = ['Front', 'End', 'JavaScript'];
highlight(str, words);
Test Case
Input:
const str = "Ultimate JavaScript / FrontEnd Guide";
const words = ['Front', 'End', 'JavaScript'];
console.log(highlight(str, words));
Output:
// "Ultimate <strong>JavaScript</strong> / <strong>FrontEnd</strong> Guide"
usePrevious() hook will take the current value as input and hold it and
will return it whenever it will get a new value. For the initial render, it
will return undefined as there will not be any previous value for it.
To create the usePrevious() hook we will need to use the useRef() and
useEffect() hook together.
useRef()
Between renderings, you can maintain values using the useRef() Hook
which means the value won’t change or be lost when the React
components re-render. This will help us to persist the previous value.
useEffect()
With the useEffect() hook, we can manage the side effects in the
components during the lifecycle events.
Thus we can create a new reference using useRef() and update its
value inside the useEffect() whenever a new value is provided, at the
end return the reference value.
function usePrevious(value) {
// create a new reference
const ref = useRef();
Test case
import { useState, useEffect, useRef } from "react";
// get the previous value passed into the hook on the last render
const prevCount = usePrevious(count);
For this, there are a set of events that we can listen to like mousemove,
mousedown, keypress, DOMMouseScroll, mousewheel, touchmove,
MSPointerMove.
Also, we need to handle edge cases where the window or tab is out of
focus, for which we will listen to the focus and blur events.
If any of these events are triggered then set the user to be Active, else if
none of them have happened for a given amount of time then set the
user to be Idle or Inactive.
We will take duration as input for useIdle(delay) for which if the user
is not performing any action then he will be considered as Idle.
Using useRef() we will track a setTimeout that will change status if the
user has not performed any action for the duration received as input,
else clear the timer and start a fresh timeout.
return () => {
cleanUp();
};
});
//edge case
//if tab is changed or is out of focus
window.addEventListener("blur", startTimer, false);
window.addEventListener("focus", resetTimer, false);
};
//edge case
//if tab is changed or is out of focus
window.removeEventListener("blur", startTimer);
window.removeEventListener("focus", resetTimer);
// memory leak
clearTimeout(timeoutId.current);
};
Test Case
Input:
const Example = () => {
const isIdle = useIdle(2000);
Output:
IsIdle: false
IsIdle: true // after 2 seconds
● state: It will have one of the four values ["idle", "pending", "success",
"error"] depending upon the current state of the asyncFn.
● value: If the state is successful then this will have the value returned
from the asyncFn.
● error: If the state is error then this will have the error returned from
the asyncFn.
● refetch(): This function can be used to invoke the function again and
refetch data.
Based on the input and output, let’s implement the useAsync() hook.
At the end, we will pass the immediate flag that we took as input to the
useEffect() hook that will trigger the refetch if the value of the immediate
flag changes and is true.
return asyncFn()
.then((response) => {
setState({
status: "success",
value: response,
error: null,
});
})
.catch((error) => {
setState({
status: "error",
value: null,
error: error,
});
});
}, [asyncFn]);
// state values
const { status, value, error } = state;
Test Case
Input:
//dummy api call
const fakeApiCall = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
const rnd = Math.random() * 10;
rnd <= 5 ? resolve("Success") : reject("Error");
}, 1000);
});
};
return (
<div>
<div>Status: {status}</div>
<div>Value: {value}</div>
<div>error: {error}</div>
</div>
);
};
Output:
Status: success
Value: Success
error:
Also, we will wrap the logic inside the useCallback() to avoid needless
re-renderings as the callback function returns a memoized function
that only changes when one of the dependencies changes.
// base case
// clear the timeout to assign the new timeout to it.
return debounce;
};
useEffect(() => {
window.addEventListener("mousemove", debounced, false);
return () => {
window.removeEventListener("mousemove", debounced, false);
};
});
Output:
"hello" //after 500 millisecond delay when user stops moving mouse
// immediate
const debounced = useDebounce(print, 500, true);
useEffect(() => {
window.addEventListener("mousemove", debounced, false);
return () => {
window.removeEventListener("mousemove", debounced, false);
};
});
return <></>;
};
Output:
"hello" //immediately only once till the mouse moving is not stopped
"hello" //immediately again once till the mouse moving is not stopped
If the user spam the click then this will make an API call on each click.
This is not what we want, we want to restrict the number of API calls
that can be made. The other call will be made only after a specified
interval of time.
Create a useThrottle() hook in React with the leading and trailing flag
When leading is enabled the first function will invoke right away and
then after the specified delay, while when trailing is enabled the first
function will invoke after the delay and so on.
Also, we will wrap the logic inside the useCallback() to avoid needless
re-renderings as the callback function returns a memoized function
that only changes when one of the dependencies changes.
return throttle;
};
Output:
"hello" // immediately
"hello" // after 2500 milliseconds of last call
"hello" // after 2500 milliseconds of last call
Output:
"hello" // after 2500 milliseconds
"hello" // after 2500 milliseconds of last call
"hello" // after 2500 milliseconds of last call
For this, we will assign an event listener to the window object and
listen to the resize event on the function onResizeHandler() that will
update the state whenever the user resizes the screen.
Assigning the event listener and removing it are abstracted inside two
functions Setup() and Cleanup() and it is called inside the useEffect()
hook, we have also called onResizeHandler() to initialize the value.
useEffect(() => {
// update the state on the initial load
onResizeHandler();
return () => {
// remove the event
Cleanup();
};
}, []);
return state;
};
Test Case
Input:
const Example = () => {
const { isMobile, isTablet, isDesktop } = useResponsive();
return <></>;
};
The idea is simple: we will extend the usePrevious() hook and compare
the previous values with new values and see if it has changed.
useEffect(() => {
if (previousProps.current) {
// merge the keys of previous and current data
const keys = Object.keys({ ...previousProps.current, ...props });
// check what values have changed between the previous and current
keys.forEach((key) => {
// if both are object
if (typeof props[key] === "object" && typeof
previousProps.current[key] === "object") {
if (JSON.stringify(previousProps.current[key]) !==
JSON.stringify(props[key])) {
// add to changesObj
Test Case
Input:
import React, { useEffect, useRef, useState } from "react";
return (
<div>
<div className="counter">
<Counter
count={count}
style={counterStyle}
testCaseWithArray={testCase}
function={() => console.log(count)}
/>
<button
onClick={() => {
setCount(count + 1);
setTestCase([count + 1]);
}}
>
Increment
</button>
</div>
</div>
);
}
Output:
This is causing re-renders Counter
{count: {...}, testCaseWithArray: {...}, function: {...}}
count:
from: 0
to: 1
function:
from: () => console.log(count) // 0
to: () => console.log(count) // 1
testCaseWithArray:
from: null
to: [1]
Another case is when you want to track the user activity like when a
user is starring a product (product is in the viewport) so that you can
use this data for recommendations.
For this we can create a useOnScreen() hook that will return boolean
value if the component is the view port or not.
useOnScreen() hook will set up the observation for the ref when the
component will be mounted. This will be done in the useEffect() and
then create an instance of IntersectionObserver and if the entry is
interacting, update the state to true which means it is visible,
otherwise false.
function useOnScreen(ref) {
useEffect(() => {
// assign the observer
observer.observe(ref.current);
return isIntersecting;
}
Test Case
const Element = ({ number }) => {
const ref = useRef();
const isVisible = useOnScreen(ref);
return (
<div ref={ref} className="box">
{number}
{isVisible ? `I am on screen` : `I am invisible`}
</div>
);
};
Using getBoundingClientRect()
Unlike Intersection Observer, here we will have to perform a simple
calculation to determine if the element is in the viewport or not.
If the top of the element is greater than zero but less than the
window.innerHeight then it is in the viewport. We can also add some
offset in case we want a buffer.
Assign a scroll event on the window and inside the listener get the
getBoundingClientRect() of the element. Perform the calculation and
update the state accordingly.
function useOnScreen2(ref) {
const [isIntersecting, setIntersecting] = useState(false);
useEffect(() => {
// first check
observer();
return isIntersecting;
}
Test Case
const Element = ({ number }) => {
const ref = useRef();
const isVisible = useOnScreen2(ref);
return (
<div ref={ref} className="box">
{number}
{isVisible ? `I am on screen` : `I am invisible`}
</div>
);
};
return arr;
};
For example, Google Adsense script, we can load it after once the
application is ready and the component that will display the ads is
mounted.
The idea is simple, pass the script source to the useScript() hook and it
will check if any script with this source is already injected or not, if it is
present, return 'ready' state. Else create a new script with the source
and inject it at the end of the body.
Assign event listeners on this script tag, which will update the statuses.
on successful load 'ready' and on error 'error'.
function useScript(src) {
// keep track of script status ("idle", "loading", "ready", "error")
const [status, setStatus] = useState(src ? "loading" : "idle");
useEffect(() => {
// if no url provided, set the state to be idle
if (!src) {
setStatus("idle");
return;
}
if (script) {
// if the script is already loaded, get its status and update.
// setup
script.addEventListener("load", setStateFromEvent);
script.addEventListener("error", setStateFromEvent);
// clean up
return () => {
if (script) {
script.removeEventListener("load", setStateFromEvent);
script.removeEventListener("error", setStateFromEvent);
}
};
}, [src]);
return status;
}
"https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.
js"
);
console.log(status);
return <></>;
};
Wrap this logic inside the useEffect() hook so that we can assign and
remove listeners.
useEffect(
() => {
const listener = (event) => {
// if the reference is not present
// or the target is descendant of the reference
// return
if (!ref.current || ref.current.contains(event.target)) {
return;
}
document.addEventListener("mousedown", listener);
document.addEventListener("touchstart", listener);
return () => {
document.removeEventListener("mousedown", listener);
Test Case
Input:
function Example() {
const ref = useRef();
useOnClickOutside(ref, () => {
console.log("Clicked");
});
return (
<div>
<p>Outside Click me!</p>
<p ref={ref}>Click me!</p>
</div>
);
}
Output:
"Clicked" // when clicked on Outside Click me!
Whenever the window is blurred, set the focus state to false, else
whenever the window is focused, update the state to true.
useEffect(() => {
// helper functions to update the status
const onFocus = () => setFocus(true);
const onBlur = () => setFocus(false);
Test Case
Input:
const Example = () => {
const focus = useHasFocus();
console.log(focus);
return <></>;
};
Output:
true
false // change the tab
true // back to the tab
This hook will return the current value and the toggle() method using
which we can toggle the values.
To create this hook all we need to do is track the indexes and we can
use the index to return the value from the array thus, use the
useState() hook to persist and track indexes and then use the
useCallback() hook to create the toggle function.
// define and memorize the toggler function in case we pass down the
component,
// this will move the index to the next level and reset it if it goes
beyond the limit.
const toggle = useCallback(
() => setIndex((prevIndex) => (prevIndex >= values.length - 1 ? 0 :
prevIndex + 1)),
[values]
);
Output:
currentValue: c // initially
currentValue: d // onClick
currentValue: a // onClick
currentValue: b // onClick
currentValue: c // onClick
Both operations are asynchronous and return promises. For our use,
we will use the writeText(text) and wrap this inside the try…catch
block.
We will also use the useState() hook to persist the copied text. If the
promise is fulfilled then update the state with the text else set the state
to null.
This operation will take place inside the copy() function that accepts
the text as input and tries to copy that.
Test Case
Input:
function Example() {
// call the hook which returns, copied text and the copy function
const [copiedText, copy] = useCopy();
return <button onClick={() => copy("Hello World!")}> "copiedText" :
{copiedText}</button>;
}
Output:
copiedText: // initially
copiedText: Hello World! // after click
The useLockedBody() hook will take the reference of the parent and
return the lock state and the method that will toggle the lock state.
To lock the body, we will have to remove the overflow from the body so
that everything inside it is prevented from scrolling and hide the
scrollbar.
To hide the scroll bar, get the scrollWidth of the referenced element
and add the same size right padding to the body to cover the gap.
Use a state to monitor the toggling and depending upon the toggle
state, lock or unlock the body.
// clean up
return () => {
document.body.style.overflow = originalOverflow;
if (scrollBarWidth) {
document.body.style.paddingRight = originalPaddingRight;
}
};
}, [locked]);
Test Case
Input:
const Example = () => {
const ref = useRef();
// call the hook which returns, current value and the toggler function
const [locked, setLocked] = useLockedBody(ref);
return (
<div style={{ height: "200vh" }} id="abc" ref={ref}>
<button onClick={() => setLocked(!locked)}>{locked ? "unlock scroll"
//App.js
import React, { useState } from "react";
//store number
const numberChangeHandler = (e) => {
const { value } = e.target;
setNumber(value);
basicReset();
};
//store duration
const durationChangeHandler = (e) => {
const { value } = e.target;
setDuration(value);
basicReset();
};
return (
<main style={{ width: "500px", margin: "50px auto" }}>
<section className="input-area">
<div>
<div>
<label htmlFor="number">Number:</label>{" "}
<input
id="number"
type="number"
value={number}
onChange={numberChangeHandler}
/>
</div>
<div>
<label htmlFor="duration">Duration:</label>{" "}
<input
id="duration"
type="number"
value={duration}
onChange={durationChangeHandler}
/>
</div>
</div>
<br />
<div>
<button onClick={startHandler}>start</button>{" "}
<button onClick={resetHandler}>reset</button>
</div>
</section>
</main>
);
};
Now there are two ways in which you can implement this in react,
1. Ref to the DOM element and increment the count directly in each
interval call.
2. Update the state and let react update the count.
Both of these approaches do not affect the time because all we are
doing is updating a single DOM element, if we had to update multiple
nested DOM elements then we should be using the second approach.
//CountMethods.js
import React, { useEffect, useState, useRef } from "react";
// label of counter
// number to increment to
// duration of count in seconds
const { number, duration } = props;
useEffect(() => {
let start = 0;
// first three numbers from props
const end = parseInt(number);
// if zero, return
if (start === end) return;
// dependency array
}, [number, duration]);
return (
<>
<span ref={countRef} className="Count">
{count}
</span>{" "}
{" "}
{number === count && (
<span>
| Took : <b>{timeTaken}</b> seconds to complete
</span>
)}
</>
);
};
I have also added a time log to determine exactly how much time it
takes to increment the count in order to make sure we are progressing
in the right direction.
Let’s call this function on the click of the start button inside the input
function.
// app.js
import React, { useState } from "react";
import { CountSetInterval } from "./CountMethods";
//store number
const numberChangeHandler = (e) => {
const { value } = e.target;
setNumber(value);
basicReset();
};
//store duration
const durationChangeHandler = (e) => {
const { value } = e.target;
setDuration(value);
basicReset();
};
return (
<main style={{ width: "500px", margin: "50px auto" }}>
<section className="input-area">
<div>
<div>
<label>Number:</label>{" "}
<input
type="number"
value={inputValue}
onChange={inputChangeHandler}
/>
</div>
<div>
Weird!, right?
//setTimeout
const CountSetTimeout = (props) => {
const intervalRef = useRef();
const countRef = useRef();
useEffect(() => {
let start = 0;
// first three numbers from props
const end = parseInt(number);
// if zero, return
if (start === end) return;
//invoke
counter();
// dependency array
}, [number, duration]);
return (
<>
<span ref={countRef} className="Count">
{count}
</span>{" "}
{" "}
{number === count && (
<span>
| Took : <b>{timeTaken}</b> seconds to complete
</span>
)}
</>
);
};
Let us see what happens when we invoke this function on the click of
the start button.
// app.js
import React, { useState } from "react";
import { CountSetTimeout } from "./CountMethods";
//store number
const numberChangeHandler = (e) => {
const { value } = e.target;
setNumber(value);
basicReset();
};
//store duration
const durationChangeHandler = (e) => {
const { value } = e.target;
setDuration(value);
basicReset();
};
return (
<main style={{ width: "500px", margin: "50px auto" }}>
<section className="input-area">
<div>
<div>
<label>Number:</label>{" "}
<input
type="number"
value={inputValue}
onChange={inputChangeHandler}
/>
</div>
<div>
<label>Duration:</label>{" "}
<input
type="number"
value={duration}
Output
If you read the definition of each of these methods you will realize
that.
Which means the time specified for either of these functions is the
minimum time, but it can take longer than that.
After some research on MDN, I found out that there are two major
reasons which are causing the delay.
1. Clamping.
2.Execution context
The timer can also fire later when the page (or the OS/browser itself)
is busy with other tasks. One important case to note is that the
function or code snippet cannot be executed until the thread that
called setTimeout() has terminated.
According to MDN –
In simple terms what this method does is ask the browser to perform
animation, which in turn refreshes the screen very fast. At-least 60
frames per second to perform animations.
Now using a startTime variable which stores the time before invoking
the function and this current timestamp which we receive in the
callback every time, we can recursively invoke this function for the
given duration and using a good calculation we can increment the
count.
For bigger numbers this is not incrementing the count by 1 but still the
animation is happening so fast that human eyes will not be able to
differentiate.
//Animation
const countAnimate = (obj, initVal, lastVal, duration) => {
let startTime = null;
//checking to make sure the counter does not exceed the last value
(lastVal)
if (progress < 1) {
window.requestAnimationFrame(step);
} else {
window.cancelAnimationFrame(window.requestAnimationFrame(step));
//start animating
window.requestAnimationFrame(step);
};
// app.js
//store number
const numberChangeHandler = (e) => {
const { value } = e.target;
setNumber(value);
basicReset();
};
//store duration
const durationChangeHandler = (e) => {
const { value } = e.target;
setDuration(value);
basicReset();
};
return (
<main style={{ width: "500px", margin: "50px auto" }}>
<section className="input-area">
<div>
<div>
<label>Number:</label>{" "}
<input
type="number"
value={inputValue}
onChange={inputChangeHandler}
/>
</div>
<div>
<label>Duration:</label>{" "}
<input
type="number"
value={duration}
onChange={durationChangeHandler}
/>
</div>
</div>
<br />
<div>
<button onClick={startHandler}>start</button>{" "}
<button onClick={resetHandler}>reset</button>
</div>
</section>
<br />
<section className="result-area">
<div>
Animate: <span ref={countRef}>0</span>
</div>
</section>
</main>
);
};
The question was quoted as “If a user scrolls and sees any property
and stays there for more than 5 sec then call API and store that
property”.
Apart from the online real-estate platform, this can be also applied on
other platforms as well, such as social media like Facebook where if
users read a post for a few seconds, store and use it to provide
recommendations of new posts. The same can be used on E-commerce
platforms or other platforms where products are listed.
Let us see how we should approach such problems and then solve
them with an example. I have created a dummy HTML template that
contains different blocks, which can be used for testing.
return (
<div style={style}>
<div>{index}</div>
</div>
);
};
Form an array of blocks from this element and render this inside a
wrapper so that the screen becomes scrollable and we will be able to
determine the elements that are in viewport or are visible.
Create a reference to this wrapper element so that we can get all its
children and then check which of them are visible.
// element style
const wrapperStyle = {
display: "flex",
alignItems: "center",
justifyContent: "center",
flexWrap: "wrap",
};
Finally listen to the debounced scroll event after a delay and check
what all elements are visible when the user stops scrolling.
return () => {
window.removeEventListener("scroll", debouncedApiCall);
};
}, []);
// base case
// clear the timeout to assign the new timeout to it.
// when event is fired repeatedly then this helps to reset
clearTimeout(timerId.current);
return debounce;
};
function useOnScreen(ref) {
const [isIntersecting, setIntersecting] = useState(false);
useEffect(() => {
// assign the observer
observer.observe(ref.current);
return isIntersecting;
}
return (
<div style={style}>
<div>{index}</div>
</div>
);
};
return () => {
window.removeEventListener("scroll", debouncedApiCall);
};
}, []);
// element style
const wrapperStyle = {
display: "flex",
alignItems: "center",
justifyContent: "center",
flexWrap: "wrap",
};
return (
<div style={wrapperStyle} ref={ref}>
{elementsList}
</div>
);
};
We can pass the element reference to the hook just to restrict the
selection of particular elements. Check if the selected text’s start and
end node is part of the element then only get the selection and its
coordinates.
// if text is selected
// update the selection
// and the co-ordinates
// the y position is adjusted to show bar above the selection
if (selection.toString()) {
setData({
x: x,
y: y + window.scrollY - 25,
showTools: true,
selectedText: selection.toString(),
width,
});
}
};
// handle selection
// on the mouseup event
useEffect(() => {
// add the event
document.addEventListener("mouseup", onMouseup);
Usage
const Example = () => {
const ref = useRef();
const data = useSelectionText(ref);
return (
<div>
{data.showTools && (
// position the popover according to the need
<span
style={{
position: "absolute",
left: `${data.x + data.width / 4}px`,
top: `${data.y}px`,
width: data.width / 2,
display: "inline-block",
height: "20px",
textAlign: "center",
}}
>
{/* twitter icon */}
<svg style={{ width: "24px", height: "24px" }} viewBox="0 0 24
24">
<path
fill="#000000"
d="M22.46,6C21.69,6.35 20.86,6.58 20,6.69C20.88,6.16
21.56,5.32 21.88,4.31C21.05,4.81 20.13,5.16 19.16,5.36C18.37,4.5 17.26,4
16,4C13.65,4 11.73,5.92 11.73,8.29C11.73,8.63 11.77,8.96
11.84,9.27C8.28,9.09 5.11,7.38 3,4.79C2.63,5.42 2.42,6.16
2.42,6.94C2.42,8.43 3.17,9.75 4.33,10.5C3.62,10.5 2.96,10.3 2.38,10C2.38,10
2.38,10 2.38,10.03C2.38,12.11 3.86,13.85 5.82,14.24C5.46,14.34 5.08,14.39
Output
Example
Input:
// API calls
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
We will use a dummy promise that will resolve after 1 second to mimic
the API call.
//output
const output = [];
let i = 0;
return output;
};
useEffect(() => {
// run first promise after 5 second
if (index === 0) {
setTimeout(() => {
asyncOperations(subArrays[index]);
return <></>;
};
Output
// after 5 seconds
0 (5) ['Completing 1', 'Completing 2', 'Completing 3', 'Completing 4',
'Completing 5']
1 (5) ['Completing 6', 'Completing 7', 'Completing 8', 'Completing 9',
'Completing 10']
2 (5) ['Completing 11', 'Completing 12', 'Completing 13', 'Completing 14',
'Completing 15']
3 (5) ['Completing 16', 'Completing 17', 'Completing 18', 'Completing 19',
'Completing 20']
// after 5 seconds
0 (5) ['Completing 1', 'Completing 2', 'Completing 3', 'Completing 4',
'Completing 5']
1 (5) ['Completing 6', 'Completing 7', 'Completing 8', 'Completing 9',
'Completing 10']
2 (5) ['Completing 11', 'Completing 12', 'Completing 13', 'Completing 14',
'Completing 15']
3 (5) ['Completing 16', 'Completing 17', 'Completing 18', 'Completing 19',
'Completing 20']
Example
Input:
<FormattedTime time={new Date("Sun Nov 20 2022 14:20:59")} />
Output:
3 hours ago
Get a time as input and get the difference between it with the current
time in seconds.
// messages
// time in seconds
const timeInSecond = {
MINUTE: 60,
HOUR: 60 * 60,
DAY: 24 * 60 * 60,
MONTH: 30 * 24 * 60 * 60,
YEAR: 365 * 24 * 60 * 60,
};
Check, in which range do times fit and return the message accordingly.
Encapsulate this logic inside a function that will format and return the
value.
// messages
const messages = {
NOW: "just now",
LESS_THAN_A_MINUTE: "a few secs ago",
LESS_THAN_5_MINUTES: "a minute ago",
MINUTES: "mins ago",
HOURS: "hours ago",
DAYS: "days ago",
MONTHS: "months ago",
YEARS: "years ago",
};
// time in seconds
const timeInSecond = {
MINUTE: 60,
HOUR: 60 * 60,
DAY: 24 * 60 * 60,
MONTH: 30 * 24 * 60 * 60,
YEAR: 365 * 24 * 60 * 60,
};
Test Case
Input:
<FormattedTime time={new Date("Sun Nov 20 2022 14:20:59")} />
Output:
3 hours ago
As all the circles will be in absolute position so that they can be freely
placed on the screen, we will calculate the top, bottom, left, and right
positions that will help in placement as well as detecting if two circles are
colliding.
Get the clientX and clientY coordinates when the user clicks and align the
circle around with a simple calculation so that it is placed in the center. Also
before updating the state check if the current circle is overlapping with the
existing circles then update the background color of the current.
Assign the event listener and draw the circle on the click.
return collide;
};
Generate the circles from the coordinates which we have stored after
the user has clicked. As the detection is done before making entry into
the state, the circles are generated with different colors if they collide.
// circle element
const Circle = ({ top, left, background }) => {
return (
<div
style={{
position: "absolute",
width: "200px",
height: "200px",
borderRadius: "50%",
opacity: "0.5",
background,
top,
return (
<div>
{/* render each circle */}
{elementsCoordinates.map((e) => (
<Circle {...e} key={e.top + e.left + e.right} />
))}
</div>
);
return collide;
};
// circle element
const Circle = ({ top, left, background }) => {
return (
<div
style={{
position: "absolute",
width: "200px",
height: "200px",
borderRadius: "50%",
opacity: "0.5",
background,
top,
left,
}}
></div>
);
};
return (
<div>
{/* render each circle */}
{elementsCoordinates.map((e) => (
<Circle {...e} key={e.top + e.left + e.right} />
))}
</div>
);
};
Make a point -> Validate the point -> And convince that the approach
you are explaining would be suited to the problem that you are dealing
with.
In the next part we will see two system design questions. This book
was not meant to cover system design and I am little in-experienced in
it. Maybe in the future I will write a book around it too.
Based on their privileges and role, the features of the applications will
be available to the users.
In this order, the intern will have the least privilege and the senior
manager will have the most privilege.
A higher authority can update the role of the lower authority to any
level lower than it, for example, a Senior manager can promote an
intern to the manager directly.
If the lower authority makes any change that requires approval, the
task should be in a pending state and anyone above its role should be
able to approve it.
● Route level restriction – where users with a certain role can access
certain routes.
● Feature level restriction – where users with certain roles and
privileges can perform the actions.
For example, an intern can view the employee hierarchy in the web
application but cannot perform any actions whereas a senior manager
can view and also do certain actions like promoting or demoting.
Thus after discussing with the product managers and getting clarity on
the roles and their privileges, we can design a complete hierarchy
chart with the features set and convert this to configuration to
dynamically add restrictions.
Based on the configurations add the restrictions at the route level and
the features level. Call this configuration API before login in and cache
it.
Once the user is logged in, based on their role, use the configuration
details and dynamically render the view.
A software engineer can only view the intern’s requests, a manager can
view both the software engineer’s and the intern’s requests, and so on.
How do you extend the application so that the role names can
be dynamic?
We can have alias mapping and can use the alias labels for the same
level. This way roles and privileges will remain the same but the labels
could be different.
The configuration JSON should be the single source of truth for both
the parts to minimize the chances of error.
Let us tackle each point individually and then at the end we will
discuss the overall application performance.
Problem listing
The problem listing page will have all the metadata related to the
problem like how many users have tried to solve the problem, what
was the acceptance percentage, difficulty, and upvoting.
We can list the problems in a tabular format and display the important
details like difficulties and provide an option to the user to filter the
problem based on the metadata.
They can also search the problem using a search bar with the fuzzy
search in the problem title and its description. All other options will be
available for filter thus it would not be necessary to search on that.
Because a user won’t solve all the problems at once, we can add
pagination to show only a specific number of problems at a time on the
page and for the rest, the user will have to navigate, this will be a good
performance measure.
This page will be a two section layout, where in one part questions
along with sample test cases will be described and in the other section,
the IDE will be integrated.
The question section can also have the solutions and the past results
tabs in it.
For answering, we can have one level reply for simplicity. Where only
a reply to the question will be given rather than another user’s reply.
Architecture
Micro-frontend architecture could be really helpful in boosting the
overall performance of this application.
We can then load each of them in the main application and ship it.
Each bundle will be lazy-loaded.
This way the code will be split and being small bundles they will be
faster to load. The state between each can be shared using the local
storage.