Interview Guide · 19 min read · Updated

Senior Frontend Interview Guide, Part 8: REST APIs, Web Services, and Axios

Almost every screen in an enterprise application is a view over data that lives somewhere else. Frontend engineers are expected to understand HTTP well enough to design robust integrations, to debug failures from the network panel, to use Axios idiomatically, and to integrate third-party platforms…

Featured image: Part 8, REST APIs, Web Services, and Axios

This article is Part 8 of 18 in The Senior Frontend Engineer Interview Guide, and includes 23 interview questions with model answers.

Almost every screen in an enterprise application is a view over data that lives somewhere else. Frontend engineers are expected to understand HTTP well enough to design robust integrations, to debug failures from the network panel, to use Axios idiomatically, and to integrate third-party platforms such as payment processors, identity providers, analytics, and document services. This chapter covers HTTP and REST fundamentals, Axios in depth, API layer architecture, error handling and resilience, CORS, other web service styles, and third-party integrations.

HTTP Essentials

Methods and Their Semantics

Method Purpose Safe Idempotent Has body
GET Read a resource Yes Yes No
HEAD GET without body Yes Yes No
POST Create or trigger processing No No Yes
PUT Replace a resource entirely No Yes Yes
PATCH Partially update a resource No Not necessarily Yes
DELETE Remove a resource No Yes Optional
OPTIONS Discover allowed methods (CORS preflight) Yes Yes No

Safe means no server state change. Idempotent means making the same request several times has the same effect as making it once. Idempotency determines what can be retried automatically: a timed-out GET can be retried safely; a timed-out POST that creates a payment cannot, unless the API supports idempotency keys.

Status Codes

Range Meaning Common codes
2xx Success 200 OK, 201 Created (with Location), 202 Accepted (async processing), 204 No Content
3xx Redirection 301/308 permanent, 302/307 temporary, 304 Not Modified
4xx Client error 400 Bad Request, 401 Unauthorized (not authenticated), 403 Forbidden (not allowed), 404 Not Found, 409 Conflict, 412 Precondition Failed, 422 Unprocessable Entity, 429 Too Many Requests
5xx Server error 500 Internal Server Error, 502 Bad Gateway, 503 Service Unavailable, 504 Gateway Timeout

A frequent interview trap: 401 means “who are you?” (missing or invalid credentials; re-authenticate), and 403 means “I know who you are, but you can’t do this” (permissions; re-authenticating will not help).

202 Accepted is common in payment systems: the request was accepted for processing, and the client should poll a status endpoint or listen for an event.

Headers Worth Knowing

  • Content-Type and Accept for media types (application/json, multipart/form-data).
  • Authorization: Bearer <token> for access tokens.
  • Cache-Control, ETag, If-None-Match, Last-Modified for caching and conditional requests.
  • If-Match with an ETag for optimistic concurrency control (prevents lost updates; server returns 412 if the resource changed).
  • Idempotency-Key for safely retrying non-idempotent requests.
  • Retry-After on 429 and 503 responses.
  • X-Request-ID or W3C traceparent for correlating logs across services.

REST Principles

REST (Representational State Transfer) is an architectural style. In practice, “RESTful” APIs share these traits:

  • Resources identified by URLs: /accounts/{id}/transactions.
  • Standard methods express intent, rather than verbs in URLs (POST /payees rather than /createPayee).
  • Stateless requests: each request carries all needed context (such as the token).
  • Representations, usually JSON, with consistent naming conventions.
  • Cacheability through HTTP caching semantics.
  • HATEOAS (links in responses that describe available actions) is part of strict REST but rarely fully implemented.

Designing Frontend-Friendly APIs

Frontend engineers often contribute to API design, especially with a backend-for-frontend (BFF). Good practices:

  • Pagination: cursor-based (?cursor=abc&limit=50) for large, changing lists such as transactions; offset-based is simpler but skips or duplicates items when data changes between pages.
  • Filtering and sorting via query parameters: ?status=pending&sort=-createdAt.
  • Consistent error format, for example RFC 9457 Problem Details:
{
  "type": "https://api.bank.example/problems/insufficient-funds",
  "title": "Insufficient funds",
  "status": 422,
  "detail": "Available balance is lower than the transfer amount.",
  "instance": "/transfers/7f3a",
  "errors": [{ "field": "amount", "code": "INSUFFICIENT_FUNDS" }],
  "traceId": "4bf92f3577b34da6"
}
  • Money as strings or integer minor units with an explicit currency, never as floating-point numbers.
  • Dates in ISO 8601 with time zone (2026-03-14T15:09:26Z), and dates without time (2026-03-14) for business dates like settlement dates.
  • Versioning: in the URL (/v2/), a header, or media type. Additive, backwards-compatible changes should not need a new version.
  • Backend-for-frontend: a server layer owned by the frontend team that aggregates and shapes backend microservice responses for a specific UI, handles token exchange, and keeps secrets off the client.

Fetch versus Axios

The browser’s native fetch is capable, but Axios remains popular in enterprise code bases.

fetch Axios
Availability Built into browsers and Node 18+ Library (about 13 KB gzipped)
JSON Manual res.json() Automatic parsing and serialization
HTTP errors Resolves on 4xx/5xx; you must check res.ok Rejects on non-2xx (configurable with validateStatus)
Interceptors None (wrap manually) Request and response interceptors
Timeouts Via AbortSignal.timeout() timeout option
Cancellation AbortController AbortController (signal); CancelToken deprecated
Upload progress Not available (streams only) onUploadProgress (XHR adapter)
Instances with defaults Manual axios.create()
XSRF protection Manual Reads cookie and sets header automatically (same origin)

Axios in Depth

Creating an Instance

Never use the global axios default in an application; create configured instances so different APIs (your BFF, a third-party service) have their own settings.

// api/client.js
import axios from 'axios';

export const api = axios.create({
  baseURL: import.meta.env.VITE_API_BASE_URL,
  timeout: 15_000,
  headers: { Accept: 'application/json' },
  withCredentials: true,              // send cookies to the BFF
  xsrfCookieName: 'XSRF-TOKEN',
  xsrfHeaderName: 'X-XSRF-TOKEN',
});

Request Configuration

const { data } = await api.get('/accounts/123/transactions', {
  params: { from: '2026-01-01', to: '2026-01-31', limit: 50 },
  signal: controller.signal,
});

await api.post('/payees', { name, routingNumber, accountNumber }, {
  headers: { 'Idempotency-Key': crypto.randomUUID() },
});

const formData = new FormData();
formData.append('document', file);
await api.post('/documents', formData, {
  onUploadProgress: e => setProgress(Math.round((e.loaded / (e.total ?? 1)) * 100)),
});

The response object has data, status, statusText, headers, config, and request. Axios errors (AxiosError) have response (if the server answered), request (if a request was sent but no response arrived), code (such as ECONNABORTED for timeouts or ERR_CANCELED), and config.

Interceptors

Interceptors are middleware for every request and response through an instance. They are the right place for cross-cutting concerns.

// Attach the access token and correlation ID
api.interceptors.request.use(async config => {
  const token = await auth.getAccessToken();       // from memory, refreshed as needed
  if (token) config.headers.Authorization = `Bearer ${token}`;
  config.headers['X-Request-ID'] = crypto.randomUUID();
  config.metadata = { start: performance.now() };
  return config;
});

// Measure latency and normalize errors
api.interceptors.response.use(
  response => {
    const ms = performance.now() - response.config.metadata.start;
    metrics.timing('api.latency', ms, { url: response.config.url, status: response.status });
    return response;
  },
  error => Promise.reject(normalizeError(error))
);

Refresh Token Handling with a Queue

When an access token expires, several requests may fail with 401 at the same moment. A naive interceptor triggers several refreshes. The correct pattern refreshes once and queues the other requests:

let refreshPromise = null;

api.interceptors.response.use(undefined, async error => {
  const original = error.config;
  if (error.response?.status !== 401 || original._retry || original.url === '/auth/refresh') {
    return Promise.reject(error);
  }
  original._retry = true;
  try {
    refreshPromise ??= auth.refresh().finally(() => { refreshPromise = null; });
    await refreshPromise;
    return api(original);              // replay with the new token (request interceptor adds it)
  } catch (refreshError) {
    auth.signOut({ reason: 'session_expired' });
    return Promise.reject(refreshError);
  }
});

Key details: the _retry flag prevents infinite loops, the refresh endpoint itself is excluded, a single shared promise de-duplicates refreshes, and a failed refresh signs the user out cleanly. If refresh tokens live in an HttpOnly cookie handled by a BFF, auth.refresh() is simply a POST that sets new cookies.

Retries with Backoff

const RETRYABLE = new Set([408, 429, 500, 502, 503, 504]);

api.interceptors.response.use(undefined, async error => {
  const config = error.config;
  const method = config.method?.toUpperCase();
  const idempotent = ['GET', 'HEAD', 'OPTIONS', 'PUT', 'DELETE'].includes(method)
    || Boolean(config.headers?.['Idempotency-Key']);
  const status = error.response?.status;
  const retryable = !error.response || RETRYABLE.has(status);
  config._retryCount = config._retryCount ?? 0;

  if (!idempotent || !retryable || config._retryCount >= 3 || axios.isCancel(error)) {
    return Promise.reject(error);
  }
  config._retryCount += 1;
  const retryAfter = Number(error.response?.headers?.['retry-after']) * 1000;
  const backoff = Math.min(1000 * 2 ** config._retryCount, 8000);
  const jitter = Math.random() * 300;
  await new Promise(r => setTimeout(r, retryAfter || backoff + jitter));
  return api(config);
});

Exponential backoff with jitter prevents synchronized retry storms that can overwhelm a recovering service. Only retry idempotent requests (or those with idempotency keys), honor Retry-After, and cap attempts. Libraries such as axios-retry implement this.

Cancellation

useEffect(() => {
  const controller = new AbortController();
  api.get('/search', { params: { q }, signal: controller.signal })
     .then(r => setResults(r.data))
     .catch(err => { if (!axios.isCancel(err)) setError(err); });
  return () => controller.abort();
}, [q]);

Typed API Layer with Runtime Validation

TypeScript types disappear at runtime; if the backend changes a field, your types still claim it exists. Validate at the boundary:

import { z } from 'zod';

const Transaction = z.object({
  id: z.string(),
  postedAt: z.string().datetime(),
  description: z.string(),
  amount: z.object({ value: z.string(), currency: z.string().length(3) }),
  status: z.enum(['pending', 'posted', 'reversed']),
});
const TransactionPage = z.object({ items: z.array(Transaction), nextCursor: z.string().nullable() });
export type TransactionPage = z.infer<typeof TransactionPage>;

export async function getTransactions(accountId: string, cursor?: string, signal?: AbortSignal) {
  const { data } = await api.get(`/accounts/${accountId}/transactions`, { params: { cursor }, signal });
  return TransactionPage.parse(data);
}

Alternatively, generate typed clients from an OpenAPI specification (openapi-typescript, Orval, OpenAPI Generator), which keeps the frontend in sync with the Spring Boot backend’s published contract (Chapter 14).

API Layer Architecture

A maintainable structure:

src/
  api/
    client.ts          # axios instance, interceptors
    errors.ts          # error normalization and types
    accounts.ts        # getAccounts, getAccount, getBalance
    payments.ts        # createTransfer, getTransferStatus
  features/
    payments/
      hooks/useCreateTransfer.ts   # TanStack Query mutation wrapping api/payments
      components/...

Components never call Axios directly. They call hooks, hooks call API functions, and API functions call the client. This layering makes it easy to mock at any level in tests, to swap transport (for example, adding GraphQL later), and to apply cross-cutting policies in one place.

Error Normalization

export type ApiError =
  | { kind: 'network'; message: string }
  | { kind: 'timeout'; message: string }
  | { kind: 'cancelled' }
  | { kind: 'unauthorized' }
  | { kind: 'forbidden' }
  | { kind: 'notFound' }
  | { kind: 'validation'; fieldErrors: Record<string, string>; message: string }
  | { kind: 'conflict'; message: string }
  | { kind: 'rateLimited'; retryAfterMs?: number }
  | { kind: 'server'; status: number; traceId?: string };

export function normalizeError(error: unknown): ApiError {
  if (axios.isCancel(error)) return { kind: 'cancelled' };
  if (!axios.isAxiosError(error)) return { kind: 'server', status: 0 };
  if (error.code === 'ECONNABORTED') return { kind: 'timeout', message: 'Request timed out' };
  if (!error.response) return { kind: 'network', message: 'Network unavailable' };
  const { status, data, headers } = error.response;
  switch (status) {
    case 401: return { kind: 'unauthorized' };
    case 403: return { kind: 'forbidden' };
    case 404: return { kind: 'notFound' };
    case 409: return { kind: 'conflict', message: data?.detail ?? 'Conflict' };
    case 400:
    case 422: return {
      kind: 'validation',
      message: data?.title ?? 'Invalid request',
      fieldErrors: Object.fromEntries((data?.errors ?? []).map((e: any) => [e.field, e.code])),
    };
    case 429: return { kind: 'rateLimited', retryAfterMs: Number(headers['retry-after']) * 1000 };
    default: return { kind: 'server', status, traceId: data?.traceId };
  }
}

UI code can now switch on error.kind exhaustively, map validation codes to localized messages, and show the trace ID to support staff.

CORS

Cross-Origin Resource Sharing is a browser mechanism that relaxes the same-origin policy in a controlled way. An origin is the combination of scheme, host, and port.

  • Simple requests (GET/POST/HEAD with only simple headers and content types like text/plain or application/x-www-form-urlencoded) are sent directly; the browser checks Access-Control-Allow-Origin on the response.
  • Preflighted requests (methods like PUT/DELETE, custom headers such as Authorization, or Content-Type: application/json) trigger an OPTIONS request first. The server must respond with Access-Control-Allow-Origin, Access-Control-Allow-Methods, Access-Control-Allow-Headers, and optionally Access-Control-Max-Age to cache the preflight.
  • Credentials (cookies) require withCredentials: true on the client and Access-Control-Allow-Credentials: true on the server, and the allowed origin cannot be the wildcard *.

CORS is enforced by browsers to protect users; it is not a server-side security control against non-browser clients. CORS errors are fixed on the server (or by routing through a same-origin proxy/BFF), never by the frontend “disabling” anything. In development, a dev server proxy (Vite’s server.proxy) avoids CORS by making API calls same-origin.

Caching

  • HTTP caching: Cache-Control: max-age=60 lets the browser reuse responses; ETag with If-None-Match allows cheap revalidation (304 Not Modified). Sensitive financial responses typically use Cache-Control: no-store.
  • Application caching: TanStack Query (Chapter 7) caches by query key in memory.
  • Service workers: offline support and cache strategies (stale-while-revalidate) for static assets; be very careful caching authenticated API responses.

Beyond REST

GraphQL

A single endpoint with a typed schema where clients request exactly the fields they need. Strengths: avoids over-fetching and under-fetching, strong typing and tooling, aggregation across services (federation). Challenges: caching is harder (POST requests, normalized client caches like Apollo or urql), query complexity must be limited to prevent abuse, and error handling differs (HTTP 200 with an errors array).

SOAP and XML Web Services

Many banks and payment networks still expose SOAP services described by WSDL, with XML payloads and WS-Security. Frontends almost never call SOAP directly; a backend (often Spring Boot) or BFF wraps them in REST/JSON. Knowing that the job description’s “web services” may include SOAP shows enterprise awareness.

gRPC and gRPC-Web

High-performance RPC with Protocol Buffers over HTTP/2, common between microservices. Browsers need gRPC-Web through a proxy (Envoy). Rare in frontend code but sometimes used for internal tools.

Real-Time Channels

Technique Direction Use case
Polling Client pulls Simple status checks (payment status every 5 s)
Long polling Client pulls, server holds Legacy real-time
Server-Sent Events Server to client over HTTP Notifications, streaming AI responses, status feeds
WebSockets Bidirectional Trading, chat, collaborative features
Webhooks Server to server Payment processor notifies your backend

Webhooks are server-to-server, but frontend engineers should know the flow: a third party (payment processor) calls your backend, which updates state and pushes an update to the browser via SSE or WebSockets, or the browser polls.

Integrating Third-Party Platforms

Enterprise apps integrate identity providers (Okta, Auth0, Azure AD/Entra ID, Ping), payment and bank-data platforms (Plaid for account linking, Stripe, card processors), document signing (DocuSign), analytics and tag managers, customer support chat, and feature flag services (LaunchDarkly).

Integration Principles

  1. Keep secrets on the server. API secret keys never go into frontend code or environment variables bundled into it. The browser uses publishable keys or short-lived tokens issued by your backend (for example, a Plaid link_token created server-side).
  2. Wrap SDKs behind an adapter. Expose your own interface (analytics.track, identityVerification.start) so the vendor can be replaced and so tests can mock it.
  3. Load scripts carefully. Load asynchronously, from the vendor’s official domain with Subresource Integrity (SRI) where possible, allow them explicitly in the Content Security Policy, and lazy-load only on pages that need them.
  4. Isolate sensitive input. Payment card fields from processors are often rendered in iframes hosted by the processor (Stripe Elements, hosted fields), so card data never touches your origin, which dramatically reduces PCI DSS scope.
  5. Handle failure. Third parties go down. Provide fallbacks, timeouts, and clear messaging; never block login or core flows on non-essential scripts like analytics or chat.
  6. Respect privacy and consent. Load tracking scripts only after consent where required, and never send PII or financial data to analytics.
  7. Verify server-side. Treat any result reported by a client-side SDK (such as “verification succeeded”) as untrusted until your backend confirms it with the vendor.

Example: Account Linking Flow

function LinkExternalAccount() {
  const { data: linkToken } = useQuery({
    queryKey: ['link-token'],
    queryFn: () => api.post('/external-accounts/link-token').then(r => r.data.linkToken),
    staleTime: 0,
  });
  const exchange = useMutation({
    mutationFn: publicToken => api.post('/external-accounts/exchange', { publicToken }),
    onSuccess: () => queryClient.invalidateQueries({ queryKey: ['external-accounts'] }),
  });
  const { open, ready } = usePlaidLink({
    token: linkToken,
    onSuccess: publicToken => exchange.mutate(publicToken),
  });
  return <Button onClick={() => open()} disabled={!ready}>Link a bank account</Button>;
}

The backend creates the link token with its secret, the browser opens the vendor’s UI, and the browser hands the resulting short-lived public token back to the backend, which exchanges it for an access token stored only on the server.

Debugging API Issues

A structured approach interviewers like:

  1. Reproduce and open the browser Network panel: status, timing, request and response headers, payload.
  2. Distinguish CORS failures (console message, failed preflight) from server errors.
  3. Check authentication: expired token, missing header, cookie not sent (SameSite, withCredentials).
  4. Compare the request with the API contract (OpenAPI spec): field names, types, content type.
  5. Use the request/trace ID to find server logs.
  6. Reproduce with curl or an HTTP client to isolate the frontend.
  7. Check environment configuration: base URL, proxy, feature flags.

Interview Questions and Answers

Q1. What is the difference between PUT and PATCH?

PUT replaces the entire resource with the request body and is idempotent. PATCH applies a partial update; it is idempotent only if the patch format makes it so (setting a field is idempotent; “increment by one” is not). Use PATCH for updating a few fields of a user profile and PUT when the client sends the complete representation.

Q2. What does idempotent mean, and why does it matter for the frontend?

An operation is idempotent if performing it multiple times has the same effect as once. It determines which requests can be retried automatically after timeouts or network failures. Non-idempotent requests like creating a payment must not be retried blindly; use an idempotency key so the server recognizes duplicates and returns the original result.

Q3. 401 versus 403?

401 Unauthorized means the request lacks valid authentication; the client should refresh the token or log in. 403 Forbidden means the user is authenticated but lacks permission; retrying or re-authenticating will not help, and the UI should explain the restriction.

Q4. Why use Axios instead of fetch?

Axios provides instances with shared configuration, request and response interceptors, automatic JSON handling, rejection on HTTP error statuses, built-in timeouts, upload progress events, and XSRF header support. Fetch is native and lighter but needs wrappers for those features. Either is fine if the team builds a consistent client layer.

Q5. What are Axios interceptors used for?

Cross-cutting concerns applied to all requests through an instance: attaching auth tokens and correlation IDs, logging and metrics, normalizing errors, refreshing expired tokens and replaying requests, retrying transient failures, and transforming data.

Q6. How do you handle token refresh when multiple requests fail with 401 at once?

In a response interceptor, on the first 401 start a single refresh request and store its promise; other failing requests await the same promise. After refresh succeeds, replay each original request once (marked with a retry flag to prevent loops). If refresh fails, clear the session and redirect to login. Exclude the refresh endpoint from the interceptor.

Q7. How do you cancel requests in Axios?

Pass an AbortController’s signal in the request config and call abort(). Check axios.isCancel(error) to ignore cancellations. The older CancelToken API is deprecated. Cancel in React effect cleanups and when a newer request supersedes an older one.

Q8. Explain CORS and preflight requests.

Browsers block cross-origin responses unless the server allows the origin with Access-Control-Allow-Origin. Requests with non-simple methods, custom headers, or JSON content type are preceded by an OPTIONS preflight asking permission for the method and headers. With credentials, the server must name the exact origin and send Access-Control-Allow-Credentials: true. CORS is configured on the server or avoided with a same-origin proxy.

Q9. How would you implement retries safely?

Retry only idempotent requests or requests with idempotency keys, only on transient failures (network errors, timeouts, 408, 429, 502, 503, 504), with exponential backoff plus jitter, honoring Retry-After, with a maximum number of attempts, and never on cancellation. Surface the final failure clearly.

Q10. Cursor versus offset pagination?

Offset pagination (page=3&size=50) is simple and supports jumping to a page, but inserts and deletes between requests cause skipped or duplicated items, and large offsets are slow in databases. Cursor pagination uses an opaque pointer to the last item, is stable under changes and efficient, but does not support random page access. Transaction histories usually use cursors.

Q11. How do you make API responses type-safe?

Generate types and clients from the OpenAPI spec so compile-time types match the contract, and validate responses at runtime at the API boundary with a schema library like Zod, so unexpected backend changes fail fast with a clear error rather than causing subtle UI bugs.

Q12. What is a backend-for-frontend (BFF)?

A server layer dedicated to a specific frontend that aggregates calls to downstream microservices, shapes responses for the UI, handles authentication and token storage (often keeping tokens server-side and using HttpOnly cookies with the browser), enforces security policies, and hides internal service topology. It reduces chattiness and keeps secrets off the client.

Q13. How do you upload large files with progress?

Use FormData with Axios and onUploadProgress, or upload directly to object storage with a pre-signed URL issued by the backend, which avoids routing large files through your API. For very large files use chunked or resumable uploads (multipart upload APIs, tus protocol). Validate file type and size on both client and server.

Q14. What is an ETag, and how can it help the frontend?

An ETag is a version identifier for a resource. The client can send If-None-Match to get a 304 Not Modified response and reuse its cached copy, saving bandwidth. With If-Match on updates, it provides optimistic concurrency control: the server rejects the update with 412 if someone else changed the resource since it was read.

Q15. How would you integrate a third-party payment form while minimizing PCI scope?

Use the processor’s hosted fields or iframe-based components so card data is entered into the processor’s origin and never touches our servers or JavaScript, then receive a token representing the card to send to our backend. Load the SDK from the official domain, permit it in CSP, and confirm the payment server-side.

Q16. Why should third-party SDKs be wrapped in adapters?

To decouple application code from vendor APIs, allowing replacement or upgrades in one place, consistent error handling and logging, easier mocking in tests, and enforcement of policies such as consent checks and data minimization.

Q17. How do you debug a request that works in Postman but fails in the browser?

Likely causes are CORS (Postman ignores it), cookies not being sent (SameSite or missing withCredentials), different headers or content type, CSRF protection, a Content Security Policy connect-src restriction, or a mixed-content block. Check the console and Network panel, compare raw requests, and inspect preflight responses.

Q18. What is the difference between REST and GraphQL from a frontend perspective?

REST exposes multiple resource endpoints with fixed response shapes and uses HTTP caching naturally. GraphQL exposes one endpoint with a schema, letting the client choose fields and fetch related data in one round trip, but requires a specialized client cache, query cost controls, and different error handling. The choice depends on data shape complexity, team tooling, and caching needs.

Q19. How do you handle rate limiting (429)?

Respect the Retry-After header, back off exponentially, reduce request volume with debouncing, caching, and de-duplication, and show users a clear message if an action must wait. Avoid retry storms with jitter and caps.

Q20. How would you design the frontend for an asynchronous operation that returns 202 Accepted?

Show the operation as submitted or pending with its reference ID, then poll a status endpoint with backoff or subscribe to updates via SSE or WebSockets. Update the UI when it reaches a terminal state, handle timeouts by explaining that processing continues, and make the status visible later in a history view.

Q21. How do you protect against duplicate form submissions?

Disable the submit button and show progress while the request is in flight, generate an idempotency key once per logical submission (stored with the form state so retries reuse it), and rely on the server to de-duplicate. Disabling the button alone is not enough because of retries and double clicks before re-render.

Q22. What are SOAP web services, and how would a React app interact with one?

SOAP is an XML-based protocol with WSDL contracts, common in legacy banking systems. A React app should not call it directly; a backend service or BFF translates it into a REST/JSON API, handling XML parsing, WS-Security, and error mapping.

Q23. What information should every API error shown to support include?

A user-friendly message, a stable error code, and a correlation or trace ID that support can use to find server logs, plus a timestamp. Never show stack traces, internal service names, or sensitive data.

Key Takeaways

  • Know HTTP methods, status codes, headers, idempotency, caching, and CORS precisely.
  • Build a layered API client: configured Axios instance, interceptors, normalized errors, typed and validated functions, and hooks on top.
  • Refresh tokens once with a shared promise; retry only idempotent requests with backoff and jitter.
  • Integrate third parties through adapters, keep secrets server-side, and verify outcomes on the server.

← Part 7: State Management: Zustand, Context, and Asynchronous Data · All chapters · Part 9: Authentication, MFA, and Frontend Security →

Senior Frontend Interview Guide (full PDF)Every question and answer from this series in one printable guide.PDF · 12.5 MB
Free download: unlock it in one step

Share this page

or

Mailchimp opens in a new tab to confirm. Unsubscribe any time.

Filed underInterview Guide
Rupak Nepali
Written byRupak Nepali

Author of four Opencart book.
The recent are Opencart 4 developer book and Opencart 4 user manual

Leave a Reply

Your email address will not be published. Required fields are marked *