Some sync bugs never show up on a clean connection or in full airplane mode. They only appear on a connection that's degraded but not dead, dropping some packets, timing out intermittently, succeeding just often enough to look fine in a quick manual test. These are some of the most frustrating bugs to chase because the obvious testing tools don't reproduce them.
Why Clean On/Off Testing Misses These Bugs
Toggling airplane mode tests two states: fully connected and fully disconnected. Real-world flaky connections spend most of their time in a third state that's neither: requests that start but never complete, DNS lookups that intermittently time out, TCP connections that establish but then stall mid-transfer. A sync implementation that handles clean connect/disconnect transitions gracefully can still have serious bugs in how it handles a request that's genuinely stuck in limbo rather than cleanly failed.
Reproduce It With a Network Conditioning Tool, Not a Real Bad Connection
Trying to reproduce a flaky-connection bug by physically finding bad wifi is slow and inconsistent. A network conditioning tool that lets you script specific packet loss percentages, added latency, and connection drops on demand turns an unpredictable field bug into something you can trigger reliably in a test environment. Both Chrome DevTools' network throttling and platform-specific tools like Apple's Network Link Conditioner support this kind of scripted degradation, and it's worth setting up a repeatable test scenario rather than relying on trial and error against real hardware.
Check What Happens to In-Flight Requests on Timeout
The specific bug pattern worth checking first: what does your sync code do with a request that started, is technically still "in flight" from the client's perspective, but has exceeded a reasonable timeout without a definitive success or failure response? If the client-side state doesn't clean this up correctly, either retrying while the original may still complete server-side, or marking data as synced when it wasn't, that ambiguous state is very often where flaky-connection bugs live.
Idempotency Keys Turn an Ambiguous Retry Into a Safe One
If a request might have succeeded server-side even though the client never received confirmation, retrying it blindly risks double-processing. An idempotency key, a unique identifier sent with the original request and any retries of it, lets the server recognize a retry of a request it already processed and return the original result instead of processing it twice. This single pattern eliminates a large share of the data-duplication bugs that show up specifically under flaky-connection conditions, since it's exactly the ambiguous "did this actually succeed" scenario where duplicate processing tends to sneak in. Stripe's API documentation is a widely referenced real-world example of idempotency keys implemented at scale, worth reading even if you're not using Stripe specifically, since the pattern transfers directly.
Log Enough Context to Reconstruct the Timeline After the Fact
Flaky-connection bugs are hard to catch live, so the debugging usually happens after the fact from logs. Logging the request ID, timestamp, connection state, and outcome for every sync attempt, not just failures, gives you enough of a timeline to reconstruct what actually happened when a user reports a sync issue days later. Sparse logging that only captures errors misses the sequence of near-misses and partial successes that usually explain the bug. The OpenTelemetry project has become a reasonable standard for structuring this kind of distributed tracing data if you want request timelines that span both client and server sides of a sync operation.
Test With Realistic Payload Sizes, Not Toy Data
A flaky connection interacts differently with a small request than a large one, since a bigger payload has more opportunity to be interrupted mid-transfer. Testing sync behavior with realistic production-sized payloads, not a trivial test fixture, under degraded network conditions surfaces failure modes that a small test payload sails through cleanly.
Watch for Silent Partial Failures in Batch Syncs
If your sync process batches multiple records into one request, a connection drop mid-transfer can leave the server having processed some records in the batch but not others, depending on how your batch endpoint is implemented. Make sure your client-side state correctly reflects a partial batch failure rather than assuming the whole batch either fully succeeded or fully failed, since that binary assumption is often wrong under exactly the flaky conditions that trigger the bug in the first place.
Getting the underlying sync architecture right from the start, with idempotency, proper timeout handling, and durable local queuing, prevents most of these bugs before they ever reach a user's flaky hotel wifi. There's a fuller walkthrough of that architecture in How to Build Offline-First Data Sync for a Mobile App Without Losing Local Edits.
Reproduce the Exact Timing, Not Just the Packet Loss Percentage
Two connections with identical average packet loss can behave very differently depending on whether the loss is evenly distributed or clustered into bursts. A sync bug that only appears under bursty loss, several consecutive packets dropped in a row rather than scattered individually, won't reproduce reliably if your test tooling only lets you configure an average loss percentage. Look for network conditioning tools that support burst-loss patterns specifically if a bug resists reproduction under simple uniform packet loss settings.
Correlate Client Logs With Server Logs on a Shared Timeline
A sync bug that spans a client request and a server response is much easier to diagnose when both sides' logs can be lined up on the same timeline using a shared request ID or trace ID. Without this correlation, debugging a flaky-connection bug means manually cross-referencing timestamps between two separate log systems, which is slow and error-prone, especially across timezones or when client and server clocks have drifted slightly out of sync with each other.
Check Whether Your HTTP Client Retries Automatically Underneath You
Some HTTP client libraries include their own automatic retry behavior by default, which can interact badly with an application-level retry queue built on top of it, potentially causing duplicate requests without your application code being aware a retry even happened. Auditing your HTTP client's default configuration for hidden retry behavior is worth doing early, since a bug caused by two retry layers operating independently is confusing to trace precisely because neither layer is behaving incorrectly in isolation.
Keep a Shared Log of Past Flaky-Connection Bugs
Once a team has chased down a handful of these bugs, patterns start to repeat: a particular endpoint that's more sensitive to mid-request drops, a particular batch size that reliably triggers a partial-failure edge case. Keeping a shared, searchable log of past flaky-connection bugs and their eventual root causes turns institutional knowledge that would otherwise live only in one engineer's memory into something the whole team can draw on the next time a similarly strange, hard-to-reproduce report comes in.
Write Down the Repro Steps the Moment You Find Them
Once you've finally reproduced a flaky-connection bug reliably through scripted network conditions, write the exact reproduction steps down immediately as an automated test, before moving on to the fix. It's tempting to fix the bug and move on once you understand it, but a bug this hard to reproduce in the first place is exactly the kind that regresses silently later if it isn't locked into a permanent test that runs on every future change to the sync code.
137foundry.com's team builds and debugs exactly this class of sync architecture for mobile and web apps on a regular, ongoing basis, and a permanent regression test born from a hard-won flaky-connection repro is usually worth considerably more long-term than the specific fix it was originally built to verify in the first place.
Top comments (0)