Top JavaScript Frontend Interview Coding Questions Explained: this, Event Loop, Debounce, EventBus, and React Closures

This is a quick-reference guide to the most common handwritten frontend interview questions. It covers this binding, hoisting, the event loop, debounce, EventBus, and React closure pitfalls to help developers build answer frameworks they can explain, write by hand, and reproduce under pressure. Keywords: JavaScript, event loop, coding interview questions.

This article focuses on the six JavaScript coding question categories that appear most often in frontend interviews

Parameter Description
Language JavaScript
License Compiled from an original CSDN article, with the page declaring CC 4.0 BY-SA
Stars Not provided; this is not a GitHub project
Core Dependencies Native JavaScript, Promise, setTimeout, React Hooks

The original content is essentially an interview question checklist. Its value does not come from the number of questions, but from how tightly it covers the most common topics. It maps directly to the standard “write the answer, explain the mechanism, then implement it” pattern frequently used in top-tier frontend interviews.

C Zhidao

AI Visual Insight: The image shows the entry point to CSDN’s sidebar AI reading assistant, highlighting features such as knowledge expansion, core summaries, and code review. This indicates that the original page is not just a plain question bank, but part of a broader learning and code-explanation experience.

Questions about this are fundamentally about the call site, not the definition site

There are three common traps in this questions: standalone invocation of regular functions, lexical binding in arrow functions, and explicit binding with call, apply, or bind. Interviewers usually do not stop at asking for the output. They often follow up by asking why obj.getArrow()() and obj.getName() produce different results.

const obj = {
  value: 42,
  getValue() {
    console.log(this.value); // Regular method call; this points to obj
  },
  getAsync() {
    setTimeout(() => {
      console.log(this.value); // Arrow function inherits outer this, still obj
    }, 100);
  }
};

obj.getValue();
obj.getAsync();

This example shows that an arrow function does not bind its own this. Instead, it captures the this value from the outer execution context at definition time.

Hoisting questions primarily test declaration hoisting and the temporal dead zone

Function declarations and var are both hoisted, but they do not behave the same way. let and const also enter the lexical environment, but they remain inside the temporal dead zone before initialization. Many candidates can recite the conclusion, but cannot clearly explain the difference between the creation phase and the execution phase.

function test() {
  console.log(a); // Outputs function a, because function declarations are hoisted and initialized first
  var a = '变量';
  function a() {
    return '函数';
  }
  console.log(a); // After assignment, outputs the string "变量"
}

test();

This code demonstrates that when names collide, the function declaration takes the slot first, and then the var assignment overwrites it during execution.

Event loop questions require you to distinguish synchronous code, microtasks, and macrotasks first

The event loop is almost always a must-have topic in first-round interviews at major companies. A reliable answer template is: run synchronous code first, then flush the microtask queue, and finally move to the next macrotask. As long as this ordering framework stays clear, even complex questions become manageable.

console.log('1');

setTimeout(() => {
  console.log('2'); // Macrotask execution
  Promise.resolve().then(() => console.log('3')); // Register another microtask inside the macrotask
}, 0);

Promise.resolve().then(() => {
  console.log('4'); // Microtask in the current turn
});

console.log('5');

The output order is 1 5 4 2 3, because microtasks are always flushed before the runtime proceeds to the next macrotask.

Debounce implementation questions test timer control and edge-case handling

In high-frequency interview scenarios, the expectation is usually not just a minimal debounce function. Interviewers often continue with follow-up questions about leading, cancel, preserving this, and forwarding arguments. Whether you can complete these edge cases directly reflects your engineering maturity.

function debounce(func, wait, options = {}) {
  let timer = null;
  let lastTime = 0;
  const leading = options.leading ?? false;

  function debounced(...args) {
    const now = Date.now();

    if (leading) {
      if (now - lastTime >= wait) {
        func.apply(this, args); // Preserve this at call time
        lastTime = now;
      }
      return;
    }

    clearTimeout(timer);
    timer = setTimeout(() => {
      func.apply(this, args); // Delay execution and forward arguments
      timer = null;
    }, wait);
  }

  debounced.cancel = function () {
    clearTimeout(timer); // Manually cancel the pending task
    timer = null;
    lastTime = 0;
  };

  return debounced;
}

This implementation supports basic debounce behavior, immediate execution mode, and cancellation. It is a reusable template for interviews.

EventBus coding questions reflect your ability to abstract the publish-subscribe pattern

These questions often use on / emit / off / once as the minimum API surface. The core challenge is not the API names themselves, but the event bucket data structure and how once uses a wrapper function to automatically unsubscribe.

class EventBus {
  constructor() {
    this.events = {}; // Event registry: eventName -> callbacks[]
  }

  on(eventName, callback) {
    if (!this.events[eventName]) this.events[eventName] = [];
    this.events[eventName].push(callback);
  }

  emit(eventName, payload) {
    if (!this.events[eventName]) return;
    this.events[eventName].forEach((fn) => fn(payload)); // Trigger subscribers in sequence
  }

  off(eventName, callback) {
    if (!this.events[eventName]) return;
    this.events[eventName] = this.events[eventName].filter((fn) => fn !== callback);
  }

  once(eventName, callback) {
    const wrapper = (payload) => {
      callback(payload); // Execute first
      this.off(eventName, wrapper); // Then remove it to ensure it only runs once
    };
    this.on(eventName, wrapper);
  }
}

This code implements a minimal but usable event bus and covers most interview follow-up questions.

React closure questions are fundamentally caused by old render snapshots captured by timers

useEffect(() => {}, []) runs only once during the initial render, so the timer captures the count value from that first render. This is not a React-specific exception. It is the combined result of closures and render snapshots.

import React, { useEffect, useRef, useState } from 'react';

export default function App() {
  const [count, setCount] = useState(0);
  const countRef = useRef(count);

  countRef.current = count; // Sync the latest value on every render

  useEffect(() => {
    const timer = setInterval(() => {
      console.log(countRef.current); // Read from ref to avoid stale closure values
    }, 1000);

    return () => clearInterval(timer);
  }, []);

  return <button onClick={() => setCount(count + 1)}>+1</button>;
}

The key to this fix is to store the latest state in a ref instead of relying on the stale value captured by the effect’s initial closure.

You should build reusable problem-solving frameworks first when preparing for frontend coding interviews

A practical way to organize preparation is to divide these questions into three layers: language mechanism questions, asynchronous scheduling questions, and implementation design questions. Language questions emphasize explanation, async questions emphasize execution order, and design questions emphasize API shape and edge cases. This approach is much more efficient than solving random problems blindly.

High-frequency supplementary checklist

  • Array flattening
  • Deep clone and circular references
  • Promise.all
  • LRU cache
  • call / apply / bind
  • Currying
  • Publish-subscribe pattern

FAQ

1. Why are this questions so easy to get wrong?

Most mistakes come from guessing this based on where a function is defined. The correct approach is to inspect how the function is called first, and then determine whether an arrow function or explicit binding is involved.

2. What is the fastest way to solve event loop questions?

Write down all synchronous output first, then mark the microtasks, and finally arrange the macrotasks. If a macrotask registers another microtask, remember that the new microtask runs immediately after the current macrotask finishes.

3. How can coding questions demonstrate engineering ability in an interview?

Do not stop at the shortest working answer. Proactively include edge-case handling, cancellation support, this preservation, argument forwarding, and error scenarios. These details matter more than simply making the code run.

Core summary: This guide systematically reviews the most common frontend interview coding questions around this binding, hoisting, the event loop, debounce, EventBus, and React closure pitfalls. It extracts reusable answer strategies, code templates, and common mistakes you should avoid.