VerifiedMeasured 2026-08-01 · task suite v1.1.0 · claude-sonnet-5

NativeScript vs. Expo

One iOS app with two deep native features — HealthKit and Speech — built from a byte-identical spec by isolated AI agents in NativeScript and in Expo (React Native), under a spec that forbids third-party wrappers for the platform capability under test.

8 trials per framework · 78/78 phases build-green across two studies · every number below is generated from the published raw data — nstudio/token-economics

Framework effect on token cost
none

With verification effort held equal on both arms, NativeScript and Expo are statistically indistinguishable (Mann-Whitney U=13/25, exact p=1.00; the null expectation is 12.5).

What actually predicts cost
84%

Variance in token cost explained by how much the agent verified its own work. Framework choice explains 6%.

Price of one verification step
~1,160

Output tokens per interactive UI check (t=8.04). The most portable number in this study.

Native-language code written
209 vs 0

Lines of Swift per Expo app across two local modules; NativeScript wrote none, reaching the same APIs from TypeScript — at the same token cost.

01Every trial — total output tokens

Each dot is one complete app build (three phased agent sessions, summed). The two arms’ ranges overlap heavily — this pairing is close enough that agent behaviour, not framework choice, drives the spread.

NativeScriptExpo
each dot = one complete app build · output tokens
NativeScriptmedian 84,366
Expomedian 72,138
037.5K75K112.5K150K
02The mechanism — verification effort, not interop architecture

Agents chose how much to drive the simulator, and that choice correlates with total token cost at r = 0.89 — within each arm and pooled. NativeScript’s agents verified in every trial (20–37 UI calls); Expo’s ranged from zero to 37. Controlling for it flips the sign of the framework coefficient and lifts explained variance from 6% to 84%. A companion study pinned verification off on both arms and found no difference at all.

NativeScriptExpo
output tokens · median of 8 trials
UI shellExpo 0.7× NativeScript
NS Expo
13.0K
8.8K
HealthKitExpo 0.7× NativeScript
NS Expo
45.0K
31.3K
SpeechExpo 1.0× NativeScript
NS Expo
27.3K
28.2K
012.5K25.0K37.5K50.0K
03Where the tokens go

The four usage buckets are reported separately: cache reads dominate raw counts in multi-turn agentic sessions, which is why a single collapsed “total tokens” would drown the signal. Output tokens — the agent’s actual work — are the headline metric.

NativeScript21,278,729 all buckets
Input · 0.00%Output · 0.40%Cache read · 98%Cache write · 1%
Expo11,362,374 all buckets
Input · 0.00%Output · 0.63%Cache read · 97%Cache write · 2%
04What you get — runtime performance of the finished apps
App size on disk
−44%

Expo 28.7 MB vs NativeScript 51.1 MB as an unsigned arm64 device archive, with an 89% smaller main executable.

Cold launch
tie

302 vs 301 ms to foreground-active; both idle at 0.0% CPU.

Idle memory
mixed

RSS favours NativeScript (216 vs 228 MB); physical footprint favours Expo (34 vs 40 MB). Neither gap is decisive.

Functional acceptance
8/8 · 8/8

Both arms pass every spec flow when agents could verify freely. With verification forbidden, Expo drops to 4/5 — one app whose HealthKit authorization never resolves at runtime.

Corrected 2026-08-02: App size was measured on simulator builds, which are not app sizes. What changed →

Performance metrics: View full performance data →

05The two interop models
Runtime reflection over native metadata

NativeScript

Direct reflection bridge over the native runtime

NativeScript runs JS/TS directly against the native platform runtimes and exposes native classes to JavaScript through metadata generated at build time from the platform SDKs. Any public iOS API is reachable from TypeScript by referencing its native name — there is no wrapping step and no native-language source to author.

Across both platform features in this study that meant a measured zero lines of native-language code; all the native-side work was Info.plist and entitlement configuration. Whether that translates into a token saving against Expo is the question this comparison asks.

Strengths

  • +No bridge-authoring step for capabilities already in the native SDK — call it directly
  • +Complete, generated TypeScript typings for the native surface
  • +Zero lines of native-language code for both platform features — all native work was configuration

Friction points

  • Verbose, fully-qualified native symbol names inflate output tokens per call site
  • Smaller ecosystem and far less training-data representation than React Native
  • Correctness depends on native marshalling/threading rules the type system does not enforce
NativeScript docs ↗
Authored Expo Modules with a generated scaffold

Expo

Autolinked native modules over JSI

Expo extends into native platform code through the Expo Modules API: `npx create-expo-module --local` scaffolds a module, a Swift `ModuleDefinition` declares what it exposes, and autolinking wires it up with no manual registration. Calls cross over JSI rather than an async bridge.

Native configuration is declarative — entitlements and Info.plist keys live in app.json or a config plugin, because Continuous Native Generation regenerates the iOS project. That layer may well prove cheaper than editing native files directly; the Swift authorship is where the cost should land.

Strengths

  • +Largest training-data footprint of any mobile stack, and the most mature ecosystem
  • +Module scaffolding is generated, and autolinking removes the registration step entirely
  • +Declarative native configuration through app.json and config plugins

Friction points

  • A capability with no module still means authoring Swift, per capability
  • Continuous Native Generation regenerates ios/, so native edits must go through config plugins or be lost
  • The official docs MCP exposes 27 tools including package install and EAS build control — broad surface, and its schemas ride in context every turn
Expo docs ↗
06Held constant across both frameworks
  • Model (claude-sonnet-5) and agent harness (headless Claude Code 2.1.220), pinned for every session on both arms
  • Byte-identical SPEC.md v1.1.0 and audio fixture in both repos, enforced by harness/sync-spec.sh --check, not by convention
  • Structurally identical CLAUDE.md files — same sections, same level of help, no implementation hints
  • One official docs MCP per framework and nothing else, verified per trial by an invocation probe that aborts the trial if the server fails to load
  • Expo’s template-shipped agent onboarding (AGENTS.md, CLAUDE.md, and an enabled official Claude Code plugin) removed at baseline so both arms get the same level of help — recorded verbatim and disclosed as a real advantage held constant
  • Expo MCP local tier (simulator tap/screenshot) left off: no other arm has a visual verification loop
  • Fresh isolated session per phase (throwaway config dir: no memory, no personal settings)
  • Per-phase turn cap (160), build gates run by the harness — never self-reported by the agent
  • Interleaved trial order (NS, Expo, NS, …), strictly sequential, same machine and Xcode 26.5
  • Both arms measured fresh inside a single window — no numbers carried over from any earlier run
  • No hidden retries: every started trial is in the published record
07The task suite
01

Phase 1 — App shell

Home screen plus two placeholder feature screens with working navigation, exact labels pinned by spec. No native calls. Expo writes React where NativeScript writes Vue — the one unavoidable asymmetry, and this phase is the read on what it is worth.

02

Phase 2 — HealthKit (first native feature)

Step-count read/write authorization, logging quantity samples, and a 7-day statistics query rendered as a list. Under v1.1 no third-party HealthKit wrapper may be installed, so Expo must author a local native module.

03

Phase 3 — Speech (second native feature)

Transcribe a bundled audio clip with streaming partial results. The decisive phase: whether a second native capability reuses the first one’s scaffolding (as direct access did) or costs full price again (as bridge authoring did).

04

Acceptance & runtime performance

Automated functional acceptance driving every Release app through the spec flows, then size, memory, launch and tap-to-result latency on one fixed simulator. Both arms pass 8/8 with free-choice verification; with verification forbidden Expo drops to 4/5.

08Raw data — all 24 measured phases

Per trial, per phase — the rows behind every chart above. Full transcripts, diffs, and manifests are in the repository.

TrialPhaseOutputEst. costTurnsWall (s)DocsJS LOCNative LOC
main-ns-1110,674$0.99471870570
242,884$4.27114802815714
312,523$0.783021841002
main-ns-2116,377$1.87632728650
228,063$2.1164375916014
350,321$10.261992,22541032
main-ns-3117,157$1.37533917340
252,071$5.901561,0721216113
342,447$5.5514986821122
main-ns-4116,531$1.43562738390
247,201$6.321371,002323212
346,658$5.711391,01261712
main-ns-5112,531$1.40562347570
257,208$6.951671,097620412
314,627$0.933316821462
main-ns-619,253$0.80401702510
232,565$2.9582555315512
336,521$3.879762351062
main-ns-7113,423$1.44613814430
230,162$1.9062479814612
313,575$0.99321853832
main-ns-8112,134$1.30542166530
252,032$4.89125839714812
318,175$1.12372213872
main-expo-1112,225$1.405660402160
224,442$1.12333290182122
322,641$1.27433070158117
main-expo-215,917$0.471714201570
226,538$1.53563771180154
326,052$1.84524292123122
main-expo-3114,937$0.00162602460
229,729$1.68513762184167
319,172$1.05352301152125
main-expo-419,948$0.00336402490
239,872$3.951147281181138
344,368$3.471007430140158
main-expo-516,610$0.502114901980
235,253$3.341136980175134
330,275$2.05524653119141
main-expo-6110,811$1.125051702300
232,836$1.99555101139147
321,171$1.35404193123124
main-expo-717,621$0.511813002650
227,453$1.79424410157143
352,758$4.581121,0060124146
main-expo-817,398$0.521613702600
242,930$4.471268170156138
361,143$7.481701,2620145126
09Go deeper
Replicate it

Every artifact behind these numbers is published.

Baselines, per-trial diffs and transcripts, build logs, perf JSON, and the harness that produced them. Run it against a different model, a new framework version, or your own framework pair — and submit the results.