JS PollyFillls Machine Coding Interview

JS PollyFillls Machine Coding Interview

This blog will give you the experience of how Pollyfills questions are asked in the interviews.

ยท

5 min read

In this blog you will get an understanding of how to approach a PollyFill implementation question in an interview. I will include proper examples of some popular questions which which will help you relate the different scenarios of how and what to answer in the interview.

1. Array.prototype.includes (Easy)

๐Ÿ’ป Implement a polyfill for Array.prototype.includes without using indexOf or built-in array methods.

if( !Array.prototype.myIncludes){
    Array.prototype.myIncludes = function(searchElement , startIndex = 0){
        startIndex = startIndex>=0 ? startIndex: Math.max( 0, this.length-Math.abs(startIndex));
        for( let i = startIndex; i< this.length; i++){
            if( this[i] == searchElement || ( Number.isNaN(this[i]) && Number.isNaN(searchElement)) ){
                return true;
            }
            // if (Object.is(this[i], searchElement)) return true; (Alternative)
        }
        return false;
    }
}

โ“ Conceptual Questions:

  • How does Array.prototype.includes differ from Array.prototype.indexOf?

    Answer

    • includes returns true or false depending on whether the element exists in the array, while indexOf returns the index of the first occurrence or -1 if the element is not found.

    • includes performs a strict equality check (===), but it correctly identifies NaN, whereas indexOf does not because NaN !== NaN.

    • indexOf is useful for getting the position of an element, while includes is better for simple existence checks.

  • Why does Array.prototype.includes return true for NaN, while indexOf does not?

    Answer

    • indexOf relies on strict equality (===), and NaN !== NaN, so indexOf(NaN) always returns -1.

    • includes uses the SameValueZero algorithm, which treats NaN as equal to NaN.

  • How does startIndex work when it is negative?

    Answer

    • If startIndex is negative then we have to search the element from this.length-startIndex to the end of the array. If the startIndexis very low value then we will have to search the entire array.

        const arr = [10, 20, 30, 40, 50];
        console.log(arr.includes(30, -2)); // false (starts searching from index 3)
      

2. Object.assign (Medium)

โ“ Conceptual Questions:

  1. What is Object.assign used for?

    • Object.assign is used to copy enumerable own properties from one or more source objects to a target object.

    • It performs a shallow copy, meaning nested objects are still referenced rather than deeply copied.

    • It is commonly used for merging objects, creating object clones, and adding properties dynamically.

  2. What happens if Object.assign receives null or undefined as a target or source?

    • If the target is null or undefined, it throws a TypeError because assignment requires an object.

    • If a source is null or undefined, it is ignored, and no properties are copied.

    Object.assign({}, null, { a: 1 }); // { a: 1 }
    Object.assign(null, { a: 1 }); // TypeError
  1. How does Object.assign handle inherited properties?

    • Object.assign only copies own properties of source objects, not properties from the prototype chain.

    • It copies both string keys and symbol keys, but only if they belong to the object itself.

    const proto = { inherited: 'I am inherited' };
    const obj = Object.create(proto);
    obj.own = 'I am ram';

    const copy = Object.assign({}, obj);
    console.log(copy); // { own: 'I am ram' } (inherited property is not copied)

๐Ÿ’ป Implement a polyfill for Object.assign

if( !Object.myAssign){
    Object.myAssign = function(target, ...sources){
        if(target == null) 
        throw new TypeError("target cannot be null as we cannot convert null/undefined to an object. Pass {} as target in the calling function.")
        let res = Object(target);
        for( let source of sources){
            if( source != null){
                // In the object there can be keys / Symbols / Inherited properties
                // Object.assign only copies the keys and symbols and not inherited properties.
                for(let key  of Object.keys(source) ){
                    res[key] = source[key];
                }
                for(let symbol of Object.getOwnPropertySymbols(sources)){
                    res[symbol] = source[symbol];
                }
            }
        }
        return res;
    }
}

const obj1 = { a: 1 };
const obj2 = { b: 2, [Symbol('c')]: 3 };
const obj3 = Object.create({ inherited: 4 }, { d: { value: 5, enumerable: true } });
console.log(Object.myAssign({}, obj1, obj2, obj3)); 
// { a: 1, b: 2, d: 5, [Symbol(c)]: 3 }

Bonus :

Read about Enumerable Properties

How Properties Become Enumerable or Not?

  1. Properties defined using object literals ({}) or Object.defineProperty():

    • By default, properties in object literals are enumerable.

    • If defined using Object.defineProperty(), you must explicitly set enumerable: true if you want them to be enumerable.

    const obj1 = { x: 10 }; // enumerable by default
    const obj2 = {};
    Object.defineProperty(obj2, 'y', { value: 20, enumerable: false });

    console.log(Object.keys(obj1)); // ['x']
    console.log(Object.keys(obj2)); // [] (because 'y' is non-enumerable)
  1. Built-in properties and methods (e.g., from prototypes) are usually non-enumerable:

    • Many built-in properties, like length in arrays and methods on prototypes, are non-enumerable.
    console.log(Object.keys([])); // [] (does not include 'length')
    console.log([].propertyIsEnumerable('length')); // false
  1. Symbol properties are not enumerable by default in Object.keys() and for...in:

    • But they can be retrieved using Object.getOwnPropertySymbols().
    const sym = Symbol('hidden');
    const obj = { visible: 1, [sym]: 2 };

    console.log(Object.keys(obj)); // ['visible'] (Symbol property is skipped)
    console.log(Object.getOwnPropertySymbols(obj)); // [Symbol(hidden)]

Summary

MethodIncludes Enumerable Properties?Includes Non-Enumerable?Includes Symbols?
Object.keys(obj)โœ… YesโŒ NoโŒ No
Object.values(obj)โœ… YesโŒ NoโŒ No
Object.entries(obj)โœ… YesโŒ NoโŒ No
Object.getOwnPropertyNames(obj)โœ… Yesโœ… YesโŒ No
Object.getOwnPropertySymbols(obj)โŒ NoโŒ Noโœ… Yes
for...inโœ… Yes (own + inherited)โŒ NoโŒ No

๐Ÿ‘‰ Enumerable properties are those that appear when iterating with for...in or Object.keys(), unless explicitly made non-enumerable using Object.defineProperty()! ๐Ÿš€

ย