DEV Community

CallmeHongmaybe
CallmeHongmaybe

Posted on

Cheatsheet for frontend interviews

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
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)
      } 
   })
   // ....
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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)
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 
}
Enter fullscreen mode Exit fullscreen mode
  • For Array.prototype.reduce the callback function will return a callback result based on previous callback result.
// 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 
}
Enter fullscreen mode Exit fullscreen mode

The general pseudocode for doing such questions would be:

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 
}
Enter fullscreen mode Exit fullscreen mode

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
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;
}
Enter fullscreen mode Exit fullscreen mode
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;
}
Enter fullscreen mode Exit fullscreen mode

Hence the closest pseudocode describing the commonality between the two DOM APIs would be:

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
}
Enter fullscreen mode Exit fullscreen mode

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.

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
Enter fullscreen mode Exit fullscreen mode

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.

export default function debounce(func, wait) {
    var timeoutId = null; 

    return (...args) => {
      clearTimeout(timeoutId)
      timeoutId = setTimeout(() => func.call(this, ...args), wait)
    }
}
Enter fullscreen mode Exit fullscreen mode
export default function throttle(func, wait) {
    let canBeToggledAgain = true;

    return (...args) => {
      if (canBeToggledAgain) {
        func.apply(this, args)
        canBeToggledAgain = false
        setTimeout(() => canBeToggledAgain = true, wait)
      }    
    }
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)