Interview question
Build an Angular HTTP interceptor with correlation and safe retry rules
Implements a functional interceptor that adds correlation metadata, records timing, and retries only explicitly safe transient requests.
TL;DR
Implements a functional interceptor that adds correlation metadata, records timing, and retries only explicitly safe transient requests.
Angular functional interceptors, HttpContextToken, immutable requests, retry policy, correlation, telemetry, and privacy.
Practice the problem like a real interview: restate, reason, implement, and test.
Implement a functional interceptor that adds an X-Correlation-ID when absent, records method, normalized route, duration, and outcome, and retries at most once only when the request opts in and the failure is transient. Do not retry unsafe commands by default, log secrets, or replace the original error.
Move to the linked follow-up, next path step, prerequisite, or deeper variant.
Practice the next layer of the same subject.
GET /catalog + RETRY_SAFE=true + 503 -> one delayed retry
POST /orders + default context + 503 -> no retry
Authorization header -> never logged
failure after retry -> original error semantics preserved
finalize without reading sensitive headers or bodies.import {HttpContextToken, HttpErrorResponse, HttpInterceptorFn} from '@angular/common/http';
import {inject} from '@angular/core';
import {retry, throwError, timer, finalize, tap} from 'rxjs';
export const RETRY_SAFE = new HttpContextToken<boolean>(() => false);
export const reliabilityInterceptor: HttpInterceptorFn = (request, next) => {
const telemetry = inject(HttpTelemetry);
const correlationId = request.headers.get('X-Correlation-ID') ?? crypto.randomUUID();
const outgoing = request.headers.has('X-Correlation-ID')
? request
: request.clone({setHeaders: {'X-Correlation-ID': correlationId}});
const started = performance.now();
let outcome = 'success';
return next(outgoing).pipe(
retry({
count: request.context.get(RETRY_SAFE) && request.method === 'GET' ? 1 : 0,
delay: error => {
const transient = error instanceof HttpErrorResponse &&
(error.status === 0 || [502, 503, 504].includes(error.status));
if (!transient) return throwError(() => error);
outcome = 'retried';
return timer(200);
},
}),
tap({error: () => { outcome = 'failure'; }}),
finalize(() => telemetry.record({
method: request.method,
route: normalizeRoute(request.url),
correlationId,
outcome,
durationMs: performance.now() - started,
})),
);
};
The interceptor adds O(1) client work per request. Retry can double one opted-in GET request, so downstream load remains explicitly bounded.
Share the question with a concise Core-answer excerpt and invite other developers to add their perspective.
Help improve the interview library.
Say thanks with a standalone, one-time $5 contribution. No account required.
Aporeon is shaped by Aleksandar Tomovski, a software developer with experience on both sides of technical interviews. Content is reviewed for accuracy, natural spoken delivery, useful depth, and honest trade-offs.