41. Safe retries for payments
A mobile app calls POST /payments. The network times out, so the app retries, and the customer is charged twice. What is the most robust fix?
- A.Client sends a unique Idempotency-Key; the server stores it with the result under a unique constraint and returns the saved result for repeats
- B.Disable retries in the mobile app completely
- C.Change the endpoint from POST to GET so it becomes idempotent
- D.Check if a payment with the same amount was made in the last 10 seconds and skip it
Show answer
Answer: A. Client sends a unique Idempotency-Key; the server stores it with the result under a unique constraint and returns the saved result for repeats
An idempotency key lets the server recognise a retry of the same logical request. Storing the key atomically (unique index) before or with the charge, and returning the stored response for duplicates, makes retries safe. Disabling retries hurts reliability, GET must not change state, and amount-based checks block real repeat purchases.
42. Retries at every layer
A mobile app, the gateway and the order service each retry a failed call up to 3 attempts in total. When the payment service is down, how many calls can one tap cause at the payment service?
- A.27
- B.9
- C.3
- D.6
Show answer
Answer: A. 27
Each layer makes up to 3 attempts, and each attempt from the layer above triggers that, so it multiplies: 3 × 3 × 3 = 27. This turns a small outage into a retry storm. Retry in one layer only, use backoff with jitter and a retry budget.
43. Users uploading 2 GB videos
Users upload videos up to 2 GB, and routing them through your API servers ties up memory and bandwidth. What is the standard design?
- A.Raise the JSON body size limit to 2 GB
- B.Let clients upload to storage with a presigned URL
- C.Base64-encode each video inside a JSON field
- D.Have users email their large files to support
Show answer
Answer: B. Let clients upload to storage with a presigned URL
The API checks the user and returns a presigned URL valid for a few minutes, and the client uploads straight to object storage, often in multiple parts. The API then records or processes the file via a callback or event. Base64 makes the payload about 33% larger.
44. Rate limiter blocks everyone together
Behind a load balancer, an Express rate limiter keyed on req.ip starts blocking all users at once. What is the likely cause?
- A.req.ip is the load balancer's IP; set 'trust proxy' correctly
- B.The limiter counts requests per URL, not per IP
- C.Express caches req.ip for every user
- D.Load balancers strip all headers from requests
Show answer
Answer: A. req.ip is the load balancer's IP; set 'trust proxy' correctly
Without 'trust proxy', Express reports the address of the direct connection, which is the load balancer, so every user shares one counter. Setting app.set('trust proxy', 1) (the number of trusted hops) makes req.ip use X-Forwarded-For. Trusting it blindly would let clients fake their IP.
45. Every page load counts 80 million rows
GET /events?limit=50 also returns total, computed with COUNT(*) on an 80-million-row table, and it is the slowest part of the request. What is a common API design change?
- A.Cache the count in the browser forever
- B.Count only the rows on the current page
- C.Return has_more or a next cursor instead of an exact total
- D.Return the total in a response header instead
Show answer
Answer: C. Return has_more or a next cursor instead of an exact total
An exact count must scan a large index or table for every request, even for page one. Many APIs return only whether more pages exist (fetch limit + 1 rows) or a next cursor, and offer an approximate or separately cached total if a UI really needs it. Moving the same count to a header costs the same.
46. Search filters too long for a URL
A search endpoint takes dozens of filters, nested conditions and a list of up to 500 SKUs. Long GET URLs fail at some proxies. What is a reasonable design?
- A.Split the SKU list across many GET requests
- B.POST /products/search with the filters in a JSON body
- C.Send the filters in a custom request header
- D.Use GET with a body, since HTTP allows it
Show answer
Answer: B. POST /products/search with the filters in a JSON body
Proxies and servers often limit URLs to around 8 KB. A dedicated search resource with POST and a JSON body is a common pattern, accepting that it is not cached by default. A GET body has no defined meaning and is often dropped, and large headers hit similar limits.
47. A PUT wipes the phone number
A client sends PUT /customers/7 with only {"email": "new@example.ae"}. The server follows PUT semantics strictly. What happens to the customer's phone number?
- A.It is kept, because PUT only changes sent fields
- B.It is cleared, because PUT replaces the whole resource
- C.It is copied into the email field
- D.The server must reject PUT bodies with one field
Show answer
Answer: B. It is cleared, because PUT replaces the whole resource
PUT means 'make the resource equal to this representation', so fields left out are removed or reset to defaults. For changing one field, use PATCH, for example JSON Merge Patch. Many bugs come from clients that treat PUT like PATCH, so document the behaviour clearly.
48. Locking accounts after 5 failures
Accounts are locked for 24 hours after 5 wrong passwords. Attackers start locking out real users on purpose. What is a better approach?
- A.Lock accounts for good after 3 failures
- B.Remove every limit on login attempts
- C.Growing delays and risk checks, not hard locks
- D.Let anyone unlock by typing the email
Show answer
Answer: C. Growing delays and risk checks, not hard locks
Hard lockouts let anyone who knows an email deny service to that user. Growing delays, per-account and per-IP limits, CAPTCHA after failures and step-up checks slow attackers while the real user can still get in. Alert users about unusual failed attempts.
49. Preventing double charges on retry
The checkout page sends POST /api/payments. The request times out on a slow network, the client retries, and the customer is charged twice. What is the most robust fix?
- A.Disable the Pay button for 2 seconds after a click
- B.Increase the server timeout to 60 seconds
- C.Send a client-generated idempotency key; the server stores it with a unique constraint and returns the saved result for any repeat with the same key
- D.Change the endpoint from POST to GET
Show answer
Answer: C. Send a client-generated idempotency key; the server stores it with a unique constraint and returns the saved result for any repeat with the same key
Timeouts mean the client cannot know whether the first request succeeded, so retries must be safe. An idempotency key stored atomically (unique index) lets the server detect repeats and return the first response instead of charging again. Disabling buttons and longer timeouts reduce but do not remove the race; GET must not have side effects.
50. Large IDs change in the browser
The database uses BIGINT ids, and the API serializes them as JSON numbers. What does the client print here?
const body = '{"id": 9007199254740993}'
console.log(JSON.parse(body).id)- A.9007199254740993
- B.9007199254740992
- C.An exception: number too large
- D.9007199254740993n
Show answer
Answer: B. 9007199254740992
JavaScript numbers are 64-bit floats and are exact only up to 2^53 - 1 (9007199254740991), so larger integers are silently rounded. The client then requests or updates the wrong record. Send 64-bit IDs as strings in JSON, which is what many large APIs do.
51. Items missing between pages
An activity feed uses this cursor query. After a bulk import gave 300 rows the same created_at, users report items missing between pages. What is the fix?
SELECT * FROM events
WHERE created_at < $1 -- created_at of last row on previous page
ORDER BY created_at DESC
LIMIT 50;
- A.Switch to OFFSET pagination for imported data
- B.Use <= instead of < in the WHERE clause
- C.Order by (created_at, id) and use both as the cursor: WHERE (created_at, id) < ($1, $2) ORDER BY created_at DESC, id DESC
- D.Increase LIMIT to 500 so ties fit on one page
Show answer
Answer: C. Order by (created_at, id) and use both as the cursor: WHERE (created_at, id) < ($1, $2) ORDER BY created_at DESC, id DESC
When many rows share the boundary timestamp, < skips the rest of them, while <= would repeat rows instead. Adding a unique tie-breaker such as id gives a strict total order, and row comparison makes the cursor exact. Back it with an index on (created_at, id).
52. Choosing an API contract approach
Your API will be used by the Next.js web app, native iOS and Android apps and outside partners. The team wants end-to-end type safety. Which approach fits best?
- A.An OpenAPI contract with generated typed clients for each platform
- B.tRPC only, because it gives every client full end-to-end types without writing any schema
- C.Share the TypeScript types folder with partners by email
- D.Skip a contract and document endpoints in a README
Show answer
Answer: A. An OpenAPI contract with generated typed clients for each platform
OpenAPI is language-neutral, so Swift, Kotlin, partner code and your TypeScript app can all generate clients from one contract and catch breaking changes in CI. tRPC is excellent when the client and server are TypeScript in the same codebase, but it does not give outside or native clients a stable, documented contract. Some teams use tRPC internally and OpenAPI for the public surface.
53. Duplicate orders from offline retries
An offline queue retries a "place order" POST after a timeout. The server had actually processed the first attempt, so the customer gets two orders. What is the right fix?
- A.Retry less often, waiting at least ten minutes between attempts, so the server has time to finish and its own duplicate check can catch up
- B.Create an idempotency key on the device, keep it with the queued request and send it on every retry, so the server returns the first result
- C.Send the order as a GET request
- D.Delete the request from the queue after the first attempt, whatever the outcome
Show answer
Answer: B. Create an idempotency key on the device, keep it with the queued request and send it on every retry, so the server returns the first result
A timeout does not tell the client whether the server received the request. A client-generated idempotency key that stays the same across retries lets the server detect repeats and return the original response. Dropping the request after one try would lose orders, and fewer retries only make duplicates rarer.
54. Renaming a field in the API
The backend team wants to rename `price` to `amount` in the orders API this week. Why must mobile apps be considered differently from the web app?
- A.Mobile apps download the API schema daily from the stores
- B.Old app versions stay installed for months and still expect price
- C.Mobile apps cannot parse JSON fields named amount
- D.Mobile releases always ship on the same day as the backend
Show answer
Answer: B. Old app versions stay installed for months and still expect price
Web clients update on the next page load, but many users run app versions that are months or years old. Breaking changes need versioned endpoints or additive changes: add amount, keep price until usage by old versions is negligible, then remove it. Tracking app versions in API logs shows when it is safe.
55. HTTP/3 on a network that blocks UDP
A corporate firewall blocks outbound UDP 443. What happens when a browser tries a site that supports HTTP/3?
- A.The site fails to load at all
- B.The browser tunnels QUIC inside DNS
- C.It falls back to HTTP/2 or HTTP/1.1 over TCP
- D.It switches to plain HTTP on port 80
Show answer
Answer: C. It falls back to HTTP/2 or HTTP/1.1 over TCP
QUIC runs over UDP. Browsers discover HTTP/3 via Alt-Svc or DNS HTTPS records and race or fall back to TCP if QUIC does not work, so pages still load over HTTP/2. Servers must keep TCP support for such networks.
56. Static pages never cached
Your marketing pages have long Cache-Control headers, yet the CDN reports 0% hits. Every response includes a Set-Cookie for an analytics session. What is the likely cause?
- A.CDNs never cache HTML pages
- B.Cache-Control is ignored over HTTPS
- C.Many CDNs skip caching responses that set cookies
- D.Analytics cookies make pages too large to cache
Show answer
Answer: C. Many CDNs skip caching responses that set cookies
Caching a response with Set-Cookie could hand one user's cookie to others, so many CDNs skip caching by default. Remove Set-Cookie from cacheable responses, set cookies from client-side scripts, or configure the CDN to strip them deliberately.
57. HTTP/3 on lossy mobile networks
Your app's users on congested mobile networks see long stalls with HTTP/2. Why might HTTP/3 help most in this case?
- A.QUIC recovers loss per stream, avoiding TCP blocking
- B.HTTP/3 compresses images better than HTTP/2
- C.HTTP/3 needs no encryption, so it is faster
- D.HTTP/3 uses larger packets to beat congestion
Show answer
Answer: A. QUIC recovers loss per stream, avoiding TCP blocking
With HTTP/2 all streams share one TCP connection, so one lost packet stalls every stream. QUIC handles each stream independently and also handles network changes better, which matters most where loss is common.
58. The gateway throttles the test
The API gateway allows 100 rps per client IP. Your test sends 1,000 rps from one load generator IP and gets mostly 429s. The goal is to test the services behind the gateway. What should you do?
- A.Agree a test exemption or spread load like real clients
- B.Report that the services can only handle 100 rps
- C.Remove all error checks from the script
- D.Retry every 429 immediately until it succeeds
Show answer
Answer: A. Agree a test exemption or spread load like real clients
Real traffic comes from many clients, each under its own limit, so one IP at 1,000 rps is not realistic. Work with the platform team to allow-list the test IPs, use many source addresses, or test the services directly. Immediate retries only make the throttling worse.
59. Infinite scroll category pages
A category page loads more products by infinite scroll with no change in URL. Products beyond the first 24 never get indexed. What is the fix?
- A.Increase the scroll speed so Googlebot sees more
- B.Put all 2,000 products into the very first page load
- C.Give each chunk its own URL, linked with <a href>
- D.Add the products only to the meta description
Show answer
Answer: C. Give each chunk its own URL, linked with <a href>
Googlebot does not scroll or click "load more" like a user. Google recommends a paginated series with unique URLs such as ?page=2, linked with normal anchors, while infinite scroll can still update the URL for users. Then every product is reachable.
60. A concierge MVP that customers love
You validated demand by manually doing the work for 10 customers (a concierge MVP). They love it. What must you test before scaling?
- A.Whether customers like your team personally
- B.Whether 10 customers is enough for an IPO
- C.Whether software can deliver the value at viable cost
- D.Nothing; love proves the business works
Show answer
Answer: C. Whether software can deliver the value at viable cost
A concierge MVP proves people want the outcome, but humans may be delivering hidden value that software cannot copy cheaply. Test whether an automated version keeps satisfaction and unit economics before investing in scale.