Quick answer: A crash terminates the process and usually leaves a stack trace. A freeze leaves the process alive but unresponsive, so there is no trace unless you capture one. Tell them apart by whether the app exits or just stops responding, and instrument differently: crash handlers for the former, watchdog timers and main-thread sampling for the latter. Players report both as the game broke, so the distinction is yours to make.

To a player, a crash and a freeze feel the same: the game stopped working. To an engineer they are nearly opposite problems. A crash is the process dying, often with an exception and a stack trace you can read. A freeze is the process staying alive while doing nothing useful, which means there is no exception, no trace, and no obvious place to look. Mistaking one for the other sends you chasing the wrong evidence. This post covers how to tell them apart from the symptoms, and how to capture the data that makes each kind fixable rather than mysterious.

What actually happens in each case

A crash is an abnormal termination. Something threw an unhandled exception, dereferenced a null, ran out of memory, or hit a fatal assertion, and the operating system tore the process down. Because the runtime usually knows where it died, you typically get a stack trace pointing at the offending line. The defining feature is that the app is gone afterward: it closed, returned to the home screen, or showed an OS-level crash dialog. That sudden disappearance is your strongest clue that you are dealing with a true crash.

A freeze, also called a hang or on mobile an ANR for application not responding, is different. The process is still running, still holding memory, often still drawing the last frame, but it has stopped advancing. Usually the main thread is stuck: an infinite loop, a deadlock between two locks, or a blocking call that never returns, like waiting on a network response with no timeout. Nothing crashed, so there is no trace by default. The game simply ignores input, and after some seconds the OS may offer to kill it for the player.

Reading the symptoms

Start with the simplest question: did the app close or just stop responding. If the player was kicked to the home screen or saw a crash dialog, it crashed. If they sat staring at a static frame, tapping uselessly, until they force quit, it froze. On mobile, a system dialog saying the app is not responding is the textbook freeze signature. The audio behavior is another tell: a crash usually cuts sound instantly, while a freeze often loops the last buffer or holds a sustained tone because the audio thread kept running.

Look at the frame state too. A crash leaves no final frame because the window is gone. A freeze leaves the last rendered frame on screen, sometimes mid-animation, which is why players describe it as the game locking up. Battery and heat can hint at a busy freeze: a spinning infinite loop pegs a CPU core and the device gets warm, whereas a deadlock leaves everything idle and cool. None of these alone is proof, but together they usually point clearly toward one category or the other before you ever open a debugger.

Capturing a crash

Crashes are the easier of the two to capture because the runtime is already producing an error. Install a crash handler or unhandled-exception hook so that when the process dies, you record the stack trace, the exception type and message, and the device and build context before the OS finishes tearing things down. The trace usually points within a few frames of the real cause, and even when the top of the stack is in engine code, the frames beneath it lead back to your call site. Capturing the build version is essential for matching crashes to releases.

Group crashes by signature rather than treating each as unique. The same null dereference can fire thousands of times across players, but it is one bug. Folding identical stack traces together turns an overwhelming stream into a ranked list of distinct problems. That ranking is what tells you which crash to fix first: the one hitting the most players, not the one that happened to be reported most loudly. Without grouping, a single common crash can drown out a dozen rarer but equally real ones in your inbox.

Capturing a freeze

Freezes need active instrumentation because nothing throws. The standard technique is a watchdog timer: a background thread periodically checks whether the main thread has updated a heartbeat counter recently. If the heartbeat stops advancing for a few seconds, the watchdog assumes a hang and captures a snapshot of every thread's current call stack. That main-thread stack is your equivalent of a crash trace, showing exactly where execution is stuck, whether in your loop, in a lock, or in a blocking system call that forgot to time out.

On mobile, the OS ANR mechanism does some of this for you, but its reports are often shallow and arrive late, so a built-in watchdog gives you better detail and earlier warning. Capture all thread stacks, not just the main one, because deadlocks only make sense when you can see which thread holds which lock. Add timeouts to every blocking operation as a matter of course, since an unbounded network or file call is the most common freeze in shipped games. A freeze you can snapshot becomes as fixable as a crash.

Setting it up with Bugnet

Bugnet handles the crash side directly: its crash reporting captures the stack trace, exception details, and full device and platform context the moment the process dies, then groups identical signatures so you see distinct problems ranked by how many players each one hits. That occurrence count is exactly what you need to decide whether a crash is a launch blocker or a rare edge case. Everything lands in one dashboard alongside your bug reports, so crashes are not a separate tool you forget to check.

Freezes are trickier because they do not self-report, but the in-game report button covers the gap. A player who hits a hang can force a report, which captures the current game state and device context even though nothing crashed, giving you a concrete starting point for your watchdog investigation. Pair that with custom fields recording your heartbeat status or last-known main-thread activity, and you can filter the dashboard to separate true crashes from suspected hangs, instead of guessing from the player's it broke description alone.

Build the distinction into your process

Make the crash-or-freeze question the first thing your triage asks about any the game broke report. Add a field to your tracker that forces the answer, even if it is only suspected, because the two categories route to different owners and different fixes. A crash usually means a logic or memory bug in a specific code path. A freeze usually means a concurrency or blocking issue in your threading or IO. Sorting them at intake stops engineers from applying crash-debugging instincts to a deadlock, which wastes everyone's time.

Over time, track how many of each you ship. A rising freeze count often signals creeping complexity in your async code or a new dependency with hidden blocking calls. A rising crash count after a release points at a regression in a recent change. Both deserve attention, but they tell different stories about your codebase's health. Teaching the whole team to name the difference, and instrumenting for both rather than only the loud crashes, is what keeps the quiet freezes from becoming the bug players quit over silently.

A crash exits, a freeze hangs. Capture stack traces for one and watchdog snapshots for the other, and never debug them with the same instincts.