Skip to content

Instantly share code, notes, and snippets.

@tuhuynh27
Last active April 17, 2024 02:33
Show Gist options
  • Save tuhuynh27/59f58623bc986f82b151108b222c16f8 to your computer and use it in GitHub Desktop.
Save tuhuynh27/59f58623bc986f82b151108b222c16f8 to your computer and use it in GitHub Desktop.
FE Checklist

Coding exercise

Algorithm & Data Structure

JavaScript

Promise/async

Modify main function to use async/await instead of Promise. Do not modify the asyncCalculate function.

// Do not modify this function
function asyncCalculate(a, b) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (isNaN(a) || isNaN(b)) {
                reject(new Error('a or b is not a number'));
            } else {
                resolve(a + b);
            }
        }, 1000);
    });
}

// Update the below function
function main() {
    asyncCalculate(1, 2).then(result => {
        console.log(result);
    }).catch(error => {
        console.log(error);
    });
}

Expected result:

async function main() {
    try {
        const result = await asyncFunction(1, 2);
        console.log(result);
    } catch (error) {
        console.log(error);
    }
}

Bonus questions:

  • What different between Promise and async function? (Expected: async function can work well in loops, eg: resolve array of promises one by one)
  • How to wait for multiple promises? (Expected: use Promise.all or Promise.allSettled)
Promise/async (Advance)

Modify the below class to achieve the expected behaviour:

// Update the below class
class Chaining {
    eat() {
        
    }
    
    sleep() {
        
    }
    
    work() {
        
    }
}

new Chaining().eat().sleep(2).work()
// Expected: print eat, sleep for 2 seconds, print work
// *sleep for 2 seconds mean print nothing in 2 seconds

new Chaining().sleep(2).eat().work().sleep(1).eat()
// Expected: sleep for 2 seconds, print eat, print work, sleep for 1 seconds, print eat

Expected result:

class Chaining {
  taskQueue = Promise.resolve()
  addTask = (f) => {
    this.taskQueue = this.taskQueue.then(f)
  }
  eat() {
    this.addTask(() => console.log('eat'))
    return this
  }
  work() {
    this.addTask(() => console.log('work'))
    return this
  }
  sleep(s) {
    this.addTask(
      () =>
        new Promise((r) => {
          setTimeout(() => {
            r()
          }, s * 1000)
        })
    )
    return this
  }
}

// new Chain().eat().work().sleep(2).work().eat().sleep(1).work()
Closure

Write a useState function that can do the following:

// Modify the below function
function useState(initialState) {
    // do something
}

// Do not modify the below function
function main() {
    const [state, setState] = useState(0);
    console.log(state()); // returns 0
    setState(1);
    console.log(state()); // returns 1
    setState(2);
    console.log(state()); // returns 2
}

Expected result:

function useState(initValue) {
    let state = initValue;
    function getState() {
        return state;
    }
    function setState(newValue) {
        state = newValue;
    }
    return [getState, setState];
}
Currying (closure advanced)

Currying is a transformation of functions that translates a function from callable as f(a, b, c) into callable as f(a)(b)(c).

Currying does not call a function. It just transforms it.

Write a curry function that can do the following:

// Modify the below function
function curry(f) {
    // do something
}

// Do not modify the below function
function main() {
    const sum = (a, b) => {
        return a + b;
    };
    const curriedSum = curry(sum);
    curriedSum(1)(2); // returns 3
}

Expected result:

function curry(f) {
    return function(a) {
        return function(b) {
            return f(a, b);
        };
    };
}
Memoization

Write a function that can memoize the result of a function:

// Modify the below function
function memoize(func) {
    // do something
}

// Do not modify the below function
function main() {
    const func = (a, b) => {
        console.log('Doing some calculation');
        return a + b;
    };
    const memoized = memoize(func);
    memoized(1, 2); // returns 3
    memoized(1, 2); // returns 3 (from cache)
    memoized(1, 3); // returns 4
    memoized(1, 3); // returns 4 (from cache)
}

Expected result:

function memoize(func) {
    const cache = {};
    return function(...args) {
        if (cache[args]) {
            return cache[args];
        }
        const result = func(...args);
        cache[args] = result;
        return result;
    }
}

JavaScript Debug

This context

Given the following code:

const Animal = (name, type) => {
  this.name = name;
  this.type = type;
  this.age = 0;
}

Animal.prototype.birthday = function () {
  this.age++;
}

const leo = new Animal('Leo', 'Lion');

// Run -> Uncaught TypeError: Cannot set properties of undefined (setting 'birthday')

Explanation:

Arrow functions don’t have their own this. This leads to three errors in our code.

First, we’re adding properties to this in the constructor function. Again, because Arrow Functions don’t have their own this, you can’t do that.

Second, we can’t use the new keyword with an Arrow Function. This will throw a X is not a constructor error.

Third, we can’t add a property on a function’s prototype if that function is an arrow function, again, because there’s no this.

Solution:

function Animal (name, type) {
  this.name = name;
  this.type = type;
  this.age = 0;
}

Animal.prototype.birthday = function () {
  this.age++;
}

const leo = new Animal('Leo', 'Lion');
Scoping issue

Given the following code:

function todoReducer(state, action) {
    switch (action.type) {
        case "ADD_TODO":
            const todos = state.todos;
            return { ...state, todos: todos.concat(action.payload) };
        case "REMOVE_TODO":
            const todos = state.todos;
            const newTodos = todos.filter(todo => todo.id !== action.id);
            return { ...state, todos: newTodos };
        default:
            return state;
    }
}

Explanation:

The todos variable is being re-declared within the same block. Variables declared with const and let are block-scoped, meaning they don’t exist outside of the block they were called in. Blocks are created with {} brackets around if/else statements, loops, and functions.

There are many solutions, including changing the variable name in the different cases or removing the variable altogether. We can also wrap each of our cases in a block to isolate the variables.

Solution:

function todoReducer(state, action) {
    switch (action.type) {
        case "ADD_TODO": {
            const todos = state.todos;
            return { ...state, todos: todos.concat(action.payload) };
        }
        case "REMOVE_TODO": {
            const todos = state.todos;
            const newTodos = todos.filter((todo) => todo.id !== action.id);
            return { ...state, todos: newTodos };
        }
        default:
            return state;
        }
}

Styling

  • Write a CSS class that put a div tag center in both the horizontal and vertical axis.

React

Counter (basic state test)

Write a Component that will take 2 props as below

import React, { useState } from "react";

export default function Timer({ total = 0, onTimerFinish }) {
  const [count, setCount] = useState(total);

  return <>{count}</>;
}
  • total: the total number of times the timer should count down
  • onTimerFinish: a function that will be called when the timer finishes
  • The component should have a count state that starts at total
  • Every second, the component should decrement the count state by 1
  • When finished, the component should call the onTimerFinish function

Expected result:

export default function Timer({ total = 0, onTimerFinish }) {
  const [count, setCount] = useState(total);

  useEffect(() => {
    const myInterval = setInterval(() => {
      if (count > 0) {
        setCount(count - 1);
      }

      if (count === 0) {
        if (onTimerFinish) {
          onTimerFinish();
        }
        clearInterval(myInterval);
      }
    }, 1000);

    return () => {
      clearInterval(myInterval);
    };
  }, [count, onTimerFinish]);

  return <>{count}</>;
}
Dual Counter ("isolate re-render" knowledge test)

Write a component to render 2 counters and 2 buttons:

function CountButton({ onClick, count }) {
  return <button onClick={onClick}>{count}</button>
}

function DualCounter() {
  const [count1, setCount1] = React.useState(0)
  const increment1 = () => setCount1(c => c + 1)

  const [count2, setCount2] = React.useState(0)
  const increment2 = () => setCount2(c => c + 1)

  return (
    <>
      <CountButton count={count1} onClick={increment1} />
      <CountButton count={count2} onClick={increment2} />
    </>
  )
}

Every time you click on either of those buttons, the DualCounter's state changes and therefore re-renders which in turn will re-render both of the CountButton. However, the only one that actually needs to re-render is the one that was clicked right? So if you click the first one, the second one gets re-rendered, but nothing changes. We call this an "unnecessary re-render."

Most of the time you should not bother optimizing unnecessary re-renders. React is very fast and there are so many things I can think of for you to do with your time that would be better than optimizing things like this.

However, there are situations when rendering can take a substantial amount of time (think highly interactive Graphs/Charts/Animations/etc.). Thanks to the pragmatistic nature of React, there's an escape hatch:

const CountButton = React.memo(function CountButton({ onClick, count }) {
  return <button onClick={onClick}>{count}</button>
})

Now React will only re-render CountButton when its props change! Woo! But we're not done yet. Remember that whole referential equality thing? In the DualCounter component, we're defining the increment1 and increment2 functions within the component functions which means every time DualCounter is re-rendered, those functions will be new and therefore React will re-render both of the CountButtons anyway.

So this is the other situation where useCallback and useMemo can be of help:

const CountButton = React.memo(function CountButton({ onClick, count }) {
  return <button onClick={onClick}>{count}</button>
})

function DualCounter() {
  const [count1, setCount1] = React.useState(0)
  const increment1 = React.useCallback(() => setCount1(c => c + 1), [])

  const [count2, setCount2] = React.useState(0)
  const increment2 = React.useCallback(() => setCount2(c => c + 1), [])

  return (
    <>
      <CountButton count={count1} onClick={increment1} />
      <CountButton count={count2} onClick={increment2} />
    </>
  )
}

Now we can avoid the so-called "unnecessary re-renders" of CountButton.

I would like to re-iterate that I strongly advise against using React.memo (or it's friends PureComponent* and shouldComponentUpdate) without measuring because those optimizations come with a cost and you need to make sure you know what that cost will be as well as the associated benefit so you can determine whether it will actually be helpful (and not harmful) in your case, and as we observe above it can be tricky to get right all the time, so you may not be reaping any benefits at all anyway.

Technical questions

Fundamentals

Browser under-the-hood

When you type "google.com" into your browser, that will happen when you type enter till everything is displayed on your screen?

  • DNS lookup (in case you already access google.com before and also in case you do not know the IP of google.com)
  • Which protocol DNS use and why?
  • The other of place to look up DNS.
  • TCP or UDP will be used in this case? why?
  • How to know "google.com" require HTTP or HTTPS? how browser can know and redirect from HTTP to HTTPS?
  • After you get the HTML content for "google.com" how to get the *.js and image files?
  • When getting *.js or image files do why use another TCP connection or use the same one as in the get HTML content? How DNS lookup work in this case?
  • After your browser display "google.com" fully, is there any connection open? -> HTTP1.1 Keep-Alive
  • Caching can apply to which steps? How caching applied? How to know if the cache is used?

JavaScript

  • What is ES5, ES6, ES7, ES8? What is polyfill?
  • What is the difference between var and let? How about const?
  • What does "JavaScript is an asynchronous, single-thread language" mean?
  • How does browser's Event-Loop work? -> Microtask vs Macrotask
  • What is requestAnimationFrame? How it works? When to use it?
  • What is requestIdleCallback? How it works? When to use it?
  • What are the primitive types of JavaScript/TypeScript

Styling

  • How do you manage the styling? -> BEM, CSS in JS, Styled-Components, etc. What is the pros/cons -> Why not atomic CSS (Tailwind)?
  • “Should I use pixels or ems/rems?!”
Details ems/rems

The truth is, if you want to build the most-accessible product possible, you need to use both pixels and ems/rems. It's not an either/or situation. There are circumstances where rems are more accessible, and other circumstances where pixels are more accessible.

To solve this problem, the CSS language designers created the rem unit. It stands for Root EM.

The rem unit is like the em unit, except it's always a multiple of the font size on the root node, the <html> element. It ignores any inherited font sizes, and always calculates based on the top-level node.

Documents have a default font size of 16px, which means that 1rem has a “native” value of 16px.* We can re-define the value of 1rem by changing the font-size on the root node. Remember earlier, when we said that1remwas equal to 16px? That's only true if the user hasn't touched their default font size! If they boost their default font size to 32px, each rem will now be 32px instead of 16.

React related questions

  • How/When does React re-render? Virtual DOM? What is React's reconciliation? -> Two phases commit?
  • Class component vs Function component? When to use Class over Function? -> Error boundary is a good example
  • Why we need React.memo or React.PureComponent, how about useCallback and useMemo?
  • Which are you using for state management? How do you use them? -> Compare Redux vs Mobx vs Redux-saga vs Redux-thunk
  • Should I use Context or Redux? Why? -> Context by itself is not a state management system, it’s a dependency injection mechanism
  • What is React Lazy Loading? How do you use it? -> React.lazy()
  • What do you know about Next.js? Why server-side rendering? Server-side rendering vs client-side rendering?
  • What is React 18 Concurrent Mode?
  • React Native sounds like a good choice but some companies decided to migrate back to two separate code bases for iOS and Android? What is your take?

Opinions and Seniority

  • What is the difference between React/Vue/Angular, which one do you like better? -> Re-render mechanism how different?
  • Do you write tests? How do you write them? How about E2E tests? Do you use TypeScript?
  • In your opinion, when to reach for Redux/state management tools?
  • Three principles of Redux:
    • Single source of truth: The state is a single source of truth.
    • State is read-only: The state cannot be changed from outside the component.
    • Changes are made with pure functions: The state is only changed by passing an action to the store.
  • What can we do to improve web application performance?
  • What is Progressive Web App? What are the benefits of it?
  • How you build a frontend app? What is Webpack/Babel? Webpack vs Vite? Code splitting?
  • How do you host a frontend app? -> Use a CDN? How about cloud service like Vercel, Netlify?
  • How do you debug/benchmark your frontend app?
  • How do you monitor your frontend app? -> Use Sentry/logging tool?
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment