Skip to content
tezvyn:
Menu

Native Module Threading Model

MediumHow cards are made

Native modules run on their own threads, not the JS thread or main UI thread. Heavy work stays off the JS event loop, but UI mutations must be manually dispatched to the main thread. Assuming they are main-thread safe causes crashes and ANRs.

Why it exists

JavaScript in React Native runs on a single dedicated thread that must never block. If native code executed synchronously on that same thread, every database query, camera capture, or Bluetooth scan would freeze the UI and break the 60 FPS contract. The threading model exists to keep the JS event loop unblocked while letting platform code do heavy lifting on its own schedulers. It also exists to respect platform rules that require UI mutations to happen only on the main thread, preventing corruption in the view hierarchy.

The mental model

Picture two translators in separate soundproof booths. The JS booth sends a request through a mail slot; the native booth picks it up later, does the work, and slides the answer back. They never share the same desk. This means you cannot touch UI state from the native booth unless you explicitly walk over to the main booth, because on both iOS and Android the UI lives in a specific room with a locked door that only opens for the main thread.

How it works

On iOS, the bridge creates a dedicated serial GCD queue for each native module unless you override methodQueue to return a custom one. Your Objective-C or Swift methods run on that queue, not the main thread. On Android, the ReactInstanceManager invokes ReactMethod-annotated functions on a background native module thread by default. To update a View or call a UI-only API, you must dispatch to the main thread using runOnUiThread on Android or dispatch_async to the main queue on iOS. Callbacks and promises always resolve back onto the JS thread automatically, but the native execution begins on a background thread and stays there unless you move it.

When to use it

Use the default background queue for any I/O, image processing, encryption, or long-running computation. Dispatch to the main thread only when you need to synchronously read layout metrics, trigger an imperative animation, or call a platform API that is main-thread-only such as UIKit or Android View methods. If you have a module that manages a shared hardware resource like a serial Bluetooth peripheral, override the queue to a single serial queue to prevent interleaved commands from different threads.

When not to use it

Do not run all native module logic on the main thread as a default. That causes ANRs on Android and dropped frames on iOS. Do not assume that because JS called your method, you are running on the JS thread; mutable state shared between JS and native is not automatically thread-safe. Do not perform blocking network calls on the main thread dispatch, and do not hold locks across bridge calls because deadlocks can occur when JS waits for native and native waits for JS.

One canonical example

Imagine a native module called CalendarManager with a method fetchEvents that reads the device calendar and a method dismissModal that imperatively closes a React Native modal. The fetchEvents implementation can safely query the calendar database on its default background queue and return a promise. However, dismissModal must call UIViewController dismissal code on iOS or Dialog dismissal on Android from the main thread. If you forget the main-thread dispatch in dismissModal, iOS throws a UIKitThreadAssertionException and Android may deadlock or throw an IllegalStateException. The correct pattern is to keep data work on the background thread and wrap every UI touch in an explicit main-thread dispatch.

Interview question

Which approach correctly handles a native module that reads encrypted files and then updates an Android View?

  • a.Execute both operations on the module's default background thread
  • b.Move the file reading to the JS thread and update the View from the native background thread
  • c.Read files on the default background thread, then dispatch the View update to the main threadCorrect
  • d.Dispatch both operations to the main thread to avoid race conditions
Why?

Native modules execute heavy I/O on their default background queue, but any Android View mutation must be explicitly dispatched to the main thread. Option A is tempting because native methods do run on a background thread by default, yet it incorrectly leaves the UI update there, which causes crashes because UI is main-thread-only.

Just read this? Test yourself on what you have been reading.

Put your scrolling time to good use

Learn one idea, try a quiz and save useful cards for revision. Tezvyn makes it easy to learn and stay current in your tech field, a few minutes at a time.

The iPhone app is on the way

We are building it. Until it lands, nothing here is held back from you: every interview card, your saved cards, streaks and the job board all work in Safari, plus hundreds of free practice quizzes of thirty questions each. Sign in and it all carries over to the app the day it arrives.

Want it as an icon? Tap Share at the bottom of Safari, then Add to Home Screen. It opens full screen and the cards you have read stay available offline.

Get it on Google PlayiPhone app coming soon

We are hiring for this. Open roles that interview on react native — each one lists the topics its interview covers.

See open roles