Ask HN: How do you catch silent logic bugs that don't crash?

1 pointsposted 14 hours ago
by vortexshadow

Item id: 46663817

8 Comments

vortexshadow

12 hours ago

To add some context: I’m specifically thinking about cases where each individual state update looks valid on its own, but the combination over time breaks a business rule.

For example, async flows where status updates and data creation are handled in different places.

Curious how teams make these kinds of assumptions explicit in practice.

vortexshadow

12 hours ago

One nuance I keep seeing is that each individual update is valid, but the invariant only breaks after a specific sequence of events.

That’s where things get tricky to reason about upfront.

OsrsNeedsf2P

13 hours ago

Fail as early as possible. In this specific case, invariants help.

vortexshadow

12 hours ago

Exactly.

Failing early is the key part — especially before invalid state has a chance to propagate through the system.

What I’ve found tricky in React apps is identifying where those invariants should live when the logic spans multiple async updates.

Do you usually enforce them close to state updates, or at higher-level boundaries?

al_borland

12 hours ago

Wouldn’t this be a gap in the tests that needs to be closed?

vortexshadow

12 hours ago

Tests definitely help, and I agree they should cover as much logic as possible.

The gap I’ve seen is that tests usually validate expected scenarios, while some invariants only break after specific sequences of events or over time — especially with async flows and state that evolves across renders.

In practice, we found tests and runtime checks to be complementary: tests verify intent, invariants catch unexpected drift when reality doesn’t match assumptions.

rekabis

8 hours ago

Re-framing the logic of the app can also help.

For example, in your example is there any situation where there is an invoice that has not been paid?

In the vast majority of shopping cart systems, that would be a hard NO. There would be an Order ID, for carts that have not yet been paid for, sure, but an Invoice ID only makes sense if the order has been fully paid for, and not at any stage previous to that.

As such, why have an Order Status at all? Have the mere presence of the Invoice ID be the flag that states the order has been paid for. As in: null = not paid, not null = paid.

vortexshadow

a few seconds ago

For what it’s worth, this discussion is exactly what pushed me to experiment with a small runtime invariant helper for React/JS.

The idea wasn’t to replace good modeling or tests, but to fail early when real-world state temporarily drifts away from its intended shape (especially in async or legacy flows).

I put a small prototype here if anyone’s curious: https://github.com/USERNAME/invariant-guard