Where bugs hide: a risk map of any web app
It is Thursday afternoon. The release goes out tomorrow at nine, and you have three hours to test an application with forty screens. You cannot test everything, so the useful question is not how to test. It is where to look first.
Bugs are not spread evenly across an application. They gather in a few places, and those places are the same in almost every web app.
Short version
- Bugs cluster. Five zones hold most of them.
- Zone 1: boundaries, where one system hands data to another.
- Zone 2: code changed in the last two weeks, and the code around it.
- Zone 3: code nobody owns — no tests, no author, called "legacy".
- Zone 4: third-party integrations you do not control.
- Zone 5: date and time logic.
- When time is short, test the zones. Skip the calm middle of the app.
How to use this map
Each zone below gives you three things: why bugs gather there, the signals to look for, and the tests to run. You do not need all five every time. Pick the zones that match this release.
| Risk zone | Signal to look for | First test to run |
|---|---|---|
| Boundaries between systems | A response arrives with status 200 but an empty body | Submit a form with one optional field left blank and read the network response |
| Recently changed code | Any file touched in the last two weeks | Retest the changed feature, then the feature next to it |
| Code nobody owns | No tests, and nobody answers "who owns this?" | Walk the plain happy path end to end |
| Third-party integrations | Webhooks, tokens, payment providers, email | Send the same webhook event twice and check for duplicates |
| Date and time logic | Dates shown with no time zone, "today" filters | Change your machine time zone and reload the page |
Zone 1: boundaries between systems
A boundary is any point where one part of the system hands data to another: the browser to the server, one backend service to another, your app to a database. Bugs gather here because each side was built by different people, at different times, with different assumptions about the data.
Each side is usually tested on its own and passes on its own. Almost nobody tests the gap between them.
Signals to look for
- A request returns status
200, which means success, but the body is empty. The screen then shows blanks or the word "undefined". - A spinner that never stops, because the frontend waits for a field the backend stopped sending.
- Two teams own the two sides of one feature.
Tests to run
- Open DevTools (press F12) and go to the Network tab, which lists every request the page makes. Submit a form with all optional fields blank, then read the response. A
200with{}inside is a bug waiting to appear on screen. - Send the longest realistic value you can: a 300-character company name, an address with three lines.
- Send characters that break naive code: an accented name like Zoë, an apostrophe as in O'Brien, a leading space.
- Set the network to Offline mid-save, then bring it back. Does the app recover, or save twice?
- Load a record created by an older version of the app. Old records often miss fields the new screen expects.
Zone 2: recently changed code and its neighbours
A regression is a bug in something that used to work. Regressions live in and around fresh changes.
New code has been read by few people and run by fewer. The damage is also rarely limited to the file that changed. A developer edits a shared button component to fix one screen, and four other screens change with it.
Signals to look for
- The pull request says "small refactor" or "tidy up". These touch many files and get light review.
- A shared component, a utility file, or a config file changed.
- The change landed late, close to the release cut.
Tests to run
- Ask for the list of changed files.
git log --since="2 weeks ago" --name-onlygives it in seconds. - Test the changed feature itself, including the case the change was meant to fix.
- Test the feature directly next to it in the interface. If checkout changed, test the cart.
- Test anything that reads the same data. If the order form changed, open the order list, the invoice, and the customer email.
- Reopen the bugs fixed in the last release and try each one again. Fixes get reverted by accident.
Zone 3: code nobody owns
Every product has a corner nobody wants. The person who wrote it left in 2023, and there are no automated tests. People call it "legacy", which usually means "code we are afraid to touch".
Bugs gather here because nothing pushes them out. No test catches them and no owner notices them.
Signals to look for
- Nobody can answer "who owns this?" in one sentence.
- The feature is missing from the product documentation and from onboarding.
- Comments in the code name people who no longer work there.
Tests to run
- Walk the plain happy path end to end. In neglected code, the ordinary route is often broken and simply unreported.
- Test it with a second account that has fewer permissions, such as [email protected] as a viewer. Old code often checks permissions in its own private way.
- Force an error. Submit an invalid value and see whether the app shows a clear message or a blank page.
- Use it together with a newer feature, and with data from a recent migration. Old export screens frequently ignore new fields.
Zone 4: third-party integrations
An integration is a connection to a service you do not control: Stripe for payments, ClickUp for tasks, an SSO provider for login, an email service. SSO means single sign-on, where one account logs you into several products.
These fail in ways your own code never does. The other service is slow, down, or sends the same message twice, and none of that shows up in your test environment on a normal day.
Signals to look for
- Webhooks, the messages a third party sends your server when something happens. They arrive out of order, twice, or late.
- Access tokens, the short-lived keys that prove who you are. They expire, sometimes mid-session.
- Rate limits, which cap how many requests you may send per minute. Going over returns
429 Too Many Requests. - Sandbox credentials on https://staging.example.com pointing at production data, or the reverse.
Tests to run
- Send the same webhook twice. Resend a Stripe
checkout.session.completedevent from the Stripe dashboard. The customer must not be charged twice or receive two orders. - Make the webhook fail. If your endpoint returns
502 Bad Gateway, most providers retry for hours. Confirm the retry does not create a second record. - Let a token expire. Log in with SSO, leave the tab open past the token lifetime, then click Save. You want a clean prompt to sign in again, not a silent failure that loses the form.
- Block the third party. In DevTools, right-click a request and choose Block request domain, then use the feature. The screen should say something useful rather than spin forever.
- Hit the rate limit on purpose. Click "Send invite" twenty times in ten seconds and watch for
429in the Network tab.
Zone 5: date and time logic
Date code looks simple and is not. A time zone is the local offset from UTC, the single global reference time. Servers store UTC, browsers show local time, and the conversion between them is where records land on the wrong day.
Most developers test in one time zone, in the middle of a normal month, on a machine set to the server clock. Real users are not so tidy.
Signals to look for
- Dates shown with no time zone label.
- Filters named "Today", "This week", or "Last 7 days".
- Anything that renews: trials, subscriptions, invoices, reminders.
Tests to run
- Change your machine time zone to Auckland, then to Los Angeles, and reload. A record created a moment ago should still appear under "Today" in both.
- Create a record at 23:50 local time and check which day it is filed under in the list, the report, and the export.
- Test the end of a month. Start a one-month trial on 31 January and check the renewal date. Then check 29 February 2028, a leap day.
- Cross a daylight saving change. Set your clock to the day the local offset shifts and run a report covering that day.
- Sort a list where two items share the same timestamp to the second. The order should stay stable between reloads.
Putting the map to work
Before your next release, take ten minutes and mark the zones. Which boundaries changed? What shipped this week? Which integration was touched? That short list is your test plan, and it beats a checklist you cannot finish.
Zone bugs depend on invisible detail, such as the exact response body or status code, so a tool like Crosscheck that captures console logs, network requests, and environment details as you report saves you reproducing the whole thing twice.
Frequently asked questions
Does this replace a full test plan? No. It tells you what to test first when you cannot test everything. Use the map to order your work, not to shorten it.
How do I find out what changed if I have no access to the code?
Ask for the release notes or the list of merged pull requests. Most teams will run git log --since="2 weeks ago" --name-only for you if you explain why.
Which zone should I check first when I only have an hour? Recently changed code, then the boundaries it touches. Those two produce the bugs most likely to reach users this week.
Can I test third-party integrations without real accounts? Usually yes. Providers such as Stripe offer sandbox modes with test cards and a button to resend webhook events, which covers duplicate and retry testing.
Is knowing where to find bugs in software something I can learn quickly? The map takes minutes to learn. Applying it to your own product takes a few releases, because you need to see which zones in your app actually break.




