Problem says X → think Y
The searchable index of this domain. The left column is what the problem sounds like when someone describes it to you; the right column is the thing to think before you start typing.
63 of 63 rows
| The problem says | Think |
|---|---|
| One endpoint is slow and nobody knows which part of it | Do not guess — trace the request. A span per layer tells you whether the time is in your code, the pool, the query or a dependency.Tracing From the Backend's Side → |
| The same rows are read on nearly every request | Candidate for a cache — but first ask what happens when it is stale, and who invalidates it. A cache you cannot invalidate is a consistency bug you have not hit yet.Cache-Aside → |
| One request makes hundreds of small database round trips | N+1. Batch the loads or eager-load the relation; the fix is one query shaped for the access pattern, not a faster database.The N+1 Query Problem → |
| The client posted twice and you charged twice | Idempotency. A client-supplied key, stored with the result, so the second attempt returns the first outcome instead of repeating it.Idempotency Keys → |
| A third-party API got slow and your whole service got slow with it | Timeout first, then circuit breaker. Without a timeout their outage becomes your outage, because your workers are all parked in their socket.Timeouts → |
| A dependency fails intermittently and the operation is safe to repeat | Retry with exponential backoff and jitter, bounded attempts. Retries without jitter synchronise clients into a second wave.Backoff and Jitter → |
| Users upload large files and the request times out or the process balloons | The bytes should not pass through your process. Presigned upload straight to object storage; your backend issues the URL and records the result.Presigned URLs → |
| The work takes longer than anyone will wait for a response | Background job. Accept, persist the intent, return a handle the client can poll or subscribe to.Request or Background? → |
| Traffic arrives in bursts far above the steady rate | A queue turns a burst into a backlog — a latency problem instead of an error. That is a trade, not a free win.Job Queues → |
| The queue depth is growing and never comes back down | Consumers are slower than producers. Either add worker capacity or apply backpressure at the producer; adding queue capacity only delays the same conversation.Queue Backlog → |
| 1000 concurrent requests, database pool of 20 | Requests are queueing for connections, not failing. Latency climbs first, errors arrive later when the acquire timeout fires.Connection Pools → |
| It worked on one instance and broke when you deployed two | Shared state living in process memory — a counter, a lock, a session, a cache. Externalise it or accept it is per-instance.Stateless Services → |
| A user changed the id in the URL and saw someone else's data | Object-level authorization. Authentication proved who they are; nothing checked whether this row is theirs.Object-Level Authorization → |
| The response is enormous and gets slower as the table grows | Pagination, and cursor-based if the data changes underneath the reader. Also ask why the client needs every row at all.Pagination That Survives a Large Table → |
| A provider sends the same webhook three times | Duplicate delivery is the normal case, not the error case. Make the handler idempotent on the provider's event id.Webhook Idempotency → |
| A deploy breaks because the old version is still running against the new schema | Expand and contract. Every migration must be compatible with the version currently serving traffic, in both directions.Expand and Contract Migrations → |
| p50 is fine, p99 is terrible, and the CPU looks idle | Something is queueing — pool, event loop, worker set, a downstream lock. Idle CPU with rising tail latency is a waiting problem, not a compute problem.Why Is My API Slow? → |
| Latency stepped up at a specific minute and never came back | Suspect the deploy first. Correlate the change point with releases, config changes and flag flips before reading any code.Deploys Are the First Suspect → |
| Memory grows monotonically until the process is killed and restarts | Something long-lived is holding references — an unbounded in-process cache, a growing array, listeners never removed.Memory Leaks in Backend Services → |
| A dependency recovered but your service stayed down | Retry storm. Every client retried at once and re-broke it. Backoff, jitter and a breaker that stays open long enough to let recovery happen.Retry Storms → |
| Every request is slow, all at once, across unrelated endpoints | A shared resource is saturated: the pool, the loop, the CPU, or one dependency that everything touches.Connection Pool Exhaustion → |
| A single CPU-heavy operation makes every concurrent request slow | On a single-threaded runtime you blocked the loop. The fix is to move the work off the loop, not to optimise the loop.Blocking the Event Loop → |
| One failing service takes down three that do not depend on it | Cascading failure through a shared resource — a thread pool, a connection pool, a gateway. Bulkheads isolate the blast radius.Cascading Failure → |
| A slow dependency consumed every worker, so healthy work could not run | Bulkhead. Give each dependency its own bounded concurrency so it can only exhaust its own share.Bulkheads → |
| A dependency is hard down and you keep calling it anyway | Circuit breaker: fail fast while it is broken, probe occasionally, and decide what a degraded response looks like.Circuit Breakers → |
| Two requests read the same row, both updated it, one update vanished | Lost update. Optimistic concurrency with a version column, or make the write atomic in the database rather than in your process.Optimistic Concurrency → |
| A counter or balance drifts from the truth under load | Read-modify-write in application code. Push the arithmetic into a single atomic statement the database serialises for you.Atomic Operations → |
| Two workers picked up the same job and both did it | At-least-once delivery is the default. The job must be idempotent, or claimed with a lock the queue actually enforces.Job Idempotency → |
| The database write succeeded and the event was never published | Dual write. Two systems, no shared transaction. The outbox pattern makes the event part of the same commit.The Dual Write Problem → |
| A transaction holds open while you call a payment provider | Network calls do not belong inside a transaction — the row locks and the pooled connection are held for however long they take to answer.External Calls Inside a Transaction → |
| Deadlocks appear under concurrency and disappear when you retry | Two transactions taking the same locks in different orders. Fix the ordering; the retry is a mitigation, not the answer.Deadlocks in Application Code → |
| A cache key expires and a hundred requests all rebuild it at once | Stampede. Coalesce the rebuild behind one worker, or serve the stale value while one refresh runs.Cache Stampede → |
| Different instances return different answers for the same cached read | A local in-process cache with no invalidation channel. Either accept per-instance staleness deliberately or move it to a shared cache.Local vs Distributed Cache → |
| You added a cache and the hit rate is low but the bugs are real | Some workloads should not be cached: highly personal, rarely reread, or cheap to compute. A cache with a low hit rate is pure added complexity.When Not to Cache → |
| The API returns the database row, and renaming a column broke a client | Schema leakage. A response model is a contract; the table is an implementation detail that must be free to change.Schema Leakage → |
| A 500 response contains a stack trace, a SQL fragment or a hostname | Error boundary. Map internal errors to a safe external shape, log the detail with a correlation id the user can quote.Not Leaking Your Internals → |
| You have the user's complaint but cannot find their request in the logs | Correlation id, generated at the edge, propagated through every layer and job, returned to the client.Correlation Ids That Survive Every Hop → |
| Logs are human sentences you cannot filter or aggregate | Structured logging. Fields, not prose — the value of a log line is what you can query it by six months later.Structured Logging → |
| The health check is green while every request fails | It is checking the process, not the service. Separate liveness from readiness, and decide deliberately which dependencies a readiness check includes.Health Checks: Startup, Readiness, Liveness → |
| Deploys drop in-flight requests and clients see connection resets | Graceful shutdown: stop accepting, drain in-flight work, close pools, exit — and make sure the orchestrator's grace period is longer than your drain.Graceful Shutdown → |
| One customer's query load degrades every other customer | Noisy neighbour in a shared tenant. Per-tenant limits and isolation, decided at the data-access layer rather than hoped for.Multi-Tenancy → |
| A tenant filter is applied in most queries but not all of them | Tenant isolation must be structural — enforced in one place that queries cannot bypass, not repeated in every handler.Tenant Isolation → |
| A user string ends up inside a SQL statement | Parameterise. String interpolation into SQL is the vulnerability; an ORM does not make it safe if you still build the fragment by hand.SQL Injection → |
| Your backend fetches a URL the user supplied | SSRF. Your service can reach the metadata endpoint and the private network; the user's browser cannot. Allow-list, resolve, and re-check after redirects.SSRF — When the Backend Fetches a URL → |
| An API token turned up in a log line or an error report | Secrets in logs. Redact at the serialiser, not in review — anything that can be logged eventually will be.Secrets in Logs → |
| Config differs between environments and the failure appears only in production | Validate configuration at startup and fail loudly. A missing variable should stop the process, not surface as a null three hours later.Validate at Startup, Fail Loudly → |
| Tests pass, production breaks, and the difference is the database | Mocked persistence proves your mock. Integration tests against a real engine are what prove the query, the constraint and the migration.Test Against the Real Database → |
| Two services agreed on a contract in a document and disagreed in production | Contract tests. The consumer's expectations, executed against the producer, in the producer's pipeline.Contract Tests Between Services → |
| Serialization shows up as the hot path in a CPU profile | Payload size and shape are a CPU cost, not just a bandwidth cost. Return fewer fields before reaching for a faster serialiser.What Serialization Costs → |
| A caller hammers one endpoint and degrades it for everyone else | Rate limiting, scoped to the identity that matters — key, tenant or IP — and returning a response that tells them when to come back.Rate Limiting → |
| Your rate limiter lets through double the limit at the window edge | A fixed window does that by construction. Sliding window or token bucket, and know which shape you actually promised.Rate Limit Algorithms → |
| Sessions vanish when a request lands on a different instance | Session state is in process memory. Move it to shared storage; sticky sessions are a workaround with its own failure mode.Where Sessions Live → |
| Auth runs after an expensive middleware, so anonymous traffic costs you money | Middleware order is a correctness and cost decision. Cheap rejections first; expensive work only for requests that earned it.Authenticate First, or Rate-Limit First? → |
| A role check passes but the user still should not see this particular record | Role-based access answers "may this kind of user do this kind of thing". It never answers "is this row theirs".Authentication vs Authorization → |
| Read traffic is the bottleneck and the primary is write-light | Read replicas — after deciding which reads tolerate replication lag and which must read their own writes.Read Replicas From the Application → |
| Autoscaling reacts long after users have already noticed | CPU is a lagging signal for an IO-bound service. Scale on the queue or concurrency signal that actually leads the pain.Autoscaling a Backend → |
| A job failed 25 times and is still being retried | Dead-letter queue with a retry limit, plus something that actually looks at the dead letters. An infinite retry hides a permanent failure.Dead-Letter Queues → |
| A search page shows data the database updated minutes ago | The index is a derived store kept in sync asynchronously. Decide the acceptable lag and make the sync recoverable.Keeping a Search Index in Sync → |
| A feature works but you cannot turn it off without a deploy | Feature flag. Separate deploying the code from enabling the behaviour, and give yourself an exit that does not need a release.Feature Flags: Rollout, Kill Switches and Debt → |
| An unbounded fan-out issues as many concurrent calls as there are items | Bound the concurrency. Promise.all over a thousand items is a thousand simultaneous dependency calls and a self-inflicted load test.Unbounded Concurrency → |
| An LLM agent can call your internal endpoints | A tool call is a backend call. Same authorization, same limits, same audit trail — the prompt is not a security boundary.A Tool Call Is a Backend Call → |
| An agent loop calls a paid API until the bill is noticed | Budgets and caps enforced server-side per run and per tenant, with the loop terminated by the backend rather than by the model changing its mind.Budgets, Deadlines and Step Limits → |
| You are about to split the monolith because the codebase feels big | Size is not a distribution problem. Modular boundaries inside one deployable get most of the benefit without turning function calls into network calls.The Modular Monolith → |