Build an analytics client that buffers events and flushes them in batches
Implement an AnalyticsClient that collects events and sends them to a server in batches. Provide queueEvent(event) (buffers an event, stamping timestamp: Date.now()), a periodic flush every interval ms that POSTs all buffered events as one JSON array, and sendNow() (flush immediately). Clear the buffer only after a successful send so a failed flush keeps the events for retry.
class AnalyticsClient {
constructor({ url, interval }) {
// your code here
}
}
Write the implementation.
Keep an events array; queueEvent pushes { ...event, timestamp: Date.now() }. A self-rescheduling setTimeout calls sendNow, which bails on an empty buffer, POSTs the events as one JSON array, and clears the buffer ONLY in the success path — so a rejected fetch leaves the events queued for retry. sendNow is also callable directly for an immediate flush.
- ✗Clearing the buffer before the send succeeds, so a failed POST silently loses the events
- ✗Sending per-event instead of batching, defeating the purpose of the buffer
- ✗Using
setInterval, allowing a slow flush to overlap the next one
- →Why must the buffer be cleared only after a successful send rather than before?
- →How does batching events reduce load compared with sending each one immediately?
Solution
Events accumulate in an array; the flush POSTs the batch and clears the buffer only on success.
class AnalyticsClient {
#events = [];
#url;
#interval;
#timer = null;
constructor({ url, interval }) {
this.#url = url;
this.#interval = interval;
this.#schedule();
}
#schedule() {
this.#timer = setTimeout(() => {
this.sendNow().finally(() => this.#schedule());
}, this.#interval);
}
queueEvent(event) {
this.#events.push({ ...event, timestamp: Date.now() });
}
async sendNow() {
if (this.#events.length === 0) return;
const batch = this.#events;
await fetch(this.#url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ events: batch }),
});
this.#events = []; // clear only after a successful await
}
}
How it works
queueEvent just pushes an event into the #events array, stamping it with a timestamp. The actual send happens as a batch: sendNow gathers all accumulated events into one JSON body and sends a single POST — far fewer requests than sending each event separately.
The key reliability detail is that the buffer is cleared strictly after a successful await fetch. If the request fails, the exception aborts sendNow before the clear line, and the events stay queued for the next attempt. Rescheduling in .finally ensures a new flush does not start until the previous one has finished. </content>