Menu
CoddyTech

JavaScript Closures, Scope and Hoisting Interview Questions

How JavaScript decides which variable a name refers to, and the closure patterns built on it.

10 questions
  1. What is a closure in JavaScript?

    A closure is a function that keeps access to the variables of the scope it was created in, even after that scope has finished. Each outer call creates a separate set.

    JavaScript
    function makeCounter() {
      let count = 0;
      return {
        increment: () => ++count,
        get: () => count,
      };
    }
    const a = makeCounter();
    const b = makeCounter();
    a.increment();
    a.increment();
    b.increment();
    console.log(a.get(), b.get());

    a and b count separately, and nothing outside can touch count. More in closures.

  2. What is hoisting in JavaScript?

    Declarations are processed before the code runs, so names exist from the start of their scope. Function declarations are hoisted with their body, var as undefined, and let, const and class cannot be used before their line.

    JavaScript
    console.log(greet());
    console.log(typeof later);
    function greet() {
      return 'hoisted';
    }
    var later = () => 'not yet';
    
    try {
      console.log(notYet);
    } catch (e) {
      console.log(e.name);
    }
    let notYet = 1;

    A function expression assigned to var behaves like the var, so typeof later is undefined.

  3. What is the temporal dead zone?

    The temporal dead zone is the time between entering a scope and the line that declares a let, const or class; touching the name then throws a ReferenceError.

    JavaScript
    function check() {
      try {
        return value;
      } catch (e) {
        return e.message;
      }
    }
    console.log(check());
    const value = 1;
    console.log(check());

    It is about when code runs, not where it is written: the second call, after the declaration, returns 1.

  4. What is scope in JavaScript, and what is lexical scope?

    Scope is where a name is visible: global, module, function or block (let, const, class inside {}). Lexical scope means a function sees the variables where it was written, not where it is called.

    const level = 'global';
    function outer() {
      const level = 'outer';
      return function inner() {
        return level; // 'outer', found by walking up where inner was defined
      };
    }
    

    Lookups walk outward to the global scope, and a name never found throws a ReferenceError.

  5. What do arrow functions pushed in a var loop return when called?

    JavaScript
    const fns = [];
    for (var i = 0; i < 3; i++) {
      fns.push(() => i);
    }
    console.log(fns.map(f => f()).join(' '));

    var makes one i shared by all three arrows, and it is 3 by the time they run. With let, each iteration gets its own i and it prints 0 1 2.

  6. What is an IIFE and why was it used?

    An IIFE (immediately invoked function expression) is defined and called in one expression, giving its variables a private scope.

    JavaScript
    const config = (() => {
      const secret = 'abc123';
      return { masked: secret.slice(0, 2) + '****' };
    })();
    console.log(config.masked, typeof secret);

    Modules and block scope replaced it for privacy; it survives as (async () => { ... })() to use await at the top level of a script.

  7. How do you write a memoize function?

    Memoization caches results by argument, so a repeated call returns the stored answer. A closure holds the cache.

    JavaScript
    function memoize(fn) {
      const cache = new Map();
      return n => {
        if (cache.has(n)) return cache.get(n);
        const result = fn(n);
        cache.set(n, result);
        return result;
      };
    }
    let calls = 0;
    const square = memoize(n => {
      calls++;
      return n * n;
    });
    square(4);
    square(4);
    square(5);
    console.log(calls);

    calls is 2 because the second square(4) hit the cache. It suits only pure functions, and an unbounded cache leaks memory.

    Practice it: Climbing Stairs, Fibonacci Number

  8. What is the difference between debounce and throttle?

    Debounce runs once after calls stop for a set time; throttle runs at most once per time window. Debounce suits search boxes, throttle suits scroll handlers.

    JavaScript
    function debounce(fn, ms) {
      let timer;
      return (...args) => {
        clearTimeout(timer);
        timer = setTimeout(() => fn(...args), ms);
      };
    }
    function throttle(fn, ms) {
      let last = 0;
      return (...args) => {
        const now = Date.now();
        if (now - last >= ms) {
          last = now;
          fn(...args);
        }
      };
    }
    const search = debounce(q => console.log('searching', q), 50);
    search('j');
    search('ja');
    search('jav');
    
    const onScroll = throttle(y => console.log('scroll', y), 1000);
    onScroll(1);
    onScroll(2);
    onScroll(3);

    This prints scroll 1 at once (calls 2 and 3 fall in the same window), then searching jav 50 ms later.

  9. What is variable shadowing, and what is illegal shadowing in JavaScript?

    Shadowing is declaring a variable with the same name in an inner scope; inside it, the inner one hides the outer. Illegal shadowing is a var in a block that already sits in the scope of a let with that name: var would escape the block and clash, so it is a SyntaxError.

    JavaScript
    let level = 'global';
    function show() {
      let level = 'function';
      {
        let level = 'block';
        console.log(level);
      }
      console.log(level);
    }
    show();
    console.log(level);
    
    try {
      new Function('let a = 1; { var a = 2; }');
    } catch (e) {
      console.log(e.name);
    }
    new Function('var b = 1; { let b = 2; }');
    console.log('let inside var scope is fine');

    The reverse, a let in a block inside a var's scope, is allowed.

  10. How do you write a once function that runs a callback only one time?

    Return a wrapper that closes over a flag and the first result: the first call runs the function, later calls return the saved result.

    JavaScript
    function once(fn) {
      let called = false;
      let result;
      return function (...args) {
        if (!called) {
          called = true;
          result = fn.apply(this, args);
        }
        return result;
      };
    }
    
    const connect = once((name) => {
      console.log('connecting as', name);
      return 42;
    });
    console.log(connect('ada'), connect('bob'));

    It logs connecting as ada once, then 42 42. Using a regular function for the wrapper keeps this, so it also works on methods. It is the same closure pattern as a counter, used for initialization code.

Coddy programming languages illustration

Learn to code with Coddy

Get started