Your defensive guard should be loud or it is just a silent feature toggle
TLDR: A guard that returns early and logs to the console is indistinguishable, from the outside, from a feature that does not work. Ours skipped the analysis on every real file for months and every user who hit it concluded the tool could not parse their document.
The position
console.warn is not user-facing. If the only trace of a skipped operation is a console line, the operation did not degrade gracefully. It failed quietly, which is worse than failing loudly, because nobody can report what they cannot see.
Why the common advice exists
"Do not freeze the browser" is correct. "Do not crash on pathological input" is correct. Bounding expensive synchronous work is basic hygiene, and every canvas, parser and layout engine does it.
The reasoning is sound, and the guards get written by people who are thinking carefully about failure. That is exactly why they end up trusted and unexamined.
Why it fails
Every threshold is a guess about the future, and the future arrives.
Ours bailed above 1200 elements, a number chosen when the test corpus was hand-drawn diagrams. Then the tool learned to import PDFs from CAD software. A real engineering drawing is 8000 elements before it has drawn anything interesting, so the guard stopped being an exception path and became the only path.
The threshold being stale is forgivable. Thresholds go stale; that is what they do.
What is not forgivable is that nothing said so. The guard printed a warning to the console and returned. The document loaded, drew correctly, and reported zero components. To a user, a tool that draws your schematic and finds nothing in it is a tool that cannot read schematics.
And the bail marked the display analyzed = true on its way out, so nothing downstream ever asked again. The branch that declined to analyse recorded that analysis was done.
The measurement that makes it worse
We eventually ran the pipeline it was protecting, on the file it refused: 380 milliseconds for all four phases on 7848 elements.
The guard was trading the product's core feature against a third of a second. Nobody decided that. It is what the number happened to mean by the time the inputs caught up with it, and no one had measured because there was no symptom pointing at it.
The better rule
Every bail is a user-visible state. If code declines to do something the user asked for, the user finds out. A toast, an empty-state line, a badge on the panel that would have been populated. It costs three lines.
Say what happened, what it was measured against, and what is missing:
Too complex to analyse: 24,310 elements (limit 20,000). Drawing loaded, but wires and components were not detected.
A user who sees that files a useful bug report. A user who sees an empty panel files "it does not work", or more often files nothing.
Every threshold carries its measurement. 1200 had no comment. It should have said what was timed, on what input, on what date. Then the next reader can see that the assumption is fifteen times out of date instead of assuming someone knew.
Do not record success on the bail path. If you did not do the work, do not set the flag that says you did.
What you give up
Noise. Some users will see a message about a limit they do not care about. That is the cost, and it is small: the message only appears in the case you were already handling silently.
The appearance of robustness. Your tool will now visibly decline things it used to quietly not do. Support volume may go up. That is not a regression, it is the discovery of a bug you already shipped.
When silence is right
When the degraded result is genuinely equivalent for the user's purpose: a cache miss, a skipped prefetch, a dropped animation frame. Nothing they asked for is missing.
The test: if the user could look at the result and reasonably conclude the feature is broken, the guard must speak. Ours could, and for months it did not.