Runtime performance

Token cost measures what it costs to build; this page measures what you get. Every implementation was rebuilt in Release configuration from its preserved source tree and profiled on one fixed simulator (iPhone 17 Pro) — no new agent trials were needed, because performance belongs to the artifact, not the agent’s path to it. Raw per-app JSON is in the repository ↗.

CorrectionAugust 2, 2026

App size was measured on simulator builds, which are not app sizes

As first published, size came from the Release-iphonesimulator .app directory. That bundle carries an x86_64 slice that never ships and full symbol tables: NativeScript’s 93.3 MB was 67.2 MB of NativeScript.framework, whose binary was a 59 MB fat Mach-O (30 MB x86_64 + 29 MB arm64) holding 49,215 symbols. Both arms were inflated, but not by the same proportion — NativeScript shed 45% on re-measurement and Expo 50% — so the published ratio was wrong as well as the absolute figures. Every app was re-measured as an unsigned arm64 device archive, which runs the strip and postprocessing an App Store build gets. The direction of the finding is unchanged and the gap is slightly wider. Memory, launch, and CPU were unaffected: they were always simulator measurements and remain so.

FigureAs publishedCorrected
NativeScript bundle93.3 MB51.1 MB
Expo bundle57.1 MB28.7 MB
NativeScript executable23.4 MB11.8 MB
Expo executable5.3 MB1.3 MB
Headline gap−39%−44%
Bundle on disk
−44%

28.7 vs 51.1 MB; main executable 1.3 vs 11.8 MB.

Cold launch
tie

301 vs 302 ms to foreground-active.

Idle memory
mixed

RSS 228 vs 216 MB; footprint 34 vs 40 MB.

Release-buildable as-written
16/16

Every implementation rebuilt in Release untouched, both arms.

01Why Release builds, and which apps

Debug builds carry development-only weight — unoptimized code and development-only attach paths — so they are never measured. Each trial’s final source tree (preserved as a git branch) was rebuilt with -configuration Release and archived. Measuring all implementations rather than one per side turns implementation variance into data: across 8 independently agent-built apps per arm, every nativescript archive came out at exactly 51.1 mb and expo spanned only 27.4–28.7 mb, while idle rss varied 214–216 mb. size and runtime characteristics are properties of the framework, not of how the agent wrote the app — which is why one app per side would have been enough here, and why the spread that does exist is worth showing.

Every app, no exclusions: All 16 free-choice implementations rebuilt in Release without modification — no build failures on either arm. React Native Debug builds do not embed the JS bundle (they fetch it from Metro at runtime), so every measurement and functional check here runs on Release builds, which embed on both arms.

02What ships — bundle composition

Almost none of either bundle is the app the agent wrote. NativeScript ships an 11.8 MB executable with the V8-based runtime metadata compiled in, plus a 37.1 MB NativeScript.framework — 96% of its total. Expo ships a 1.3 MB executable over React (11.5 MB), Hermes (4.8 MB) and the Expo module frameworks (7.6 MB). Both are dominated by their runtime, and Expo’s is the smaller one.

Size is an unsigned arm64 device archive per app (xcodebuild archive, generic/platform=iOS), which runs the same strip and postprocessing an App Store build gets. Uncompressed and before App Store thinning, so these are upper bounds on download size. Memory, launch, and CPU below come from simulator builds instead, and are not comparable to these figures.

NativeScript51.1 MB
executable 11.8 MBframeworks 39.3 MB
Expo28.7 MB
executable 1.3 MBframeworks 27.4 MB
03Memory, launch, idle CPU
Release, simulator (median)NativeScript (n=8)Expo (n=8)Δ
Idle RSS216 MB228 MB+6%
Idle physical footprint40 MB34 MB−15%
Cold launch → foreground-active302 ms301 ms~equal
Launch-burst settle (CPU quiesce)1.45 s1.55 s+7%
Idle CPU0.0%0.0%equal

5 cold launches per app, terminated between runs; launch timing from just before invocation to the OS foreground-active marker in the system log, CPU-quiesce fallback recorded alongside. Idle sampled 10×1 s after a 12 s settle. Both frameworks idle clean at 0.0% CPU on a static screen.

04Using the platform APIs — tap to result
Feature path (median ms, range)NativeScriptExpoVerdict
Nav → Health screen rendered2,220 (1,7992,268)1,998 (1,3592,254)Expo ~10% faster
7-day HealthKit query → rows rendered1,069 (1,0441,097)1,075 (1,0291,097)tie — the OS API dominates
Log 500 steps → list refreshed513 (369643)386 (355403)Expo ~25% faster
Nav → Transcribe screen rendered2,168 (1,7362,192)2,205 (1,7472,219)tie

Measured by a generic external UI-test driver tapping every app through its real flows by the spec-pinned labels, 3 iterations per app, fresh launch per iteration, permission sheets auto-granted. The heavy read path is a dead tie — 1,069 ms against 1,075 ms, a 0.6% gap — confirming that the OS API, not the framework, dominates it. Expo takes the write→refresh round trip, though NativeScript’s wide range (369–643 ms) traces to differing agent refresh strategies, so that is partly implementation choice.

Transcription is simulator-blocked identically on both frameworks: SFSpeechRecognizer’s server path rides Siri infrastructure the Simulator lacks. All 26 apps surfaced the error in their status line per spec rather than crashing. It is a physical-device metric.

05JS↔native interop microbenchmarks — the dial-to-native test

Not measured for this pairing. The suite requires a hand-written bench app plus an equivalent native fixture per framework; NativeScript has one, Expo does not. Building it would test whether Expo’s synchronous JSI modules land near NativeScript’s ~1 µs direct binding or an order of magnitude above it. Worth doing; not done here, and not claimed.

06Scope limits, stated plainly
  • iOS Simulator on one Mac: absolute numbers are not device numbers — only the comparative readings transfer, and they share host, runtime, and interleaved scheduling.
  • No scroll/FPS metric: the spec’d app has no scroll-stressing surface, so neither framework’s rendering under list or animation load is tested here.
  • JS↔native bridge micro-latency is not separately instrumented (that would require modifying the agent-built apps); it shows up indirectly in the tap-to-result timings, where it proved immaterial.
← Back to resultsMethodology →