Skill: Threading Model

January 16, 2026 · View on GitHub

Understand which threads Turbo Modules and Fabric use for initialization, method calls, and view updates.

Quick Reference

ActioniOS ThreadAndroid Thread
Module initMainJS (lazy) / Native (eager)
Sync methodJSJS
Async methodNative modulesNative modules
View init/propsMainMain
Yoga layoutJSJS

Key rule: Sync methods block JS thread. Keep under 16ms or make async.

When to Use

  • Building native modules
  • Debugging threading issues
  • Accessing UI from native code
  • Understanding async vs sync method behavior

Available Threads

ThreadName in DebuggerPurpose
Main/UIMain threadUI rendering, UIKit/Android Views
JavaScriptmqt_v_jsJS execution, React
Native Modulesmqt_v_nativeAsync Turbo Module calls
CustomVariousYour background threads

Turbo Modules Threading

Initialization

PlatformThreadNotes
iOSMain threadAssumes UIKit access needed
Android (lazy)JS threadDefault behavior
Android (eager)Native modules threadWhen needsEagerInit = true

iOS: React Native runs init on main thread assuming UIKit access.

Android Eager Loading:

// ReactModuleInfo constructor params:
// canOverrideExistingModule, needsEagerInit, isCxxModule, isTurboModule
ReactModuleInfo(
    AwesomeModule.NAME,
    AwesomeModule.NAME,
    false,
    true,   // needsEagerInit = true → runs on native modules thread
    false,
    true
)

Synchronous Method Calls

Always run on JS thread - blocks until return.

// iOS - runs on JS thread
@objc func multiply(_ a: Double, b: Double) -> NSNumber {
    // This blocks JS for entire duration!
    return a * b as NSNumber
}

Danger: Long sync operations freeze the app:

// BAD: Blocks JS for 20 seconds
@objc func multiply(_ a: Double, b: Double) -> NSNumber {
    Thread.sleep(forTimeInterval: 20)  // App frozen!
    return a * b as NSNumber
}

Asynchronous Method Calls

Run on Native Modules thread - doesn't block JS.

// iOS - runs on mqt_v_native thread
@objc func asyncOperation(
    _ a: Double,
    resolve: @escaping RCTPromiseResolveBlock,
    reject: RCTPromiseRejectBlock
) {
    // Already on background thread
    resolve(a * 2)
}
// Android - runs on native modules thread
override fun asyncOperation(a: Double, promise: Promise?) {
    // Already on background thread
    promise?.resolve(a * 2)
}

Module Invalidation

Called when React Native instance is torn down (e.g., Metro reload):

PlatformThread
iOSNative modules thread
AndroidReactHost thread pool

iOS: Implement RCTInvalidating protocol.

Fabric (Native Views) Threading

View Lifecycle

OperationThread
View initMain thread
Prop updatesMain thread
Layout (Yoga)JS thread

Views always manipulate UI on main thread (UIKit/Android requirement).

Yoga Layout

Layout calculations happen on JS thread:

JS Thread: Calculate Yoga tree → Shadow tree
Main Thread: Apply layout to native views

Moving Work to Background

iOS: DispatchQueue

@objc func heavyWork(
    resolve: @escaping RCTPromiseResolveBlock,
    reject: RCTPromiseRejectBlock
) {
    DispatchQueue.global().async {
        // Heavy computation here
        let result = self.compute()
        resolve(result)
    }
}

Android: Coroutines

class MyModule(reactContext: ReactApplicationContext) :
    NativeMyModuleSpec(reactContext) {
    
    private val moduleScope = CoroutineScope(Dispatchers.Default + SupervisorJob())
    
    override fun heavyWork(promise: Promise?) {
        moduleScope.launch {
            // Heavy computation here
            val result = compute()
            promise?.resolve(result)
        }
    }
    
    override fun invalidate() {
        super.invalidate()
        moduleScope.cancel()  // Important: cancel to prevent leaks
    }
}

Thread Safety Checklist

ScenarioSafe?Solution
Sync method accessing shared state⚠️Use locks/synchronized
Async method accessing UIDispatch to main thread
Multiple async calls to same resource⚠️Queue or mutex
Accessing JS from backgroundUse CallInvoker

Accessing UI from Background (iOS)

DispatchQueue.global().async {
    let result = self.heavyComputation()
    
    DispatchQueue.main.async {
        // Safe to update UI here
        self.updateUI(with: result)
    }
}

Accessing UI from Background (Android)

moduleScope.launch(Dispatchers.Default) {
    val result = heavyComputation()
    
    withContext(Dispatchers.Main) {
        // Safe to update UI here
        updateUI(result)
    }
}

Summary Table

ActioniOS ThreadAndroid Thread
Module initMainJS (lazy) / Native (eager)
Sync methodJSJS
Async methodNative modulesNative modules
View initMainMain
Prop updateMainMain
Yoga layoutJSJS
InvalidateNative modulesReactHost pool