Debounce and throttle
Debounce and throttle solve different timing problems. A trailing debounce waits until calls stop, then runs with the latest arguments. A leading throttle runs immediately and suppresses calls until its window ends. A trailing throttle can also keep one final call for the boundary.
Use the injected logical clock to test these policies without sleeping. Before each incoming call, the wrapper runs all timers due at that timestamp; that ordering makes exact-boundary behavior explicit. Canceling a debounce drops its stored arguments. Flushing runs pending work immediately and must consume it, so a second flush doesn'thing. The scheduler is part of the test fixture, while your wrapper function is the reusable implementation. Hidden cases include time zero, empty labels, repeated control calls, and consecutive throttle windows.
class Clock {now=0;private next=0;private jobs=new Map<number,{at:number;fn:()=>void}>();set(fn:()=>void,delay:number):number {const id=++this.next;this.jobs.set(id,{at:this.now+delay,fn});return id;}clear(id:number|undefined):void {if(id!==undefined)this.jobs.delete(id);}tick(until:number):void {for(;;){const ready=[...this.jobs].filter(([,job])=>job.at<=until).sort((a,b)=>a[1].at-b[1].at || a[0]-b[0]);if(!ready.length)break;const [id,job]=ready[0];this.now=job.at;this.jobs.delete(id);job.fn();}this.now=until;}}
function debounce<A extends unknown[]>(fn:(...args:A)=>void,wait:number,clock:Clock){let id:number|undefined;let last:A|undefined;const call=(...args:A)=>{last=args;clock.clear(id);id=clock.set(()=>{id=undefined;const current=last;last=undefined;if(current)fn(...current);},wait);};return Object.assign(call,{cancel(){clock.clear(id);id=undefined;last=undefined;},flush(){clock.clear(id);id=undefined;const current=last;last=undefined;if(current)fn(...current);}});}
function solve(value:string):string[] {const x=JSON.parse(value) as {wait:number;end:number;calls:[number,string][]};const clock=new Clock();const out:string[]=[];const handler=debounce((label:string)=>out.push(clock.now+":"+label),x.wait,clock);for(const [at,label] of x.calls){clock.tick(at);handler(label);}clock.tick(x.end);return out;}
A complete implementation for the first scenario; trace its input before editing.