Freezes in Development — Essence, Causes and Prevention

Author: IT Sectr Published: 2026-07-28 Reading time: 9 min

Freeze (hang) is a state where a mobile application stops responding to any user actions for an extended period. Unlike lags (slowdown) and glitches (incorrect behavior), a freeze completely blocks the UI: touches are not processed, animation stops, the screen “freezes”. The cause is blocking the main thread with a synchronous operation, a deadlock in multithreaded code, or abnormally long garbage collection. According to Apple Main Thread Checker Documentation, more than 40% of iOS crash reports are related to main thread blocking. On Android, a similar situation leads to ANR — the system dialog “App not responding”.

Key Takeaways

  • Freeze — complete UI blocking for an extended period (seconds to tens of seconds), different from lags and glitches
  • Main causes — main thread blocking by I/O, deadlock between threads, infinite loop, and memory leak with long GC
  • Diagnostics include Main Thread Checker on iOS, ANR logs /data/anr/traces.txt on Android, and thread dump analysis
  • Elimination — offloading all potentially long operations to background threads, using Structured Concurrency and avoiding synchronized in the UI thread
  • Prevention — StrictMode, Main Thread Checker in Debug scheme, static analysis for deadlock and periodic tests measuring response time

What Is a Freeze in Mobile Development

Freeze (hang) in a mobile application is a state where the app stops processing input events and updating the interface for several seconds or more. Technically, this means the main thread is blocked and cannot execute the next run loop iteration.

Difference Between a Freeze, a Lag, and ANR

A lag is a delay of up to 500 ms where the user notices slowdown but the application continues to work. Freeze lasts from 1 second to tens of seconds. ANR on Android is a special case of a freeze that lasted more than 5 seconds and was detected by the system. Not every freeze leads to ANR, but every ANR is a system-documented freeze.

Consequences of Freezes

On Android, a freeze lasting more than 5 seconds triggers an ANR dialog offering to close the app. On iOS, the system has a watchdog — if the app does not respond to events for 10–20 seconds, the Watchdog terminates the process with code 0x8badf00d (ate bad food). The user only sees the app suddenly closing to the home screen.

Causes of Freezes on Android and iOS

Any operation that takes longer than 100 ms and is launched on the main thread can potentially cause a freeze. Let’s look at the main sources of blockages.

Synchronous I/O in the UI Thread

Reading a large file, a network request without asynchrony, saving data to SharedPreferences using the synchronous apply method followed by commit — all these operations block the main thread. On Android, synchronously reading a 10 MB file can take 200–500 ms depending on flash memory speed. On iOS, a synchronous URLSession load without a completionHandler blocks the UI for the server response time.

Deadlock in Multithreaded Code

When two threads wait for resources held by each other, a deadlock occurs. In mobile applications, a typical scenario is thread A locks Lock1 and waits for Lock2, while thread B locks Lock2 and waits for Lock1. Both threads freeze forever. If one of them is the main thread, the application freezes completely.

Infinite Loop or Recursion

A logic error — for example, while(true) without an exit condition or recursion without a base case — leads to infinite execution on the main thread. Android detects this via ANR after 5 seconds, iOS — via Stackshot, which captures an infinitely repeating call stack.

  • Android — Cursor without closing, synchronous request via execute() instead of enqueue(), FileInputStream.read() in the UI thread
  • iOS — performSelectorOnMainThread:withObject:waitUntilDone:YES, launching NSURLConnection sendSynchronousRequest, loading an image with dataWithContentsOfURL
  • Cross-platform — Flutter compute without a dedicated isolate, React Native synchronous NativeModule

How to Diagnose Freezes

Diagnosing freezes requires tools capable of capturing the state of all threads at the moment of blockage.

ANR Logs on Android

On each ANR, the Android system saves a file /data/anr/traces.txt containing a stack dump of each application thread. Analyzing this file is the main diagnostic method: find the main thread and see which method it stopped on. If the stack ends with Thread.sleep, InputStream.read, or Lock.lock — the cause is found.

Stackshot on iOS

Xcode can take a Stackshot — a snapshot of all thread stacks — when the application freezes (SIGSTOP signal). Enable “Logging” → “Include Stackshot Logs” in the scheme. On a crash with code 0x8badf00d, extract the crash log from Devices & Simulators and find the com.apple.main-thread with the stuck stack.

Main Thread Checker in Xcode

Main Thread Checker automatically detects UIKit calls from background threads while the app is running. Enable it in the scheme (Diagnostics → Main Thread Checker). Each warning is a potential cause of a freeze, especially if it occurs in a network request completionHandler closure.

Example of detecting blockages via StrictMode on Android:

kotlin
class App : Application() {
    override fun onCreate() {
        super.onCreate()
        StrictMode.setVmPolicy(StrictMode.VmPolicy.Builder()
            .detectLeakedSqlLiteObjects()
            .detectLeakedClosableObjects()
            .penaltyLog()
            .build())
        StrictMode.setThreadPolicy(StrictMode.ThreadPolicy.Builder()
            .detectAll()
            .penaltyDeath()
            .build())
    }
}

Methods for Eliminating UI Blockages

Eliminating freezes starts with moving all potentially long operations to background threads. Let’s look at specific techniques for each platform.

Structured Concurrency with Coroutines

Kotlin Coroutines with viewModelScope.launch(Dispatchers.IO) ensure that network operations or database reads execute on a background thread. Dispatchers.Main is used only for UI updates. Important: all suspend functions must be structured — child coroutines are cancelled when the parent is cancelled, preventing thread leaks.

Asynchronous Queues on iOS

Grand Central Dispatch with DispatchQueue.global(qos: .userInitiated) for background tasks and DispatchQueue.main.async for UI updates is the standard pattern. Avoid sync() on the main queue — this is a guaranteed deadlock. Use async/await (Swift 5.5+) for more readable asynchronous code with automatic return to the main thread via MainActor.

Avoiding synchronized in the UI Thread

synchronized blocks in Kotlin and @synchronized in Swift on the main thread are dangerous: if another thread has already acquired this lock, the main thread will freeze waiting. Use atomic types (AtomicInteger, atomic properties in Swift) or serial queues instead of locks.

Example of asynchronous data loading with coroutines on Android:

kotlin
class DataViewModel : ViewModel() {
    private val _data = MutableStateFlow<List<Item>>(emptyList())
    val data: StateFlow<List<Item>> = _data.asStateFlow()

    fun loadData() {
        viewModelScope.launch(Dispatchers.IO) {
            val result = fetchFromNetwork()
            _data.emit(result)
        }
    }
}

Preventing Freezes at the Development Stage

A combination of tools, architectural principles, and code review processes helps systematically prevent freezes.

StrictMode with penaltyDeath

Configure StrictMode with penaltyDeath for thread policies — this will cause an immediate app crash when a network call or disk I/O is detected on the main thread. The developer cannot ignore the problem. In production builds, use penaltyLog to collect statistics without crashes.

Main Thread Checker in Debug Scheme

On iOS, enable Main Thread Checker in the Debug scheme and configure CI to run tests with this option. If a test contains a UIKit call from a background thread — it should fail. This is the only reliable way to identify the problem before sending to TestFlight.

Peer Review with Multithreading Checks

Add a mandatory item to the code review process: check that any network call, file operation, database access, or heavy computation runs on a background thread. Deadlock can be detected with static analyzers: Infer by Facebook and Thread Safety Checker by Xcode find potential locks before runtime.

  • Android — StrictMode, Infer, Android Lint Multithread, Kotlin Coroutines with viewModelScope
  • iOS — Main Thread Checker, TSAN (Thread Sanitizer), Xcode Analyze, Swift async/await with MainActor
  • Cross-platform — Flutter compute isolate, React Native interaction manager with requestAnimationFrame

Frequently Asked Questions

What is the difference between a freeze and ANR?

ANR (Application Not Responding) is an Android system notification that appears when the main thread freezes for more than 5 seconds. Freeze is a broader concept: any UI blockage of any duration. iOS does not have ANR, but it has a Watchdog with a 10–20 second timeout.

How to read traces.txt on Android?

The file is located at /data/anr/traces.txt. Access requires root access or adb shell: run adb shell cat /data/anr/traces.txt \> traces.txt with root privileges. In the stack, find the “main” thread — the last called method indicates the cause of the blockage.

Why does the app freeze on iOS but not crash?

If the freeze lasts less than 10 seconds, the Watchdog does not trigger, and the app simply “hangs” until the blocking operation completes. The user does not see a crash but experiences frustration. To detect such cases, use MetricKit with custom execution time traces.

How to test an app for freezes?

Use UI tests with a check that the screen opens in < 1 second. Add time measurement between tap and the next screen appearance to CI. On Android, use Espresso with IdlingResource to wait for asynchronous operations. On iOS, use XCTest with XCTWaiter to check loading time.

Can SwiftUI cause freezes?

SwiftUI itself does not cause freezes, but complex computations in the body property do. If body takes 500 ms to compute due to heavy operations, the UI freezes. The solution is to offload computations to Task.detached and update @State asynchronously on the main actor.

Summary

  • Freeze — complete UI blockage for seconds to tens of seconds, caused by main thread blocking, deadlock, or infinite loop
  • Diagnostics — /data/anr/traces.txt on Android, Stackshot and Main Thread Checker on iOS
  • Main causes — synchronous I/O, deadlock between threads, infinite recursion, long GC
  • Elimination — coroutines with correct dispatchers, async/await with MainActor, offloading all I/O operations to background threads
  • Prevention — StrictMode with penaltyDeath, Main Thread Checker, static analysis for deadlock (Infer, TSAN)
  • On Android freeze > 5 s = ANR; on iOS > 10–20 s = Watchdog crash (0x8badf00d)
  • Recommendation: enable Thread Sanitizer in the Debug scheme and configure CI to run tests with TSAN to detect data races and deadlocks

We will develop a mobile application turnkey

IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.

Discuss the project

Read also