Level 2 – Intermediate (Functional Programming + Async Control) Flashcards

Focus: closures in async, currying chains, throttling, event systems (10 cards)

1
Q

Infinite Arithmetic Currying

Description:
You are to implement a function chainCalc(initialValue) that allows chaining of arithmetic operations. The operations are specified in a chainable manner, and evaluated in left-to-right order. The final result is returned when .value() is called.

✅ Requirements:
* Support operations: “+”, “-“, “*”, “/” (strings).
* The chain may be extended with as many operations as needed.
* The .value() method must return the evaluated result.
* Maintain correct order of operations as written (no operator precedence).
* Do not use eval, Function, .toString, or global object references.

💡 Examples

chainCalc(10)("+", 5)("*", 2).value();      // → (10 + 5) * 2 = 30
chainCalc(100)("/", 4)("-", 5).value();     // → (100 / 4) - 5 = 20
chainCalc(1)("+", 2)("+", 3)("+", 4).value(); // → 10
chainCalc(10).value();                     // → 10

🔒 Constraints
Disallowed patterns (checked by the test suite):
* eval
* Function constructor
* .toString
* Global object references like window, globalThis, self
* Use of global variables to store state

Code:

// chainCalc.js

function chainCalc(initialValue) {
  // TODO: Return a chainable object with an API like:
  // .value() to get the result
  // and .call(operator, operand) chaining like: chainCalc(1)("+", 2).value()
}

✅ Test Suite

// enforce-chainCalc.js

(function enforceChainCalcConstraints() {
  const source = chainCalc.toString();
  const forbidden = [
    "eval",
    "Function",
    ".toString",
    "window",
    "globalThis",
    "self"
  ];
  forbidden.forEach(term => {
    if (source.includes(term)) {
      throw new Error(`❌ Forbidden pattern used in chainCalc(): "${term}" is not allowed.`);
    }
  });
})();

// test-chainCalc.js

function runChainCalcTests() {
  const assert = (desc, actual, expected) => {
    const pass = actual === expected;
    console.log(`${pass ? "✅" : "❌"} ${desc}`);
    if (!pass) console.log(`   Expected: ${expected}, but got: ${actual}`);
  };

  try {
    assert("Single value returns correctly", chainCalc(10).value(), 10);
    assert("Simple addition", chainCalc(1)("+", 2).value(), 3);
    assert("Multiple additions", chainCalc(1)("+", 2)("+", 3).value(), 6);
    assert("Mixed operations", chainCalc(10)("+", 5)("*", 2).value(), 30);
    assert("Subtraction and division", chainCalc(100)("/", 4)("-", 5).value(), 20);
    assert("Long chain", chainCalc(1)("+", 1)("*", 5)("-", 3).value(), 7);
    assert("No operations", chainCalc(42).value(), 42);
  } catch (err) {
    console.error("❌ Runtime error during tests:", err);
  }
}

runChainCalcTests();
A
function chainCalc(initialValue) {
  let result = initialValue;

  function calculator(operator, operand) {
    if (operator === "+") result += operand;
    else if (operator === "-") result -= operand;
    else if (operator === "*") result *= operand;
    else if (operator === "/") result /= operand;
    return calculator;
  }

  calculator.value = function () {
    return result;
  };

  return calculator;
}
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
2
Q

Implement Debounce Utility

Description:
Your task is to implement the debounce function that returns a debounced version of a given function fn. The debounced function delays the execution of fn until after delay milliseconds have passed since the last time the debounced function was called.

This is useful for rate-limiting tasks like window resizing, API requests on input change, etc.

Requirements:
Return a function that:
* Cancels the previous timer (if any),
* Resets the timer with each call,
* Executes fn after the delay (in milliseconds),
* Uses the correct this context and arguments when calling fn.

💡 Examples

const debouncedFn = debounce(() => console.log("run"), 300);

debouncedFn(); // call 1
debouncedFn(); // call 2
debouncedFn(); // call 3

// Only 1 call to "run" should happen, 300ms after the last call.

🔒 Constraints
Your implementation must NOT use any of the following:
* eval
* Function constructor
* toString
* Global references like window, globalThis, self
These patterns are forbidden and will be checked by enforcement logic.

Code:

/**
 * Debounce implementation stub
 * @param {Function} fn - the function to debounce
 * @param {number} delay - milliseconds to wait
 * @returns {Function} - debounced function
 */
// debounce.js
function debounce(fn, delay) {
  // TODO: Return a debounced version of `fn` using `setTimeout` and `clearTimeout`
}
// enforce-debounce.js

(function enforceDebounceConstraints() {
  const source = debounce.toString();
  const forbidden = [
    "eval",
    "Function",
    ".toString",
    "window",
    "globalThis",
    "self"
  ];
  forbidden.forEach(term => {
    if (source.includes(term)) {
      throw new Error(`❌ Forbidden pattern used in debounce(): "${term}" is not allowed.`);
    }
  });
})();
// test-debounce.js

function runDebounceTests() {
  let callCount = 0;
  const output = [];

  const fn = () => {
    callCount++;
    output.push("called");
  };

  const debouncedFn = debounce(fn, 200);

  const assert = (desc, actual, expected) => {
    const pass = actual === expected;
    console.log(`${pass ? "✅" : "❌"} ${desc}`);
    if (!pass) {
      console.log(`   Expected: ${expected}, but got: ${actual}`);
    }
  };

  // Simulate rapid calls
  debouncedFn();
  setTimeout(debouncedFn, 50);
  setTimeout(debouncedFn, 100);
  setTimeout(() => {
    assert("Only one call after debounce delay", callCount, 1);
    assert("Output should have one call entry", output.length, 1);
  }, 400);
}

runDebounceTests();
A
/**
 * Debounce implementation
 * @param {Function} fn - the function to debounce
 * @param {number} delay - milliseconds to wait
 * @returns {Function} - debounced function
 */
function debounce(fn, delay) {
  let timer;
  return function (...args) {
    const context = this;
    clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply(context, args);
    }, delay);
  };
}
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
3
Q

Throttle Function Implementation

Write a function throttle that limits the rate at which the input function fn can be invoked.

Once the throttled function is called, it should immediately invoke fn, and then suppress any subsequent calls for the specified delay (in milliseconds). After the delay, it can be called again.

This is particularly useful for handling events like scrolling or resizing efficiently.

📌 Requirements:
throttle(fn, delay) returns a function that:
* Executes fn immediately on the first call.
* Ignores subsequent calls within the delay window.
* Uses the correct this context and arguments when invoking fn.

💡 Examples

const throttledFn = throttle(() => console.log("called"), 1000);

throttledFn(); // → immediately logs "called"
throttledFn(); // → ignored
setTimeout(throttledFn, 500);  // → ignored
setTimeout(throttledFn, 1100); // → logs "called"

🔒 Constraints
The following are strictly forbidden and will be enforced automatically:
* eval
* Function constructor
* .toString
* Global references like window, globalThis, self

Code:

/**
 * Throttle implementation stub
 * @param {Function} fn - the function to throttle
 * @param {number} timeLimit - milliseconds between allowed calls
 * @returns {Function} - throttled version of fn
 */
// throttle.js
function throttle(fn, delay) {
  // TODO: Return a throttled version of `fn`
}

✅ Test Suite

// enforce-throttle.js

(function enforceThrottleConstraints() {
  const source = throttle.toString();
  const forbidden = [
    "eval",
    "Function",
    ".toString",
    "window",
    "globalThis",
    "self"
  ];
  forbidden.forEach(term => {
    if (source.includes(term)) {
      throw new Error(`❌ Forbidden pattern used in throttle(): "${term}" is not allowed.`);
    }
  });
})();

// test-throttle.js

function runThrottleTests() {
  let calls = 0;
  const output = [];

  const fn = () => {
    calls++;
    output.push("called");
  };

  const throttledFn = throttle(fn, 300);

  const assert = (desc, actual, expected) => {
    const pass = actual === expected;
    console.log(`${pass ? "✅" : "❌"} ${desc}`);
    if (!pass) {
      console.log(`   Expected: ${expected}, but got: ${actual}`);
    }
  };

  throttledFn();                // Should trigger
  throttledFn();                // Ignored
  setTimeout(throttledFn, 100); // Ignored
  setTimeout(throttledFn, 310); // Should trigger
  setTimeout(throttledFn, 320); // Ignored
  setTimeout(() => {
    assert("Throttle allows 2 calls in this timeframe", calls, 2);
    assert("Output reflects correct throttle behavior", output.length, 2);
    console.log("🎉 throttle tests completed.");
  }, 700);
}

runThrottleTests();
A
/**
 * Throttle implementation
 * @param {Function} fn - the function to throttle
 * @param {number} timeLimit - milliseconds between allowed calls
 * @returns {Function} - throttled version of fn
 */
function throttle(fn, delay) {
  let throttled = false;

  return function (...args) {
    if (!throttled) {
      fn.apply(this, args);
      throttled = true;
      setTimeout(() => {
        throttled = false;
      }, delay);
    }
  };
}
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
4
Q

Recursively freeze object

Description
Write a function deepFreeze(obj) that takes an object and recursively freezes all nested objects and arrays within it, so that the entire structure becomes immutable.

JavaScript provides Object.freeze(), but it only performs a shallow freeze. Your implementation should ensure that all nested levels are frozen too.

💡 Requirements
* All nested objects and arrays should be recursively frozen.
* The original object should be returned.
* No new properties can be added or modified at any depth after freezing.
* Circular references will not be tested.

🧪 Example Usage

const data = {
  user: {
    name: "Alice",
    settings: {
      darkMode: true
    }
  },
  tags: ["js", "freeze"]
};

const frozen = deepFreeze(data);
frozen.user.name = "Bob";             // Ignored or fails silently in strict mode
frozen.user.settings.darkMode = false; // Ignored
frozen.tags.push("new");              // Fails
console.log(Object.isFrozen(frozen));                   // true
console.log(Object.isFrozen(frozen.user));              // true
console.log(Object.isFrozen(frozen.user.settings));     // true
console.log(Object.isFrozen(frozen.tags));              // true

🔒 Constraints
The following patterns are forbidden and will be automatically enforced:
* eval
* Function constructor
* .toString
* Global references like window, globalThis, self
* JSON-based tricks like JSON.parse(JSON.stringify(…))

Code

// deepFreeze.js

function deepFreeze(obj) {
  // TODO: Recursively freeze all nested objects and arrays in `obj`
  return obj;
}

🧪 Test Suite

// enforce-deepFreeze.js

(function enforceDeepFreezeConstraints() {
  const source = deepFreeze.toString();
  const forbidden = [
    "eval",
    "Function",
    ".toString",
    "window",
    "globalThis",
    "self",
    "JSON.parse",
    "JSON.stringify"
  ];
  forbidden.forEach(term => {
    if (source.includes(term)) {
      throw new Error(`❌ Forbidden pattern used in deepFreeze(): "${term}" is not allowed.`);
    }
  });
})();

// test-deepFreeze.js

function runDeepFreezeTests() {
  const assert = (desc, actual) => {
    console.log(`${actual ? "✅" : "❌"} ${desc}`);
    if (!actual) {
      throw new Error(`Assertion failed: ${desc}`);
    }
  };

  const obj = {
    user: {
      name: "Alice",
      preferences: {
        theme: "dark",
        notifications: true
      }
    },
    tags: ["js", "node"]
  };

  const result = deepFreeze(obj);

  assert("Top-level object is frozen", Object.isFrozen(result));
  assert("Nested object is frozen", Object.isFrozen(result.user));
  assert("Deeply nested object is frozen", Object.isFrozen(result.user.preferences));
  assert("Array is frozen", Object.isFrozen(result.tags));

  try {
    result.user.name = "Bob"; // should fail silently or throw
    assert("Modifying frozen nested property has no effect", result.user.name === "Alice");
  } catch {
    assert("Throws when modifying frozen nested property", true);
  }

  try {
    result.tags.push("new");
    assert("Array mutation should not work", false);
  } catch {
    assert("Throws when mutating frozen array", true);
  }

  console.log("🎉 All deepFreeze tests completed.");
}

runDeepFreezeTests();
A
function deepFreeze(obj) {
  if (obj && typeof obj === "object" && !Object.isFrozen(obj)) {
    Object.freeze(obj);
    for (const key of Object.keys(obj)) {
      deepFreeze(obj[key]);
    }
  }
  return obj;
}
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
5
Q

Recursively Clone Objects and Arrays

Description
You are to implement a function deepClone(obj) that takes an object (which may include nested objects, arrays, numbers, strings, booleans, nulls, and undefined values) and returns a new object that is a deep copy of the input.

The original object and the returned object must be entirely independent — modifying the clone must not affect the original.

💡 Requirements
* Recursively clone all nested objects and arrays.
* Preserve all primitive types including null and undefined.
* Function properties, if any, may be copied as-is (no deep clone of closures required).
* Circular references will not be tested.

🚫 Constraints
The following patterns are forbidden and will be automatically enforced:
* JSON.stringify, JSON.parse
* structuredClone
* eval, Function
* .toString
* Global references like window, globalThis, self

🧪 Example Usage

const original = {
  name: "Alice",
  details: {
    age: 30,
    skills: ["JS", "Node"],
  },
  meta: null
};

const cloned = deepClone(original);
cloned.details.age = 99;
cloned.details.skills.push("React");

console.log(original.details.age); // 30 → Unchanged
console.log(original.details.skills.length); // 2 → Unchanged

Code

// deepClone.js

function deepClone(obj) {
  // TODO: Implement recursive deep cloning of plain objects and arrays
}

🧪 Test Suite

// enforce-deepClone.js

(function enforceDeepCloneConstraints() {
  const source = deepClone.toString();
  const forbidden = [
    "eval",
    "Function",
    ".toString",
    "window",
    "globalThis",
    "self",
    "JSON.stringify",
    "JSON.parse",
    "structuredClone"
  ];
  forbidden.forEach(term => {
    if (source.includes(term)) {
      throw new Error(`❌ Forbidden pattern used in deepClone(): "${term}" is not allowed.`);
    }
  });
})();

// test-deepClone.js

function runDeepCloneTests() {
  const assert = (desc, testFn) => {
    try {
      testFn();
      console.log(`✅ ${desc}`);
    } catch (e) {
      console.log(`❌ ${desc}`);
      console.error("   ", e.message);
    }
  };

  const obj = {
    user: {
      name: "Alice",
      age: 30,
      skills: ["JavaScript", "Node"],
    },
    active: true,
    meta: null,
    nestedArr: [{ id: 1 }, { id: 2 }],
    created: undefined,
  };

  const clone = deepClone(obj);

  assert("Cloned object is not the same reference", () => {
    if (clone === obj) throw new Error("Reference should be different");
  });

  assert("Nested object is deep cloned", () => {
    clone.user.age = 100;
    if (obj.user.age === 100) throw new Error("Nested object was not cloned deeply");
  });

  assert("Nested arrays are deep cloned", () => {
    clone.user.skills.push("React");
    if (obj.user.skills.includes("React")) throw new Error("Nested array was not cloned deeply");
  });

  assert("Primitive values are copied correctly", () => {
    if (clone.meta !== null || clone.active !== true) throw new Error("Primitives did not copy properly");
  });

  assert("Nested array of objects is cloned deeply", () => {
    clone.nestedArr[0].id = 99;
    if (obj.nestedArr[0].id === 99) throw new Error("Nested object in array was not cloned deeply");
  });

  assert("Undefined values are preserved", () => {
    if (!clone.hasOwnProperty("created")) throw new Error("Missing undefined value");
  });

  console.log("🎉 All deepClone tests completed.");
}

runDeepCloneTests();
A
function deepClone(obj) {
  if (obj === null || typeof obj !== "object") {
    return obj;
  }

  if (Array.isArray(obj)) {
    return obj.map(item => deepClone(item));
  }

  const result = {};
  for (const key in obj) {
    if (Object.prototype.hasOwnProperty.call(obj, key)) {
      result[key] = deepClone(obj[key]);
    }
  }

  return result;
}
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
6
Q

Memoization for Asynchronous Functions

Description
Implement a function memoizeAsync(fn) that memoizes a function which returns Promises.

📘 Requirements
* The returned memoized function should return the same resolved Promise if it was previously called with the same arguments.
* You must not recompute or re-call the original function fn for the same arguments.
* Handle multiple simultaneous calls with the same arguments gracefully (i.e., don’t trigger multiple internal calls to fn).
* Arguments are primitives only (e.g., strings, numbers, booleans).

🚫 Constraints
You may not use:
* eval, Function, .toString
* window, globalThis, self

💡 Example Usage

const slowDouble = async (x) => {
  await new Promise(r => setTimeout(r, 100));
  return x * 2;
};

const memoized = memoizeAsync(slowDouble);

console.time("first");
memoized(10).then(res => {
  console.timeEnd("first"); // ~100ms
  console.log(res); // 20
});

console.time("second");
memoized(10).then(res => {
  console.timeEnd("second"); // ~0ms if memoized
  console.log(res); // 20
});

Code

// memoizeAsync.js

function memoizeAsync(fn) {
  // TODO: Implement async memoization logic
}

🧪 Test Suite

// enforce-memoizeAsync.js

(function enforceMemoizeAsyncConstraints() {
  const source = memoizeAsync.toString();
  const forbidden = ["eval", "Function", ".toString", "window", "globalThis", "self"];
  forbidden.forEach(term => {
    if (source.includes(term)) {
      throw new Error(`❌ Forbidden pattern used in memoizeAsync(): "${term}" is not allowed.`);
    }
  });
})();

// test-memoizeAsync.js

function runMemoizeAsyncTests() {
  const assertEqual = async (desc, actualPromise, expected) => {
    const result = await actualPromise;
    const pass = result === expected;
    console.log(`${pass ? "✅" : "❌"} ${desc}`);
    if (!pass) {
      console.log(`   Expected: ${expected}, but got: ${result}`);
    }
  };

  let callCount = 0;
  const asyncAdder = async (a, b) => {
    callCount++;
    await new Promise(res => setTimeout(res, 50));
    return a + b;
  };

  const memoized = memoizeAsync(asyncAdder);

  (async () => {
    callCount = 0;

    const p1 = memoized(1, 2);
    const p2 = memoized(1, 2); // Should not increase callCount

    const result1 = await p1;
    const result2 = await p2;

    console.log(result1 === result2 ? "✅ Same promise result for same args" : "❌ Promise mismatch");

    await assertEqual("Result matches expected sum", p1, 3);
    await assertEqual("Second call reused memoized result", p2, 3);
    await assertEqual("Call count should be 1", Promise.resolve(callCount), 1);

    const p3 = memoized(2, 3);
    const result3 = await p3;

    await assertEqual("Different args compute new result", Promise.resolve(result3), 5);
    await assertEqual("Call count should be 2 now", Promise.resolve(callCount), 2);

    console.log("🎉 memoizeAsync tests completed.");
  })();
}

runMemoizeAsyncTests();
A
function memoizeAsync(fn) {
  const cache = new Map();

  return function (...args) {
    const key = JSON.stringify(args);
    if (!cache.has(key)) {
      const resultPromise = fn(...args);
      cache.set(key, resultPromise);
    }
    return cache.get(key);
  };
}
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
7
Q

Compose Asynchronous Functions from Right to Left

Description
Write a function composeAsync(…fns) that composes asynchronous (i.e., Promise-returning) functions and returns a new function that executes them right to left.

💡 Requirements
* All functions return Promises (i.e., async functions or functions returning Promise.resolve(…))
* composeAsync(f3, f2, f1)(x) should behave like:
f3(f2(f1(x))) but each function is awaited before the next.
* Your final composed function must return a Promise.

📌 Example

const double = async x => x * 2;
const increment = async x => x + 1;

const composed = composeAsync(double, increment);
composed(3).then(console.log); // (3 + 1) * 2 = 8

🚫 Constraints
You must NOT use any of the following:
* eval, Function, .toString
* window, globalThis, self
* reduceRight (for learning purposes)

Code

// composeAsync.js

function composeAsync(...fns) {
  // TODO: Return a composed function that applies fns right to left, handling async
}

🧪 Test Suite

// enforce-composeAsync.js

(function enforceComposeAsyncConstraints() {
  const source = composeAsync.toString();
  const forbidden = [
    "eval", "Function", ".toString", "window", "globalThis", "self", "reduceRight"
  ];
  forbidden.forEach(term => {
    if (source.includes(term)) {
      throw new Error(`❌ Forbidden pattern used in composeAsync(): "${term}" is not allowed.`);
    }
  });
})();

// test-composeAsync.js

function runComposeAsyncTests() {
  const assertAsync = async (desc, actualPromise, expected) => {
    try {
      const result = await actualPromise;
      const pass = result === expected;
      console.log(`${pass ? "✅" : "❌"} ${desc}`);
      if (!pass) console.log(`   Expected: ${expected}, but got: ${result}`);
    } catch (err) {
      console.log(`❌ ${desc}`);
      console.error("   Error:", err.message);
    }
  };

  const double = async x => x * 2;
  const increment = async x => x + 1;
  const square = async x => x * x;

  const composed = composeAsync(double, increment, square);
  const composed2 = composeAsync(square, increment);

  (async () => {
    await assertAsync("Composes square → increment → double", composed(2), 18); // (2^2 + 1) * 2 = 18
    await assertAsync("Composes increment → square", composed2(3), 16); // (3 + 1)^2 = 16
    await assertAsync("Handles single function", composeAsync(double)(5), 10);
    await assertAsync("Handles no functions", composeAsync()(42), 42);

    console.log("🎉 composeAsync tests completed.");
  })();
}

runComposeAsyncTests();
A
function composeAsync(...fns) {
  return async function (input) {
    let result = input;
    for (let i = fns.length - 1; i >= 0; i--) {
      result = await fns[i](result);
    }
    return result;
  };
}
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
8
Q

Build a Custom Event Bus

Description
Implement a lightweight version of Node.js-style EventEmitter.
You must build a class EventEmitter with the following methods:
* .on(eventName, handler) — Register a handler for the given event
* .off(eventName, handler) — Remove the specific handler for the event
* .emit(eventName, …args) — Invoke all handlers for the event with the provided arguments.

📘 Behavior Requirements
* Multiple handlers can be registered for the same event.
* Removing a handler with .off() should only remove the exact matching function.
* .emit() must pass all arguments to every registered listener in the order they were added.

💡 Example Usage

const emitter = new EventEmitter();

function greet(name) {
  console.log("Hello", name);
}

emitter.on("hello", greet);
emitter.emit("hello", "Alice"); // → Hello Alice

emitter.off("hello", greet);
emitter.emit("hello", "Alice"); // → (no output)

🚫 Forbidden Patterns
Your implementation must not use:
* eval, Function, .toString
* window, globalThis, self, document
* Prototype chain manipulation

Code

// EventEmitter.js

class EventEmitter {
  constructor() {
    // TODO: Initialize event store
  }

  on(eventName, handler) {
    // TODO: Register the handler for the event
  }

  off(eventName, handler) {
    // TODO: Remove the specified handler from the event
  }

  emit(eventName, ...args) {
    // TODO: Trigger all handlers for the event
  }
}

🧪 Test Suite

// enforce-EventEmitter.js

(function enforceEventEmitterConstraints() {
  const source = EventEmitter.toString();
  const forbidden = [
    "eval", "Function", ".toString", "window", "globalThis", "self", "document", "\_\_proto\_\_"
  ];
  forbidden.forEach(term => {
    if (source.includes(term)) {
      throw new Error(`❌ Forbidden pattern used in EventEmitter: "${term}" is not allowed.`);
    }
  });
})();

// test-EventEmitter.js

function runEventEmitterTests() {
  const assert = (desc, actual, expected) => {
    const pass = JSON.stringify(actual) === JSON.stringify(expected);
    console.log(`${pass ? "✅" : "❌"} ${desc}`);
    if (!pass) {
      console.log(`   Expected: ${JSON.stringify(expected)}, but got: ${JSON.stringify(actual)}`);
    }
  };

  const emitter = new EventEmitter();
  let results = [];

  const handler1 = name => results.push(`Hi ${name}`);
  const handler2 = name => results.push(`Welcome ${name}`);

  emitter.on("greet", handler1);
  emitter.on("greet", handler2);

  emitter.emit("greet", "Alice");
  assert("Handlers should execute in order", results, ["Hi Alice", "Welcome Alice"]);

  results = [];
  emitter.off("greet", handler1);
  emitter.emit("greet", "Bob");
  assert("After removing handler1", results, ["Welcome Bob"]);

  results = [];
  emitter.off("greet", handler2);
  emitter.emit("greet", "Eve");
  assert("All handlers removed", results, []);

  // Edge: multiple .on() for same function
  results = [];
  emitter.on("echo", handler1);
  emitter.on("echo", handler1);
  emitter.emit("echo", "Foo");
  assert("Same function registered twice", results, ["Hi Foo", "Hi Foo"]);

  results = [];
  emitter.off("echo", handler1);
  emitter.emit("echo", "Bar");
  assert("Only one instance removed", results, ["Hi Bar"]);

  console.log("🎉 EventEmitter tests completed.");
}

runEventEmitterTests();
A
class EventEmitter {
  constructor() {
    this.events = {};
  }

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

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

  emit(eventName, ...args) {
    if (!this.events[eventName]) return;
    for (const handler of this.events[eventName]) {
      handler(...args);
    }
  }
}
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
9
Q

Retry a Async Function N Times on Failure

Description
Implement a function retryAsync(fn, n) that wraps an asynchronous function fn.
When invoked, the returned function should:
* Call fn() and return its result if it resolves
* If it rejects, retry calling fn() up to n additional times
* If all attempts fail, the promise should reject with the last error
You may assume fn is a function that returns a Promise. All attempts should be sequential (not in parallel).

🧪 Example

let tries = 0;
const unstableAsync = () => {
  return new Promise((resolve, reject) => {
    tries++;
    if (tries < 3) reject("Fail");
    else resolve("Success");
  });
};

const retryWrapper = retryAsync(unstableAsync, 5);

retryWrapper().then(console.log); // → "Success"

🚫 Constraints
Your implementation must not use:
* eval, Function, .toString
* Access to global scope (window, globalThis, etc.)

Code

// 🚫 DO NOT USE: eval, Function, .toString, window, globalThis
function retryAsync(fn, n) {
    // TODO: Return a function that retries async `fn` up to `n` times if it rejects
}

🧪 Test Suite

// 🔒 Enforce constraints
(function enforceRetryAsyncConstraints() {
    const source = retryAsync.toString();
    const forbidden = ["eval", "Function", ".toString", "window", "globalThis"];
    forbidden.forEach(term => {
        if (source.includes(term)) {
            throw new Error(`❌ Forbidden pattern used in retryAsync(): "${term}" is not allowed.`);
        }
    });
})();

// ✅ Functional Tests
async function runRetryAsyncTests() {
    let passCount = 0;
    let failCount = 0;

    const flakyAsync = () => {
        return new Promise((resolve, reject) => {
            failCount++;
            if (failCount < 3) reject("Error");
            else resolve("OK");
        });
    };

    const wrapped = retryAsync(flakyAsync, 5);
    const result = await wrapped();
    console.assert(result === "OK", "❌ Should succeed after retries");

    let totalAttempts = 0;
    const alwaysFail = () => {
        return new Promise((_, reject) => {
            totalAttempts++;
            reject("Nope");
        });
    };

    const alwaysFails = retryAsync(alwaysFail, 2);
    let threw = false;
    try {
        await alwaysFails();
    } catch (err) {
        threw = true;
        console.assert(totalAttempts === 3, "❌ Should try initial + 2 retries");
    }
    console.assert(threw, "❌ Should throw after all retries failed");

    let singleTry = false;
    const succeedFirst = () => {
        return new Promise(resolve => {
            singleTry = true;
            resolve("Yay");
        });
    };

    const oneTry = retryAsync(succeedFirst, 0);
    const success = await oneTry();
    console.assert(success === "Yay", "❌ Should return result on first try");

    console.log("✅ All retryAsync(fn, n) tests passed.");
}

runRetryAsyncTests();
A
function retryAsync(fn, n) {
    return async function (...args) {
        let lastError;
        for (let i = 0; i <= n; i++) {
            try {
                return await fn.apply(this, args);
            } catch (err) {
                lastError = err;
            }
        }
        throw lastError;
    };
}
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
10
Q

Retry a Async Function N Times with Delay

Description
Retry an async function fn up to n times, waiting delayMs milliseconds between each failed attempt.

Code

function retryAsyncWithDelay(fn, n, delayMs) {
    // TODO: Retry up to n times with delay between retries
}

🧪 Test Suite

(function enforceConstraints() {
    const src = retryAsyncWithDelay.toString();
    ["eval", "Function", ".toString", "window", "globalThis"].forEach(term => {
        if (src.includes(term)) {
            throw new Error(`❌ Forbidden pattern used: ${term}`);
        }
    });
})();

function wait(ms) {
    return new Promise(res => setTimeout(res, ms));
}

async function runRetryAsyncWithDelayTests() {
    let attempts = 0;
    const failTwice = () => {
        return new Promise((resolve, reject) => {
            attempts++;
            if (attempts < 3) reject("fail");
            else resolve("success");
        });
    };

    const wrapped = retryAsyncWithDelay(failTwice, 3, 100);
    const start = Date.now();
    const result = await wrapped();
    const duration = Date.now() - start;

    console.assert(result === "success", "❌ Expected 'success'");
    console.assert(duration >= 200, "❌ Expected at least 2 delays of 100ms");

    let hardFailCount = 0;
    const alwaysFail = () => {
        return new Promise((_, reject) => {
            hardFailCount++;
            reject("nope");
        });
    };

    const failing = retryAsyncWithDelay(alwaysFail, 2, 50);
    let threw = false;
    try {
        await failing();
    } catch (e) {
        threw = true;
    }

    console.assert(threw, "❌ Should throw if all retries fail");
    console.assert(hardFailCount === 3, "❌ Must attempt 3 times");

    console.log("✅ retryAsyncWithDelay tests passed.");
}

runRetryAsyncWithDelayTests();
A
function retryAsyncWithDelay(fn, n, delayMs) {
    return async function (...args) {
        let lastError;
        for (let i = 0; i <= n; i++) {
            try {
                return await fn.apply(this, args);
            } catch (err) {
                lastError = err;
                if (i < n) {
                    await new Promise(res => setTimeout(res, delayMs));
                }
            }
        }
        throw lastError;
    };
}
How well did you know this?
1
Not at all
2
3
4
5
Perfectly