LeetCode 2725 - Interval Cancellation
This problem asks us to implement a function cancellable that repeatedly calls a given function fn with a set of arguments args at a fixed interval t milliseconds and allows this repeated execution to be stopped by a cancel function cancelFn.
Difficulty: 🟢 Easy
Topics: —
Solution
Problem Understanding
This problem asks us to implement a function cancellable that repeatedly calls a given function fn with a set of arguments args at a fixed interval t milliseconds and allows this repeated execution to be stopped by a cancel function cancelFn. The first call to fn happens immediately (at time 0), and subsequent calls occur every t milliseconds. After a specified time, cancelTimeMs, the returned cancelFn is invoked, which stops further calls to fn.
The inputs are:
fn: a function that can accept 1 to 10 arguments. It is the function to be invoked repeatedly.args: an array of arguments to pass tofnon each call.t: the interval in milliseconds between successive function calls.
The output is a function cancelFn that stops the interval when invoked. The problem focuses on simulating interval behavior, ensuring the timing is correct, and guaranteeing that no calls happen after cancellation.
Constraints give us a bounded range for t and cancelTimeMs, meaning we do not need to handle extremely high frequencies or durations. Edge cases that could trip up a naive implementation include cancelling immediately, having cancelTimeMs that is not a multiple of t, and functions with multiple arguments.
Approaches
Brute Force Approach
A brute force implementation would repeatedly call fn using a setInterval in JavaScript or a loop with sleep in other languages until the time elapsed reaches cancelTimeMs. This works because you can manually track elapsed time and stop the interval after the cancel time. However, this approach is inefficient for languages that do not handle accurate timing in tight loops and could be error-prone if timing drift occurs.
Optimal Approach
The key insight is that JavaScript (or equivalent environments) already provides a native mechanism to handle repeated function calls (setInterval) and cancellation (clearInterval). By using setInterval for periodic execution and returning a function that calls clearInterval, we get a clean and accurate implementation. The first call is executed immediately before starting the interval.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(cancelTimeMs / t) | O(1) | Manually track elapsed time and call fn repeatedly until cancel |
| Optimal | O(cancelTimeMs / t) | O(1) | Use setInterval and clearInterval to handle timing and cancellation |
Algorithm Walkthrough
- Define the function
cancellablewith parametersfn,args, andt. - Call
fnimmediately with the given arguments and store the returned value if needed. - Use
setIntervalto schedule repeated execution offneverytmilliseconds. - Inside the interval callback, call
fnwith the provided arguments. - Return a
cancelFnfunction that callsclearIntervalon the interval to stop further calls. - When
cancelFnis invoked (viasetTimeoutor manually), the repeated execution halts.
Why it works: The invariant is that fn is always called immediately and then periodically every t milliseconds. Using setInterval ensures the timing is consistent, and returning a function that calls clearInterval guarantees we can cancel at any moment.
Python Solution
from typing import Any, Callable
def cancellable(fn: Callable[..., Any], args: list[Any], t: int) -> Callable[[], None]:
import threading
# Call fn immediately
fn(*args)
# Create an event to signal cancellation
stop_event = threading.Event()
def interval_runner():
while not stop_event.is_set():
threading.Event().wait(t / 1000) # convert ms to seconds
if stop_event.is_set():
break
fn(*args)
# Start the interval in a separate thread
thread = threading.Thread(target=interval_runner)
thread.daemon = True
thread.start()
# Return the cancel function
def cancel_fn():
stop_event.set()
thread.join() # ensure the thread finishes cleanly
return cancel_fn
The Python implementation uses a background thread to simulate setInterval behavior. The first call happens immediately. A threading.Event is used to signal cancellation safely. The cancel_fn stops the loop and joins the thread to clean up resources.
Go Solution
package main
import (
"time"
)
func Cancellable(fn func(...interface{}), args []interface{}, t int) func() {
ticker := time.NewTicker(time.Duration(t) * time.Millisecond)
done := make(chan struct{})
// Call fn immediately
fn(args...)
// Start a goroutine to handle periodic calls
go func() {
for {
select {
case <-ticker.C:
fn(args...)
case <-done:
ticker.Stop()
return
}
}
}()
// Return the cancel function
return func() {
close(done)
}
}
The Go implementation uses a time.Ticker to schedule periodic execution and a channel to signal cancellation. Calling the returned function closes the channel, which stops the goroutine and halts the ticker.
Worked Examples
Example 1
fn = (x) => x * 2
args = [4]
t = 35
cancelTimeMs = 190
| Time (ms) | Action | Result |
|---|---|---|
| 0 | Call fn(4) | 8 |
| 35 | Call fn(4) | 8 |
| 70 | Call fn(4) | 8 |
| 105 | Call fn(4) | 8 |
| 140 | Call fn(4) | 8 |
| 175 | Call fn(4) | 8 |
| 190 | Cancel interval | stop |
The function is called immediately and then every 35ms until cancellation.
Example 2
fn = (x1, x2) => x1 * x2
args = [2, 5]
t = 30
cancelTimeMs = 165
| Time (ms) | Action | Result |
|---|---|---|
| 0 | Call fn(2, 5) | 10 |
| 30 | Call fn(2, 5) | 10 |
| 60 | Call fn(2, 5) | 10 |
| 90 | Call fn(2, 5) | 10 |
| 120 | Call fn(2, 5) | 10 |
| 150 | Call fn(2, 5) | 10 |
| 165 | Cancel interval | stop |
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(cancelTimeMs / t) | The function is called once immediately and then approximately cancelTimeMs / t times |
| Space | O(1) | Only a small amount of state is stored for interval/cancellation |
The complexity is proportional to the number of times the function is executed. Memory usage is constant outside of system-managed threads or goroutines.
Test Cases
# Example 1
cancel = cancellable(lambda x: x*2, [4], 35)
import time
time.sleep(0.19) # 190ms
cancel() # cancel the interval
# Example 2
cancel = cancellable(lambda x1, x2: x1*x2, [2,5], 30)
time.sleep(0.165)
cancel()
# Example 3
cancel = cancellable(lambda x1, x2, x3: x1+x2+x3, [5,1,3], 50)
time.sleep(0.18)
cancel()
# Edge case: cancel immediately
cancel = cancellable(lambda x: x, [10], 50)
cancel()
| Test | Why |
|---|---|
| Example 1 | Validates repeated calls and cancellation timing |
| Example 2 | Checks multiple arguments |
| Example 3 | Checks non-multiples of t vs cancelTimeMs |
| Immediate cancel | Ensures no extra calls occur |
Edge Cases
One important edge case is immediate cancellation. If cancelFn is invoked right after creation, the function should not execute more than the initial immediate call. This is handled by checking the stop condition before each subsequent interval execution.
Another edge case is when cancelTimeMs is not a multiple of t. The last call to fn before cancellation should still occur. Both implementations account for this by letting the interval continue until cancelFn is called.
Finally, functions with the maximum number of arguments (10) must be handled. The implementations support this via variadic argument unpacking in Python and Go. This ensures correctness regardless of the number of arguments.