Table of Content
01. Implement Debounce - Easy
02. Implement Throttle - Medium
03. Implement Currying - Easy
04. Implement Currying with Placeholders - Medium
js
05. Deep Flatten I - Easy
06. Deep Flatten II - Easy
07. Deep Flatten III - Easy
w.
08. Deep Flatten IV - Hard
09. Negative Indexing in Arrays (Proxies) - Medium
10. Implement a Pipe Method - Easy
e
11. Implement Auto-retry Promises - Medium
12. Implement Promise.all - Medium
vi
13. Implement Promise.allSettled - Medium
14. Implement Promise.any - Medium
r
15. Implement Promise.race - Easy
te
16. Implement Promise.finally - Medium
17. Implement Custom Javascript Promises - Super Hard
18. Throttling Promises by Batching - Medium
In
19. Implement Custom Deep Equal - Hard
20. Implement Custom Object.assign - Medium
21. Implement Custom JSON.stringify - Hard
22. Implement Custom JSON.parse - Super Hard
23. Implement Custom typeof operator - Medium
24. Implement Custom lodash _.get() - Medium
25. Implement Custom lodash _.set() - Medium
26. Implement Custom lodash _.omit() - Medium
27. Implement Custom String Tokenizer - Medium
28. Implement Custom setTimeout - Medium
29. Implement Custom setInterval - Medium
30. Implement Custom clearAllTimers - Easy
31. Implement Custom Event Emitter - Medium
32. Implement Custom Browser History - Medium
33. Implement Custom lodash _.chunk() - Medium
js
34. Implement Custom Deep Clone - Medium
35. Promisify the Async Callbacks - Easy
w.
36. Implement 'N' async tasks in Series - Hard
37. Implement 'N' async tasks in Parallel - Medium
38. Implement 'N' async tasks in Race - Easy
e
39. Implement Custom Object.is() method - Easy
40. Implement Custom lodash _.partial() - Medium
vi
41. Implement Custom lodash _.once() - Medium
42. Implement Custom trim() operation - Medium
43. Implement Custom reduce() method - Medium
r
44. Implement Custom lodash _.memoize() - Medium
te
45. Implement Custom memoizeLast() method - Medium
46. Implement Custom call() method - Medium
47. Implement Custom apply() method - Medium
In
48. Implement Custom bind() method - Medium
49. Implement Custom React "classnames" library - Medium
50. Implement Custom Redux used "Immer" library - Medium
51. Implement Custom Virtual DOM - I (Serialize) - Hard
52. Implement Custom Virtual DOM - II (Deserialize) - Medium
53. Implement Memoize/Cache identical API calls - Hard
Implement Custom Object.assign
Problem Statement
Implement a function `objectAssign()` which is a polyfill of the
built-in `Object.assign()` function. And you should not use the
built-in function directly for the problem, instead write your
js
own version.
w.
What does `Object.assign()` do?
By definition, it copies all enumerable own properties from
e
one or more source objects to a target object and returns the
target object.
vi
So, the `Object.assign()` will be passed a target object and
any number of source objects as inputs.
r
te
Note:
➔Enumerable - Enumerability refers to whether a property
In
can be iterated over or accessed using iteration methods
like Object.keys or for..in loop
➔Own (Ownership) - Ownership determines whether a
property belongs to the object directly or is inherited from
its prototype chain.
We’ll talk about these in great depth below.
Example
const target = { a: 1, b: 2 };
const source = { c: 4, d: 5 };
objectAssign(target, source);
console.log(target);
// { a: 1, b: 2, c: 4, d: 5 }
js
// --------------------------------------
w.
const target = { a: 1 };
const source1 = { b: 2 };
const source2 = { c: 30 };
const source3 = { d: 40 };
e
objectAssign(target, source1, source2, source3);
vi
console.log(target);
// { a: 1, b: 2, c: 30, d: 40 }
r
// --------------------------------------
te
// When target and the source has the same property name, the
value of source's property will override the value in target
In
const target = { a: 1, b: 20 };
const source1 = { b: 2, c: 3 };
const source2 = { c: 30, d: 4 };
objectAssign(target, source1, source2);
console.log(target);
// { a: 1, b: 2, c: 30, d: 4 }
// --------------------------------------
const target = { a: 1 };
const source = { b: 5 };
const returnedTarget = objectAssign(target, source);
// The `returnedTarget` will be the same `target` obj which was
passed as input
console.log(returnedTarget === target); // true
js
Concepts
w.
Before jumping straight away to the solution or
implementation we need to understand a few concepts which
are important for the problem.
e
1. Objects - Property Flags and descriptors :
vi
On a general note we know, objects can store properties,
where each property is a “key-value” pair. But there’s more to
object properties.
r
te
Property Flags :
We know the fact that in an object a key has a value. But the
fact is the value which we have mapped to a `key` in the
In
object is also an object which has an attribute of value in it
which ultimately stores our value. But, when we try to
access/write the property we don’t see it as an object (actually
it’s an internal implementation of the language).
We usually don’t see them while doing console.log for an
object because generally they do not show up. Although
JavaScript provides us some utility methods to get those.
Refer below example -
const user = {
name: "Peter"
}
// The below method helps us to get the descriptor object (full
js
information about a property) for the property 'name' from the
object 'user'
let descriptor = Object.getOwnPropertyDescriptor(user, 'name');
w.
console.log(descriptor);
/* This below object is basically called as 'property
descriptor'
{
e
value: 'Peter', // This what we were talking about
writable: true,
vi
enumerable: true,
configurable: true
}
r
*/
te
Also note from the above example, the property descriptor
object mapped to the `name` key in our `user` object has 3
In
more special attributes (so-called "flags").
➔writable - if `true`, the value can be changed/modified,
else it’s read-only.
➔enumerable - if `true`, then listed in loops (or) the
property can be iterated, otherwise not listed.
➔configurable - if `true`, the property can be deleted and
these 3 attributes can be modified, otherwise not.
// We can use Object.defineProperty() method to set these
attributes for a property for the give object
Object.defineProperty(object, propertyName, descriptor);
Let’s see some example in code:
js
const user = {
age: 20
}
w.
// `writable` set to false
Object.defineProperty(user, 'name', {
value: 'Peter',
writable: false,
enumerable: true,
e
vi
configurable: true
})
r
console.log(user.name); // Peter
user.name = 'Parker';
te
console.log(user.name); // Peter
// Re-defining the descriptor of the property 'name'
In
// `writable` set to true
Object.defineProperty(user, 'name', {
value: 'Parker',
writable: true,
enumerable: true,
configurable: true
})
console.log(user.name); // Parker
user.name = 'Parker';
console.log(user.name); // Parker
// -------------------------------------------
const user = {
age: 20,
dept: 'cse'
}
js
// `enumerable` set to false
Object.defineProperty(user, 'name', {
value: 'Peter',
w.
writable: true,
enumerable: false,
configurable: true
})
e
for (let key in user) {
console.log(key);
vi
}
// 'age', 'dept'
r
console.log(Object.keys(user));
// ['age', 'dept'] - 'name' property will be ignored
te
// Re-defining the descriptor of the property 'name'
In
// `enumerable` set to true
Object.defineProperty(user, 'name', {
value: 'Parker',
writable: true,
enumerable: true,
configurable: true
})
for (let key in user) {
console.log(key);
}
// 'age', 'dept', 'name'
console.log(Object.keys(user));
// ['age', 'dept', 'name']
// -------------------------------------------
const user = {
js
age: 20,
}
w.
// `configurable` set to false
Object.defineProperty(user, 'name', {
value: 'Peter',
writable: true,
})
enumerable: true,e
configurable: false
vi
// Let's try redefining the descriptors of the property 'name'
r
// trying `writable` set to false
te
Object.defineProperty(user, 'name', {
value: 'Parker',
writable: false,
In
enumerable: true,
configurable: true
})
// throws [Error]: Cannot redefine property: name
// trying `enumerable` set to false
Object.defineProperty(user, 'name', {
value: 'Parker',
writable: true,
enumerable: false,
configurable: true
})
// throws [Error]: Cannot redefine property: name
// trying `configurable` set to false
Object.defineProperty(user, 'name', {
value: 'Parker',
js
writable: true,
enumerable: true,
configurable: true
w.
})
// throws [Error]: Cannot redefine property: name
// Yes, we can't also change the `configured` flag back to true
2. Ownership:
e
Ownership determines whether a property belongs to an
vi
object directly or is inherited from its prototype chain. It is
important to differentiate between own and inherited
r
properties.
te
Own properties are defined directly on the object itself.
In
Ever wondered about methods like `.toUpperCase()` on
strings and `.reverse()` on Arrays and properties like `.length`
on Arrays? We never defined them or implemented them on
our objects but they still exist.
The answer is that these methods come built-in within each
type of data structure thanks to something called prototype
inheritance.
In JavaScript, an object can inherit properties of another object.
The object from where the properties are inherited is called the
prototype. In short, objects can inherit properties from other
objects — the prototypes.
// Use hasOwnProperty() and hasOwn() methods to check if own
property or inherited property
js
const obj = { name: 'Peter' };
console.log(obj.hasOwnProperty('name')); // true
console.log(Object.hasOwn(obj, 'name')); // true
w.
const newObj = { age: 20 };
e
const parent = { name: 'Peter' };
vi
Object.setPrototypeOf(newObj, parent);
console.log(newObj.hasOwnProperty('name')); // false
console.log(Object.hasOwn(newObj, 'name')); // false
r
console.log(newObj.hasOwnProperty('age')); // true
te
console.log(newObj.name); // 'Peter' (property accessed from
parent in prototype chain)
Note: When you iterate using Object.keys() methods or normal
In
for..in loop only own and enumerable properties will be iterated
on.
3. Symbols :
(Although the concept of how `Symbol` works is not that
necessary to be known to solve this problem, but it’s definitely
a good-to-learn)
Only two primitive types are allowed to be used as keys in
objects.
➔string type
➔symbol type
Note: When you try to set a key of type number it’s
automatically converted to string. So, obj[1] is the same as
obj["1"] and obj[true] is the same as obj["true"].
js
Symbols basically are "always" a unique value returned.
w.
Symbols can be used to be used as a key in an object.
// You can't see what the value is, since it's meant to be
private as per spec e
// Instead it just prints `Symbol()`
vi
let sym = Symbol();
console.log(sym); // Symbol()
// Since Symbols always will be unique, that's why we can use
r
these for keys in objects
te
Symbol() === Symbol(); // false
// A symbol can also be passed an optional description as an
argument
In
const symWithDesc = Symbol('id');
console.log(symWithDesc); // Symbol(id)
// Since same symbol reference
console.log(symWithDesc === symWithDesc); // true
// Everytime the constructor of Symbol is called it returns a
unique value even if the description for both are same - 'id'
console.log(Symbol('id') === Symbol('id')); // false
// 1. Global Symbols: You can have the same Symbol with
description declared globally to access anywhere in your
codebase
// 2. Use Symbol with `.for()` method. This method creates a
global Symbol value for the passed description 'world' (as in
below case) and can be accessed anywhere across your codebase
// 3. `.for()` creates a new Symbol with the description with
doesn't exists and returns,
// if Symbol with given description already exists then returns
js
the same Symbol value
console.log(Symbol.for('world')); // Symbol(world)
console.log(Symbol.for('world') === Symbol.for('world')); //
w.
true
// Note: We have to use the square-bracket notation to declare a
symbol-type property
const obj = { e
name: 'Peter', // string-type property
[Symbol('age')]: 19 // symbol-type property
vi
}
console.log(obj);
r
// { name: 'Peter', [Symbol(age)]: 19 }
te
// Note: String-type and Symbol-type are completely different
console.log(typeof 'Hello'); // string
In
console.log(typeof Symbol()); // symbol
console.log(typeof Symbol('Hello')); // symbol
Note: Any string type property used as key for an object will
be `enumerable` as `true` by default (i.e. while looping the key
will be shown). But the Symbol type property used as key on
the other hand will have the enumerable as `false` by default
(i.e. while looping the key will not be shown). As per the spec,
Symbol type is non-enumerable since it indicates private
information.
Implementation
`objectAssign()` function takes a target object as input and
also any number of source objects.
js
1. If the target is not a valid object like `null` or `undefined` it
should throw an exception.
w.
2. Loop over all the source objects individually and collect
all properties (keys) of the source object both
enumerable and non-enumerable. Assign only the
e
enumerable properties.
3. If the target and both source objects have the same
vi
property (key) and if the property in the target object has
the flag writable set to `false` then we should throw an
exception.
r
te
Below is the full implementation in code,
In
// We collect all the source objects using the rest operator
function objectAssign(target, ...sources) {
if (target === null || target === undefined) {
throw new Error('target should not be null or
undefined');
}
// Using the Object constructor to wrap and turn primitives
like number, boolean, string, BigInt, NaN into its wrapper
Object form
target = Object(target);
// loop through all source objects and assign properties to
target object
for (let source of sources) {
if (source === null || source === undefined) {
continue; // skip if the source is not a valid object
}
js
// Using the Object constructor to wrap and turn
primitives like number, boolean, string, BigInt, NaN into its
wrapper Object form
w.
source = Object(source);
// Reflect.ownKeys returns an array of own property keys
including string and symbol (both enumerable and non-enumerable)
e
const keys = Reflect.ownKeys(source);
const descriptors =
Object.getOwnPropertyDescriptors(source);
vi
keys.forEach(key => {
const targetDescriptor =
r
Object.getOwnPropertyDescriptor(target, key);
te
if (targetDescriptor && targetDescriptor.writable ===
false) {
throw new Error(`Property ${key} is not writable
In
to target`);
}
// Remember the definition of Object.assign() method
// We should assign only enumerable properties of the
source. So if the property on the source is enumerable, assign
it to target.
if (descriptors[key].enumerable) {
target[key] = source[key];
}
})
}
return target;
}
Test Cases
js
const target = null;
const source = { name: 'Hello' };
w.
objectAssign(target, source);
// Error: target should not be null or undefined
// -------------------------------------------
const target = 'world';
e
vi
const source = { name: 'Hello' };
console.log(objectAssign(target, source));
// { name: 'Hello' }
r
te
// -------------------------------------------
const target = Infinity;
const source = { name: 'Hello' };
In
console.log(objectAssign(target, source));
// { name: 'Hello' }
// -------------------------------------------
const target = {};
const source = { name: 'Hello' };
console.log(objectAssign(target, source));
// { name: 'Hello' }
// -------------------------------------------
const target = { a: 1, b: 2 };
const source = { c: 30, d: -1 };
console.log(objectAssign(target, source));
// { a: 1, b: 2, c: 30, d: -1 }
js
// -------------------------------------------
w.
const target = { a: 1, b: 2 };
const source = { c: 30, b: -1 };
console.log(objectAssign(target, source));
e
// { a: 1, b: -1, c: 30 }
// -------------------------------------------
vi
const target = { a: 1, b: 2 };
const source1 = { c: 40, d: -1 };
r
const source2 = { e: 50, a: 0 };
const source3 = { f: 3, f: -1 };
te
console.log(objectAssign(target, source1, source2, source3));
// { a: 0, b: 2, c: 40, d: -1, e: 50, f: -1 }
In
// -------------------------------------------
const source = { name: 'Peter' };
Object.defineProperty(source, 'age', {
value: '19',
writable: true,
enumerable: false,
configurable: true
});
const target = {};
console.log(objectAssign(target, source));
// { name: 'Peter' } - 'age' doesn't appear since non-enumerable
// -------------------------------------------
const target = { name: 'Peter', age: '19' };
js
Object.defineProperty(target, 'name', {
value: 'Parker',
writable: false,
w.
enumerable: true,
configurable: true
});
console.log(target); e
// { name: 'Parker', age: '19' }
vi
const source = { name: 'Spiderman', age: '19' };
console.log(objectAssign(target, source));
r
// Error: Property name is not writable to target
te
// -------------------------------------------
const target = { a: 1, b: 2 };
In
const source1 = { c: 30, b: -1 };
const source2 = null;
const source3 = NaN;
const source4 = { d: 100 };
console.log(objectAssign(target, source1, source2, source3,
source4));
// { a: 1, b: -1, c: 30, d: 100 }
// -------------------------------------------
const target = { name: 'Spiderman', age: '19' };
const source = { age: '29' };
Object.defineProperty(source, Symbol('name'), {
value: 'Peter',
writable: true,
enumerable: true,
configurable: true
js
});
console.log(source);
w.
// { age: '29', [Symbol(name)]: 'Peter' }
console.log(objectAssign(target, source));
// { name: 'Spiderman', age: '29', [Symbol(name)]: 'Peter' }
e
// -------------------------------------------
vi
const target = { name: 'Spiderman', age: '19' };
// Symbol(name) property set to false
r
const source = { age: '29' };
Object.defineProperty(source, Symbol('name'), {
te
value: 'Peter',
writable: true,
enumerable: false,
In
configurable: true
});
console.log(source);
// { age: '29' }
console.log(objectAssign(target, source));
// { name: 'Spiderman', age: '29' }
Implement Custom lodash _.get()
Problem Statement
Implement a function `get()` which is your own version of the
lodash’s `_.get()` method. This method is used to get the
value from the object based on the path given as input.
js
The `get()` method accepts 3 parameters as input - object,
w.
path, defaultValue.
➔object - the actual object from which the value has to be
retrieved e
➔path - (Array/String) denoting the path to access the
vi
property (key) from the object
➔defaultValue - An optional value used to be returned from
the `get()` method when no property is found in the
r
object based on the path
te
// Syntax
function get(obj, path, defaultValue) {}
In
Example
const obj = {
a: {
b: 'Hello',
c: null,
d: [1, 2, 'World'],
e: [
{ name: 'Peter Parker' },
{ work: 'Spiderman' }
],
h: {
i: {
j: 'Iron Man',
k: 'Batman'
}
js
}
},
f: {
w.
g: undefined
}
}
e
get(obj, 'a.h.i.j', 'Key Not Found');
// Iron Man
vi
get(obj, 'a.c', 'Key Not Found');
// null
r
get(obj, 'f.g.h.i', 'Key Not Found');
// Key Not Found
te
get(obj, ['a', 'e', '1', 'work'], 'Key Not Found');
// Spiderman
In
get(obj, 'a.d[2]', 'Key Not Found');
// World
get(obj, 'f[g]', 'Key Not Found');
// undefined
Note: The path is either a string with dot notations or square
bracket notations (applicable for both arrays and objects) as
well. Or the path can also be an array of keys passed in array
positions.
Implementation
Let’s go ahead with implementing the solution :
➔The first thing you have to do is parse the path making it
js
a valid array of keys.
➔Then evaluate the current key for its existence in the
w.
object.
➔If all the keys are traversed or processed in the object,
return the value, else recursively continue processing to
get the value. e
vi
The first thing we need to do is to parse the input to a
common notation. That is, we don’t want square brackets, so
replace them with dot notation.
r
te
function get(obj, path, defaultValue) {
// if the input is not an array and instead a string
if (!Array.isArray(path)) {
In
// Below 2 lines replaces all the square bracket notation
`[]` with dot notation `.`
// This makes our job of parsing easy
// Example : 'a.b[c]' -> 'a.b.c', 'a.b.c[0][1]' ->
'a.b.c.0.1', 'a[1].b.c' -> a.1.b.c
path = path.replaceAll('[', '.');
path = path.replaceAll(']', '');
}
}
Now let’s move to the full implementation :
function get(obj, path, defaultValue) {
// The `obj` passed should be a valid object
// Note: Array is also an object
if (obj === null || typeof obj !== 'object') {
return defaultValue;
}
js
// First step is to replace all of the square bracket
notation [] with dot notation
// This will work for accessing values from both Objects and
w.
arrays
let keys = [];
if (!Array.isArray(path)) {
path = path.replaceAll('[', '.');
e
path = path.replaceAll(']',
keys = path.split('.');
'');
vi
}
else {
keys = path;
r
}
te
const currKey = keys[0];
// Means we have processed all the keys in the path, so just
return the value for the key
In
if (keys.length === 1) {
// We use `hasOwnProperty` method to check if a key
exists on the object
// Using `obj[currKey]` is not good, since there can be a
falsy value as well like null, undefined, '' (which are
completely valid)
// So the aim should be to check if the property was
defined on the object or not
return obj.hasOwnProperty(currKey) ? obj[currKey] :
defaultValue;
}
else {
// Recursively continue traversing the path on the object
to get the value
if (obj.hasOwnProperty(currKey)) {
return get(obj[currKey], keys.slice(1),
defaultValue);
}
js
return defaultValue;
}
}
w.
Test Cases
const obj
a: {
= { e
vi
b: 'Hello',
c: null,
d: [1, 2, 'World'],
e: [
r
{ name: 'Peter Parker' },
te
{ work: 'Spiderman' }
],
h: {
i: {
In
j: 'Iron Man',
k: 'Batman'
}
}
},
f: {
g: undefined
}
}
console.log(get(obj, 'a.b', 'Key Not Found'));
// Hello
console.log(get(obj, ['a', 'h', 'i', 'k'], 'Key Not Found'));
// Batman
console.log(get(obj, 'a[b]', 'Key Not Found'));
// Hello
js
console.log(get(obj, ['a', 'e', '1', 'work'], 'Key Not Found'));
// Spiderman
w.
console.log(get(obj, 'a[d].1', 'Key Not Found'));
// 2
console.log(get(obj, 'a.d.2', 'Key Not Found'));
// World e
console.log(get(obj, 'a.d.3', 'Key Not Found'));
vi
// Key Not Found
console.log(get(obj, 'a[d][0]', 'Key Not Found'));
r
// 1
te
console.log(get(obj, 'a.e.0.name', 'Key Not Found'));
// Peter Parker
In
console.log(get(obj, 'f.g', 'Key Not Found'));
// undefined
console.log(get(obj, 'f.g.h.i.j.k', 'Key Not Found'));
// Key Not Found
Implement Custom bind() method
Problem Statement
Implement a function `bind()` which is your own version of the
`Function.prototype.bind` method. You should not use the
in-built `bind()` method.
js
This problem is exactly the same as those of the previous
w.
problems, 46. Implement Custom call() method and 47.
Implement Custom apply() method
e
Recommend you to go through those first and understand the
concepts discussed.
vi
The `bind()` method takes an object as the first argument and
sets it as the "this" context (kind of permanently) for the
r
function which needs to be invoked.
te
The only difference of `bind()` method to that of the `call()`
method is -
In
Basically it will return a newly bound function with the "this"
context passed, so whenever the bound function is invoked,
automatically the "this" context set on it will be applied.
The `bind()` method then optionally takes any arbitrary
number of arguments that needs to be passed to the function
that needs to be invoked.
Example
function printName(role, city) {
return `${this.firstname} ${this.lastname} - ${role} -
${city}`;
}
js
const spidermanObj = {
firstname: "Peter",
lastname: "Parker",
w.
printName
}
const printSpiderman = printName.bind(spidermanObj, 'spiderman',
'NY');
printSpiderman();
e
vi
// Peter Parker - spiderman - NY
printSpiderman();
r
// Peter Parker - spiderman - NY
te
const ironmanObj = {
firstname: "Tony",
lastname: "Stark",
In
printName
}
const printIronman = printName.bind(ironmanObj, 'ironman',
'NJ');
console.log(printIronman());
// Tony Stark - ironman - NJ
console.log(printIronman());
// Tony Stark - ironman - NJ
We won't be discussing all the important concepts again, as
we have discussed it already in the previous problem here,
46. Implement Custom call() method (Recommended to read
out that first)
js
Implementation
w.
Function.prototype.bind = function myBind(thisArg, ...args) {
// Get the actual function that needs to be invoked and on
which we need to set the "this" context
const fn = this;
e
// We know the fact that the "this" context for a method will
vi
be set based on the object which calls it
// `thisArg` will be our object or context we need to set
const context = Object(thisArg);
r
// Note: We wrap `thisArg` in an Object constructor, to
te
handle primitive values as well like null, undefined, number,
string, which returns their wrapper objects
In
// Generate a unique key using `Symbol()` to avoid object
property conflicts
const key = Symbol();
// Set the invoking function `fn` as a value for the unique
`key` as a property on the object `context`
context[key] = fn;
// This is the only major change we did, by wrapping it in a
new function
return function() {
// Now all we need to do is invoke `fn` via `context`
object and pass the additional args as well
const result = context[key](...args);
// We won't be deleting `context[key]` since this
returned function can be invoked many times, so we need to
persist it
js
// Return away the result generated by invoking the
function `fn`
return result;
w.
}
}
e
r vi
te
Note:
Rest of the premium content is added in the complete
In
Interview.js Ebook - A 400 pager Optimised Bundle of 50+
Solved Interview Questions. Link to the book