Back to blog
Apr 18, 2025
4 min read

Interval Cancellation

Implement a function to schedule a recurring function call with an initial delay and return a function to cancel it.

Difficulty: Easy | Acceptance: 84.90% | Paid: No Topics: N/A

Given a function fn, an array of arguments args, an interval in milliseconds t, and an initial delay delay, return a cancel function cancelfn.

The function fn should be called every t milliseconds, starting after delay milliseconds.

The returned function cancelfn should cancel the interval.

Examples

Example 1:

Input: fn = (x) => console.log(x), args = [42], t = 100, delay = 1000
Output: [{"time": 1000, "returned": 42}]
Explanation:
const cancelfn = cancellable((x) => console.log(x), [42], 100, 1000);
setTimeout(() => cancelfn(), 1200);
The cancellation was scheduled to run after 1200ms, but fn was already called at 1000ms.

Example 2:

Input: fn = (x) => console.log(x), args = [42], t = 100, delay = 1000
Output: []
Explanation:
const cancelfn = cancellable((x) => console.log(x), [42], 100, 1000);
setTimeout(() => cancelfn(), 200);
The cancellation was scheduled to run after 200ms, so fn was never called.

Example 3:

Input: fn = (x) => console.log(x), args = [42], t = 100, delay = 1000
Output: [{"time": 1000, "returned": 42}, {"time": 1100, "returned": 42}]
Explanation:
const cancelfn = cancellable((x) => console.log(x), [42], 100, 1000);
setTimeout(() => cancelfn(), 1500);
The cancellation was scheduled to run after 1500ms, so fn was called at 1000ms and 1100ms.

Constraints

- fn is a function
- args is a valid JSON array
- 1 <= args.length <= 10
- 30 <= t <= 100
- 10 <= cancelTimeMs <= 500

Approach 1: Using setTimeout and setInterval

Intuition Use setTimeout to handle the initial delay and setInterval to handle the recurring execution. The cancel function must clear both timers to handle cancellation at any stage.

Steps

  • Use setTimeout to wait for the delay period.
  • Inside the timeout callback, execute fn immediately and then start a setInterval to run fn every t milliseconds.
  • Store the IDs of both the timeout and the interval.
  • The returned cancelfn clears both the timeout (if it hasn’t fired) and the interval (if it has started).
python
import threading

def cancellable(fn, args, t, delay):
    timer = None
    interval = None
    
    def start_interval():
        nonlocal interval
        fn(*args)
        interval = threading.Timer(t / 1000.0, start_interval)
        interval.start()

    timer = threading.Timer(delay / 1000.0, start_interval)
    timer.start()

    def cancel():
        if timer:
            timer.cancel()
        if interval:
            interval.cancel()

    return cancel

Complexity

  • Time: O(1) for setup, O(k) for execution where k is the number of times fn is called.
  • Space: O(1)
  • Notes: Requires managing two timer IDs. setInterval can drift if fn takes longer than t to execute.

Approach 2: Using Recursive setTimeout

Intuition Use a single setTimeout loop. After the initial delay, execute fn and then schedule the next execution using setTimeout again. This avoids managing two separate timer IDs and handles the initial delay naturally within the loop logic.

Steps

  • Define a recursive function that executes fn and then schedules itself with setTimeout.
  • Use a variable to store the current timer ID.
  • Initialize the loop with the initial delay.
  • The returned cancelfn simply clears the current timer ID, stopping the recursion.
python
import threading

def cancellable(fn, args, t, delay):
    timer = None
    
    def run():
        nonlocal timer
        fn(*args)
        timer = threading.Timer(t / 1000.0, run)
        timer.start()

    timer = threading.Timer(delay / 1000.0, run)
    timer.start()

    def cancel():
        if timer:
            timer.cancel()

    return cancel

Complexity

  • Time: O(1) for setup, O(k) for execution.
  • Space: O(1)
  • Notes: Only one timer ID needs to be managed. This approach ensures that fn is not called again until t milliseconds after the previous call completes, preventing overlap if fn is slow.