Volume 2

Q117: AbortController, Cancellation and Request Lifecycles

Difficulty: SeniorFrequency: HighAnswer time: 8-12 minutes

What Interviewers Want To Evaluate

This question checks whether you understand cancellation as a first-class frontend concern. Interviewers want AbortController, AbortSignal, fetch cancellation, stale response prevention, UI lifecycle cleanup, timeouts, retries, and when promises are not enough.

Senior answers should connect cancellation to search, routing, form submissions, dashboards, memory, and user trust.

Short Interview Answer

AbortController creates an AbortSignal that can be passed to abortable APIs like fetch. Calling controller.abort() marks the signal as aborted and causes supported operations to reject with an abort error. Promises themselves do not cancel work, so cancellation needs a separate signal or API. In frontend apps, cancellation prevents obsolete requests, wasted work, stale UI updates, and memory retention.

Detailed Interview Answer

Promises represent eventual results. They do not cancel the underlying operation by themselves.

const promise = fetch("/api/search?q=react");

Dropping this promise does not necessarily stop the network request.

AbortController gives code a cancellation channel.

const controller = new AbortController();

fetch("/api/search?q=react", {
  signal: controller.signal,
});

controller.abort();

AbortController and AbortSignal

The controller owns the ability to abort.

The signal is passed to the operation.

AbortController -> controller.abort()
AbortSignal -> signal.aborted, abort event

This separates authority from observation.

Fetch Cancellation

When fetch receives an abort signal and the controller aborts, the fetch rejects.

async function loadUser(id, signal) {
  const response = await fetch(`/api/users/${id}`, { signal });
  return response.json();
}

Callers decide when the signal should abort.

Handling Abort Errors

Abort is often not a user-facing error.

try {
  await loadUser(id, signal);
} catch (error) {
  if (error instanceof DOMException && error.name === "AbortError") {
    return;
  }

  throw error;
}

The exact error shape can vary by environment, so some teams centralize abort detection.

Stale Response Problem

Cancellation also protects against stale responses.

Example: a user types quickly in a search box.

request for "r" starts
request for "re" starts
request for "rea" starts
first request finishes last
old results overwrite new results

Aborting old requests or ignoring stale request IDs prevents this.

Search Example

let currentController;

async function search(query) {
  currentController?.abort();

  const controller = new AbortController();
  currentController = controller;

  const response = await fetch(`/api/search?q=${encodeURIComponent(query)}`, {
    signal: controller.signal,
  });

  return response.json();
}

Each new search cancels the previous one.

Timeout Pattern

Cancellation can implement request timeouts.

async function fetchWithTimeout(url, timeoutMs) {
  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), timeoutMs);

  try {
    return await fetch(url, { signal: controller.signal });
  } finally {
    clearTimeout(timeoutId);
  }
}

The timeout must be cleared to avoid retaining the controller longer than needed.

Combining Signals

Modern platforms support ways to combine abort behavior in some environments, such as timeout signals or composite signals.

In interviews, focus on the principle: cancellation should follow ownership.

If the page, component, or request owner is no longer active, the work should stop or be ignored.

React Lifecycle Example

useEffect(() => {
  const controller = new AbortController();

  loadProfile(userId, controller.signal)
    .then(setProfile)
    .catch((error) => {
      if (error.name !== "AbortError") {
        setError(error);
      }
    });

  return () => {
    controller.abort();
  };
}, [userId]);

When userId changes, the old request is cancelled.

Retry Interaction

Retries should respect cancellation.

async function retry(task, signal) {
  for (let attempt = 1; attempt <= 3; attempt += 1) {
    if (signal.aborted) {
      throw signal.reason ?? new Error("Aborted");
    }

    try {
      return await task(signal);
    } catch (error) {
      if (signal.aborted || attempt === 3) {
        throw error;
      }
    }
  }
}

Retry loops that ignore cancellation can continue doing obsolete work.

Common Mistakes

A common mistake is thinking a rejected promise means the underlying work stopped. It may not.

Another mistake is showing aborts as real errors to users. Cancelled obsolete work should often be silent.

Another mistake is starting new requests without aborting or guarding old results.

Senior Trade-Offs

Cancellation improves performance and correctness, but not every operation needs hard cancellation.

Sometimes ignoring stale results is enough. Sometimes the expensive operation must actually stop. The decision depends on cost, user impact, backend load, and API support.

Code or Design Example

function createLatestOnlyLoader(load) {
  let requestId = 0;

  return async function latestOnly(...args) {
    const id = requestId + 1;
    requestId = id;

    const result = await load(...args);

    if (id !== requestId) {
      return undefined;
    }

    return result;
  };
}

This pattern ignores stale results when the underlying API cannot be aborted.

Debugging Workflow

When async UI updates are wrong:

check whether obsolete requests are aborted
check whether stale results are ignored
check abort error handling
check cleanup on route or component change
check timeouts are cleared
check retry loops respect signal.aborted
check loading state cleanup in finally

Interview Framing

Explain that promises do not cancel work. Then show AbortController with fetch, lifecycle cleanup, and stale response prevention.

Revision Notes

AbortController is a cancellation signal system. It is essential when async work can become obsolete.

Key Takeaways

Cancellation is about ownership. Whoever owns async work should also define when that work is no longer useful.

Follow-Up Questions

  1. Do promises cancel work?
  2. What does AbortController create?
  3. What is AbortSignal?
  4. How does fetch react to abort?
  5. What is a stale response?
  6. How do you cancel search requests?
  7. How do you implement a timeout?
  8. Should abort errors be shown to users?
  9. How do retries interact with cancellation?
  10. When is ignoring stale results enough?

How I Would Answer This In A Real Interview

I would say promises only represent results, so cancellation needs a separate signal. Then I would show AbortController with fetch, explain lifecycle cleanup, and discuss stale response protection for search, filters, and route changes.