Skip to content

Flexily Performance

Flexily and Yoga each win in different scenarios. The right choice depends on your workload.

Performance Profile

Flexily's pure JavaScript architecture creates a distinctive performance profile compared to Yoga's WASM:

ScenarioWinnerMarginWhy
Initial layout (create + layout)Flexily1.5-2.5xJS node creation is cheap; no WASM boundary crossings
No-change re-layoutFlexily5.5xFingerprint cache catches unchanged trees at the root
Incremental re-layout (single dirty leaf)Yoga2.8-3.4xWASM per-node computation is faster
Full re-layout (constraint change on pre-existing tree)Yoga2.7xSame reason — WASM layout computation is faster
Deep nesting (15+ levels)YogaincreasingFlexily's function call overhead compounds at depth

Key insight: Flexily wins at node creation and cache hits. Yoga wins at raw layout computation. The "2x faster" initial-layout advantage comes from JS node creation being ~8x cheaper than WASM node creation, which more than compensates for Yoga's faster layout algorithm.

Why These Trade-offs Exist

JS/WASM Interop: Flexily's Initial Layout Advantage

Yoga's WASM module is fast internally, but every interaction crosses the JS/WASM boundary:

JS                          WASM
│                           │
├─ node.setWidth(100) ──────┼─► Write to linear memory
├─ node.setFlexGrow(1) ─────┼─► Write to linear memory
├─ node.insertChild() ──────┼─► Update pointers in memory
├─ calculateLayout() ───────┼─► Run layout algorithm
├─ node.getComputedWidth() ─┼─► Read from linear memory
│                           │

Each boundary crossing involves type conversion, memory read/write, and function call overhead. For a 100-node layout, that's 400+ crossings. This makes Yoga's node creation ~8x slower than Flexily's pure-JS creation, which dominates initial layout benchmarks.

WASM Computation: Yoga's Re-layout Advantage

When re-laying out a pre-existing tree, node creation is amortized away. The benchmark becomes pure layout computation, where WASM's compiled code outperforms JIT-compiled JavaScript. Flexily's per-node overhead (5-field fingerprint comparison, cache management) is more expensive than Yoga's simpler dirty-flag check.

Fingerprint Cache: Flexily's No-Change Advantage

Flexily stores a 5-field fingerprint per node: (availableWidth, availableHeight, direction, offsetX, offsetY). When calculateLayout() is called with unchanged constraints on a clean tree, Flexily checks the fingerprint at the root and returns immediately — zero tree traversal. Yoga must still walk the tree to verify nothing changed.

This makes Flexily 5.5x faster for the common case of cursor movement, selection changes, and other UI updates that don't affect layout.

Zero-Allocation Design

The layout algorithm eliminates temporary allocations during layout:

  1. FlexInfo structs on nodes — Mutated in place, not reallocated each pass
  2. Pre-allocated typed arrays — For flex-line tracking
  3. Inline iteration — No filter() calls that allocate intermediate arrays

This reduces GC pressure for high-frequency renders.

See zero-allocation.md for implementation details.

Benchmark Results

All benchmarks on Apple M-series, Bun 1.2, macOS (February 2026), with JIT warmup. Times are mean per operation.

Initial Layout (Create + Layout)

The primary benchmark. Includes tree creation + calculateLayout().

Flat Layouts

NodesFlexilyYogaRatio
10074 µs157 µsFlexily 2.1x
500371 µs835 µsFlexily 2.3x
1000767 µs1797 µsFlexily 2.3x
20001497 µs3937 µsFlexily 2.6x
50004929 µs12496 µsFlexily 2.5x

TUI Board (columns × bordered cards with measure functions)

This mirrors real terminal UI structure: columns with headers, bordered card containers, icon + text rows, text nodes with measure functions.

Structure~NodesFlexilyYogaRatio
3×564124 µs191 µsFlexily 1.5x
5×10206367 µs619 µsFlexily 1.7x
5×20406605 µs1234 µsFlexily 2.0x
8×309691479 µs3015 µsFlexily 2.0x

Property-Rich (shrink, align, justify, wrap)

Trees using diverse flex properties (justifyContent, alignItems, alignSelf, flexWrap, flexShrink, margin).

NodesFlexilyYogaRatio
~100228 µs284 µsFlexily 1.2x
~300635 µs861 µsFlexily 1.4x
~6001348 µs1762 µsFlexily 1.3x

Deep Nesting

DepthFlexilyYogaRatio
11.4 µs3.1 µsFlexily 2.2x
57.3 µs11 µsFlexily 1.5x
1019 µs21 µsFlexily 1.1x
1539 µs31 µsYoga 1.3x
2053 µs41 µsYoga 1.3x
50255 µs101 µsYoga 2.5x

Flexily wins at shallow nesting but Yoga overtakes at 15+ levels.

Re-layout (Pre-existing Tree)

For applications that create the tree once and re-layout repeatedly (React UIs, TUI apps).

No-Change Re-layout

When nothing changed — tests the fingerprint cache (Flexily's key innovation).

StructureFlexilyYogaRatio
5×20 TUI (~406 nodes)0.027 µs0.15 µsFlexily 5.5x
8×30 TUI (~969 nodes)0.026 µs0.14 µsFlexily 5.5x

Flexily returns in 27 nanoseconds regardless of tree size — fingerprint check at root, zero traversal.

Single Leaf Dirty (Incremental)

One text node marked dirty, full tree re-laid out. The most common update pattern.

StructureFlexilyYogaRatio
5×20 TUI (~406 nodes)123 µs37 µsYoga 3.4x
8×30 TUI (~969 nodes)244 µs87 µsYoga 2.8x

Width Change Cycle (120→80→120)

Full constraint change requiring complete re-layout.

StructureFlexilyYogaRatio
5×20 TUI (~406 nodes)955 µs (2 layouts)353 µs (2 layouts)Yoga 2.7x

Feature-Specific Benchmarks

FeatureWinnerMargin
AbsolutePositioningFlexily3.5x
FlexShrinkFlexily2.7x
AlignContentFlexily2.3x
FlexGrowFlexily1.9x
GapFlexily1.5x
MeasureFuncFlexily1.4x
FlexWrapFlexily1.2x
PercentValues~Equal-

These are initial layout benchmarks (create + layout), where Flexily's node creation advantage dominates.

Real-World Performance Mix

For a terminal UI app (our primary target), the operation mix is roughly:

OperationFrequencyWinner
Initial renderOnceFlexily 1.5-2x
Cursor movement (no layout change)Very frequentFlexily 5.5x
Content edit (single node dirty)FrequentYoga 3x
Window resizeOccasionalYoga 2.7x

The no-change case dominates in interactive TUIs (most keystrokes don't change layout). Flexily's fingerprint cache makes this essentially free.

Benchmark Variance

EngineCold RMEWarm RME
Flexily±5-12%±1-3%
Yoga±0.3-1%±0.3-1%

WASM is AOT-compiled with manual memory management, so it's consistent from the first run. JavaScript needs JIT warmup. For sustained rendering, both stabilize.

Benchmark Methodology

How Benchmarks Were Run

All benchmarks were run on Apple M-series (ARM64), macOS, Bun 1.2, February-March 2026. Each benchmark uses Vitest's bench() with JIT warmup iterations to ensure stable numbers. Times reported are mean per operation after warmup.

Before each run, system load was verified with top -- no CPU-heavy processes (builds, browsers, video encoding) running concurrently. Benchmarks were run multiple times and results checked for consistency within the RME (relative margin of error) ranges listed above.

Caveats About the Yoga Comparison

These benchmarks compare fundamentally different execution models (pure JS vs WASM), which means apples-to-apples comparison is inherently limited:

  1. Initial layout benchmarks include node creation. Flexily's advantage (1.5-2.5x) is dominated by JS object creation being ~8x cheaper than WASM node creation. If you create the tree once and re-layout many times, this advantage is amortized away.

  2. WASM per-node computation is genuinely faster. For incremental re-layout of pre-existing trees, Yoga wins (2.8-3.4x) because compiled C++ outperforms JIT-compiled JavaScript at the per-node level. This is a fundamental characteristic, not an implementation gap.

  3. Fingerprint cache advantage is workload-dependent. Flexily's 5.5x no-change advantage is dramatic but only applies when the layout inputs are identical between frames. If every frame changes something, this cache doesn't help.

  4. Deep nesting compounds JS overhead. Flexily's recursive JS function calls get increasingly expensive at 15+ levels of nesting. Most TUI apps have 3-5 levels of nesting, well below this crossover point, but document renderers or deeply nested component trees may hit it.

  5. Bun's JIT differs from V8/Node. Benchmark numbers may differ on Node.js (V8) vs Bun (JavaScriptCore). WASM performance is more consistent across runtimes.

Source Code

All benchmark source code is in the bench/ directory:

FileWhat it measures
bench/yoga-compare-warmup.bench.tsFlat + deep initial layout with JIT warmup
bench/yoga-compare-rich.bench.tsTUI boards, measure functions, property diversity
bench/incremental.bench.tsNo-change, dirty leaf, resize re-layout
bench/features.bench.tsPer-feature comparison (grow, shrink, wrap, etc.)

Running Benchmarks

bash
# Quick comparison (flat + deep, with warmup)
bun bench bench/yoga-compare-warmup.bench.ts

# Real-world scenarios (TUI boards, measure functions, property diversity)
bun bench bench/yoga-compare-rich.bench.ts

# Incremental re-layout (no-change, dirty leaf, resize)
bun bench bench/incremental.bench.ts

# Feature-specific
bun bench bench/features.bench.ts

When to Use Yoga Instead

  • Frequent incremental re-layout — If your primary workload is single-node updates on large pre-existing trees, Yoga's WASM layout computation is 2-3x faster
  • Deep nesting (15+ levels) — Yoga's per-node overhead is lower
  • React Native ecosystem — Yoga is the native choice
  • Cold single-shot layouts — No warmup benefit for Flexily