Cheatsheet for frontend interviews
Tags: frontend, career, interview, javascript
Published: 2023-08-11 04:21:25

For coding interviews, I think the biggest problem would be encountering unfamiliar questions (I guess this should be obvious) and deciding on which approach works best given the time constraints. 

What I am trying to accomplish in this article is to lay out patterns that occurs in certain sets of questions, so that when we deal with its derivative questions in the future, we can at least don't have to do too much guesswork before we even start talking about solutions.

Before we start, I want to mention that this only focuses on Javascript coding questions only, not UI questions since they can be varied and difficult to discern patterns. 

As I'm preparing for my upcoming interviews, here is a tentative list of different question types with concepts and possible patterns. 

## Promise programming 
Like the title suggests, this tests our ability to do async programming, by reinventing the wheels like `Promise.any` and `Promise.all` so that we know how it works under the hood. 

### Patterns: 
- In all cases you will loop through the iterables 

```js
promiseAny(iterable) // function similar to Promise.any
promiseAll(iterable) // similar to Promise.all

function promiseAll(iterable) {
   // ... 
   iterable.forEach(async (item, index) => {
      try {
        const value = await item;
        iterable[index] = value 
        // if function is promiseAny instead then resolve(value) 
      }
      catch (err) {
        reject(err)
      } 
   })
   // ....
}
```

Then the pseudocode would be as follows: 
```
promiseAll(iter) or promiseAny(iter) { 
    for each element in `iter`, wait for it to load
    if you return all promises, transform the input with results fetched from the elements 
    else resolve the iter as soon as it's fetched
}
```

## Rewriting `Array.prototype` methods  
The goal of this part is really for us to get used to the workflow of the function. 

### Patterns: 
- Callback functions accept at most four parameters `prev, curr, index, array`
- Callbacks in `Array.prototype.filter` and  `Array.prototype.map` will return a `Boolean` in order to transform the array calling the methods (denoted as `this`)
```js
Array.prototype.filter = function(callbackFn) {
  let copyArr = []

    for (let i = 0; i < this.length; i++) { 
        if (!this[i]) continue; 
        else if (callbackFn.call(thisArg, this[i], i, this)){ 
          copyArr.push(this[i])
        }
    }

    return copyArr 
}
```

- For `Array.prototype.reduce` the callback function will return a callback result based on previous callback result. 

```js
// in Array.prototype.reduce function 
Array.prototype.myReduce = function(callbackFn, initVal) { 
   let startingIndex = ... 
   
   for (let i = startingIndex; i < this.length; i++) {
      if (!this[i]) continue 
      result = callbackFn(result, this[i], i, this)
   }

   return result 
}
```
The general pseudocode for doing such questions would be: 

```md
arrayMethod = function (prev, curr, index, array){ 
   declare vars 
   for each element in this list
       - for reduce update `prev` value using the callback function every time taking current `prev`value as input
       - for others transform `this` array 
}
```

## Rewriting DOM APIs 
Even though front end interview questions put less emphasis on data structures and algorithms, a handful of such questions still remain for us, such as rewriting `getElementsByTagName` and `getElementsByClassName` using DOM traversal and the likes of it. 

### Patterns: 
- Using the function `traverse(element)` recursively for the elements and its offspring to check the condition of each element, for both
`getElementsByTagName` and `getElementsByClassName` 

```js
export default function getElementsByTagName(rootElement, tagNameParam) {
  const elements = [];
  const tagName = tagNameParam.toUpperCase();

  function traverse(element) {
    if (element == null) return;

    if (element.tagName === tagName) elements.push(element);

    for (const child of element.children) {
      traverse(child);
    } // recursing through its children
  }

  for (const child of rootElement.children) {
    traverse(child); // recursing through its root children 
  }

  return elements;
}
```
```js
function isSubset(a, b) {
  return Array.from(a).every((value) => b.contains(value));
}

export default function getElementsByClassName(rootElement, classNames) {
  const elements = [];
  const classNamesSet = new Set(classNames.trim().split(/\s+/));

  function traverse(element) {
    if (element == null) {
      return;
    }

    if (isSubset(classNamesSet, element.classList)) {
      elements.push(element);
    }

    for (const child of element.children) {
      traverse(child);
    }
  }

  for (const child of rootElement.children) {
    traverse(child);
  }

  return elements;
}
```
Hence the closest pseudocode describing the commonality between the two DOM APIs would be: 

```md 
getElementsByAttr(rootElement, tagName = undefined, classNames = undefined) { 
     declaring empty list of elements 
     traverse(el) { 
        check if el satisfies a condition, for ex. its tag name matches or is contained by classNames
        if so push it to the new el list
        traverse every child of el and its descendants 
     }
     traverse through root elements' offsprings 
     return updated list of elements
}
``` 

## Functional programming 
Coding questions would come in all forms and shapes, but they  will demonstrate the same set of concepts including closures, `this` keyword, using `setTimeout`/`setInterval` and Invoking functions via `Function.prototype.apply()`/`Function.prototype.call()`

### Patterns: 
For debounce/throttle questions, you might image the test cases to look like this. Sort of like when you try to press the elevator button and after a certain time passed since you last pressed it, the door finally shuts itself. As for throttling, you use it when you want to prevent rage clicking, meaning that you could only click once every x seconds. Just like in Discord's slowmode chat.

```js
let i = 0, j = 0;
function increment(val) {
  val++; 
}
const debouncedIncrement = debounce(increment(i), 100);
const throttledIncrement = throttle(increment(j), 100);

// t = 0
debouncedIncrement(); // i = 0
throttledIncrement(); // j = 1

// t = 50: i is still 0 because 100ms have not passed, throttled remains moot until t=100ms
debouncedIncrement(); // i = 0
throttledIncrement(); // j = 1 

// t = 150: Because 100ms have passed since the last debouncedIncrement() at t = 50, increment was invoked and i is now 1
throttledIncrement(); // j = 2
```
Notice that in 2 functions, the variable `timeoutId` can only be assessed and modified through `debouncedIncrement` or simply `debounce(increment(i), 100)()`. Closure hides variables behind a veil and prevent outside actors to play with it unless they know the keyword (namely which function) to access it. 

```js
export default function debounce(func, wait) {
    var timeoutId = null; 
  
    return (...args) => {
      clearTimeout(timeoutId)
      timeoutId = setTimeout(() => func.call(this, ...args), wait)
    }
}
```
```js
export default function throttle(func, wait) {
    let canBeToggledAgain = true;
  
    return (...args) => {
      if (canBeToggledAgain) {
        func.apply(this, args)
        canBeToggledAgain = false
        setTimeout(() => canBeToggledAgain = true, wait)
      }    
    }
}
```
Without further ado, here's the pseudocode for the 2 function s above 
```md
function throttle || debounce () { 
  set timeoutId or toggle boolean 
  return function if timeout ends ( clear timer if necessary ) or when condition to do so is right
}