Compose SwiftUIv0.4.0
esc
GuideAPIGitHub
Article

Performance

How Compose SwiftUI compares with plain SwiftUI.

Two benchmarks compare Compose SwiftUI with plain SwiftUI (release builds). Full results are in docs/bench/RESULTS.md.

50 screens, each written once in plain SwiftUI and once in Compose SwiftUI, reading the same Kotlin ViewModel (mean of per-screen medians). The SwiftUI version observes the whole screen state through one ObservableObject, the usual way to bridge a StateFlow in a KMP app:

Pixel-identicalMount: SwiftUI → Compose SwiftUIUpdate: SwiftUI → Compose SwiftUI
iOS simulator48 / 5024.1 → 26.1 ms1.92 → 1.48 ms
macOS50 / 5014.4 → 17.1 ms2.11 → 2.07 ms

Scenarios that the 50 screens don't cover, against plain SwiftUI with native @Observable models (one object per row, so a change re-evaluates only the views that read it). iOS simulator, list rows with an icon, two lines of text and an unread dot, all pixel-identical (macOS results are in the full results):

Plain SwiftUICompose SwiftUI, LazyForEachCompose SwiftUI, Kotlin loop
10,000-row List: mount23.5 ms28.2 ms818.8 ms
10,000 rows: one row changes0.16 ms0.41 ms3.24 ms
10,000 rows: every row changes4.0 ms4.5 ms78.5 ms
Scrolling, per page of rows9.7 ms13.8 ms22.9 ms
60 bars, animated change: apply5.1 ms7.5 ms (same frame count, no hitches)—

Updates of a regular screen are as fast as plain SwiftUI or faster, because only the views whose data changed re-render. Mounting costs 8% to 18% more, and a new row costs 40% (iOS) to 70% (macOS) more while scrolling: SwiftUI builds one type-erased view per node because the tree is built at runtime. For long lists, use LazyForEach: with a Kotlin loop, every row is composed and kept up to date even off screen.

iOS simulator: 50 screens

  • Pixel-identical (≤0.01% before and after 30 updates): 48 / 50
  • Mount (median per screen, then mean): SwiftUI 24.08 ms, Compose SwiftUI 26.06 ms (×1.08)
  • Update (state change → laid out): SwiftUI 1.915 ms, Compose SwiftUI 1.476 ms (×0.77)
#Screendiffafter updatesmount SwiftUImount Composeupdate SwiftUIupdate Compose
101 · Profile & settings0.0000%0.0000%25.8726.851.8311.032
202 · Mail inbox0.0000%0.0000%29.5630.391.3831.049
303 · Analytics dashboard0.0000%0.0000%28.3029.632.1851.483
404 · Music player0.0000%0.0000%12.9314.251.0900.717
505 · Weather0.0281%0.1333%16.8119.321.2500.907
606 · Shopping cart0.0000%0.0000%30.2632.142.3361.663
707 · Calendar agenda0.0000%0.0000%18.2521.931.6281.430
808 · Chat conversation0.0000%0.0000%16.0317.211.2440.696
909 · Sign-up form0.0000%0.0000%41.0542.314.6011.484
1010 · Preferences0.0000%0.0000%41.2443.446.2296.710
1111 · Photo grid0.0000%0.0000%18.2021.301.4321.614
1212 · Fitness activity0.0000%0.0000%18.5921.961.7561.564
1313 · Stocks watchlist0.0000%0.0000%24.2327.473.2623.857
1414 · News feed0.0000%0.0000%15.6517.290.9150.989
1515 · File browser0.0000%0.0000%34.1635.761.8470.832
1616 · Kanban board0.0000%0.0000%27.0531.042.4692.359
1717 · Recipe detail0.0000%0.0000%19.5921.201.5581.418
1818 · Banking0.0000%0.0000%26.0227.221.6701.168
1919 · Travel booking0.0000%0.0000%24.1725.851.4921.045
2020 · Contacts0.0000%0.2485%39.3239.452.4671.033
2121 · Notification center0.0000%0.0000%17.4719.531.2440.778
2222 · Map overview0.0000%0.0000%14.0915.001.2991.283
2323 · Bar chart report0.0000%0.0000%16.2917.791.4621.201
2424 · Pricing tiers0.0000%0.0000%18.0519.842.1542.226
2525 · Onboarding0.0000%0.0000%11.6911.502.2551.160
2626 · Leaderboard0.0000%0.0000%30.4731.961.9791.719
2727 · Order tracking0.0000%0.0000%16.6518.712.4641.817
2828 · Smart home0.0000%0.0000%22.2523.761.7311.427
2929 · Podcast episodes0.0000%0.0000%26.6328.132.1911.736
3030 · Pull request review0.0000%0.0000%17.1019.341.5781.791
3131 · Social profile0.0000%0.0000%19.2521.282.5062.519
3232 · Habit tracker0.0000%0.0000%24.3826.441.6870.750
3333 · Expense splitting0.0000%0.0000%17.8520.111.4901.314
3434 · Video library0.0000%0.0000%29.3834.141.1360.832
3535 · Online course0.0000%0.0000%29.0630.891.5190.982
3636 · Hotel detail0.0000%0.0000%20.4121.351.3790.825
3737 · Boarding pass0.0000%0.0000%15.2818.471.4921.263
3838 · Medication schedule0.0000%0.0000%41.9544.672.1141.127
3939 · Analytics table0.0000%0.0000%18.0618.591.3901.146
4040 · Restaurant menu0.0000%0.0000%33.0036.331.4550.515
4141 · Crypto wallet0.0000%0.0000%29.7532.732.1202.009
4242 · Task manager0.0000%0.0000%27.0127.791.4240.968
4343 · Event tickets0.0000%0.0000%30.5435.853.1513.528
4444 · Job listings0.0000%0.0000%24.9926.601.6331.052
4545 · Real-estate listing0.0000%0.0000%17.5918.381.4851.151
4646 · Sports scores0.0000%0.0000%25.5128.801.6240.876
4747 · Book library0.0000%0.0000%19.2822.351.5360.671
4848 · EV dashboard0.0000%0.0000%31.6931.491.8811.390
4949 · Sleep tracker0.0000%0.0000%27.4930.351.8831.125
5050 · System status0.0000%0.0000%23.7324.781.8571.581

macOS: 50 screens

  • Pixel-identical (≤0.01% before and after 30 updates): 50 / 50
  • Mount (median per screen, then mean): SwiftUI 14.44 ms, Compose SwiftUI 17.06 ms (×1.18)
  • Update (state change → laid out): SwiftUI 2.108 ms, Compose SwiftUI 2.071 ms (×0.98)
#Screendiffafter updatesmount SwiftUImount Composeupdate SwiftUIupdate Compose
101 · Profile & settings0.0000%0.0000%14.1215.541.4101.407
202 · Mail inbox0.0000%0.0000%14.5616.301.3691.383
303 · Analytics dashboard0.0000%0.0000%17.3418.772.2042.477
404 · Music player0.0000%0.0000%8.7410.151.1511.093
505 · Weather0.0000%0.0000%9.0112.791.0461.052
606 · Shopping cart0.0000%0.0000%22.2625.001.9381.915
707 · Calendar agenda0.0000%0.0000%11.2615.891.9232.306
808 · Chat conversation0.0000%0.0000%7.959.871.4841.040
909 · Sign-up form0.0000%0.0000%29.6331.204.2354.589
1010 · Preferences0.0000%0.0000%35.1937.368.5669.906
1111 · Photo grid0.0000%0.0000%7.4411.690.8471.127
1212 · Fitness activity0.0000%0.0000%10.7214.201.7652.142
1313 · Stocks watchlist0.0000%0.0000%14.6817.772.9603.595
1414 · News feed0.0000%0.0000%8.8311.100.8771.169
1515 · File browser0.0000%0.0000%13.2815.181.9230.695
1616 · Kanban board0.0000%0.0000%16.7620.171.8512.071
1717 · Recipe detail0.0000%0.0000%12.2014.232.1452.381
1818 · Banking0.0000%0.0000%19.8121.361.7380.751
1919 · Travel booking0.0000%0.0000%14.6316.932.1092.159
2020 · Contacts0.0000%0.0000%26.1330.264.0742.552
2121 · Notification center0.0000%0.0000%8.9811.011.1570.956
2222 · Map overview0.0000%0.0000%7.5210.431.2371.564
2323 · Bar chart report0.0000%0.0000%9.1310.972.0432.196
2424 · Pricing tiers0.0000%0.0000%13.5516.263.5673.709
2525 · Onboarding0.0000%0.0000%5.657.171.7911.686
2626 · Leaderboard0.0000%0.0000%16.0019.601.8741.205
2727 · Order tracking0.0000%0.0000%9.4812.442.3412.717
2828 · Smart home0.0000%0.0000%16.0618.102.0922.082
2929 · Podcast episodes0.0000%0.0000%17.3519.632.6232.011
3030 · Pull request review0.0000%0.0000%10.0813.861.5692.433
3131 · Social profile0.0000%0.0000%14.0816.153.4794.434
3232 · Habit tracker0.0000%0.0000%13.4515.521.5271.083
3333 · Expense splitting0.0000%0.0000%9.0111.231.8892.106
3434 · Video library0.0000%0.0000%14.2219.201.4661.357
3535 · Online course0.0000%0.0000%13.5614.921.7121.100
3636 · Hotel detail0.0000%0.0000%10.7413.021.2691.391
3737 · Boarding pass0.0000%0.0000%9.3813.111.4841.395
3838 · Medication schedule0.0000%0.0000%21.4622.712.4691.505
3939 · Analytics table0.0000%0.0000%10.5612.221.8882.083
4040 · Restaurant menu0.0000%0.0000%16.3618.171.3620.495
4141 · Crypto wallet0.0000%0.0000%18.0519.582.9192.900
4242 · Task manager0.0000%0.0000%20.4523.431.7721.275
4343 · Event tickets0.0000%0.0000%15.4822.304.6906.127
4444 · Job listings0.0000%0.0000%17.6420.071.7231.356
4545 · Real-estate listing0.0000%0.0000%9.9611.921.8161.830
4646 · Sports scores0.0000%0.0000%16.1719.641.2510.917
4747 · Book library0.0000%0.0000%12.5516.260.9620.929
4848 · EV dashboard0.0000%0.0000%21.0322.012.1142.258
4949 · Sleep tracker0.0000%0.0000%17.2820.941.5490.976
5050 · System status0.0000%0.0000%12.0515.172.1661.678

Scenarios: iOS simulator

scripts/run_scenarios.sh -sizes 1000,10000 [-lazy]. Plain SwiftUI uses native @Observable models (one object per row); Compose SwiftUI uses an immutable list in one Kotlin state, as a ViewModel would. Every case is pixel-identical. Mount is the median of three mounts after a warm-up; the other numbers are means over 30 one-row changes, 5 full updates and 60 scrolled pages.

ScenarioPlain SwiftUICompose SwiftUI, LazyForEachCompose SwiftUI, Kotlin loop
1,000 rows: mount23.85 ms27.81 ms55.90 ms
1,000 rows: one row changes0.167 ms0.157 ms0.562 ms
1,000 rows: every row changes1.90 ms1.95 ms9.02 ms
1,000 rows: scrolling, per page9.94 ms13.66 ms20.20 ms
10,000 rows: mount23.48 ms28.24 ms818.83 ms
10,000 rows: one row changes0.160 ms0.407 ms3.240 ms
10,000 rows: every row changes4.00 ms4.52 ms78.49 ms
10,000 rows: scrolling, per page9.72 ms13.77 ms22.89 ms
60 animated bars: apply one change5.12 ms7.54 ms—
60 animated bars: frames / hitches over 10 animations254 / 0254 / 0—

Scenarios: macOS

Same scenarios in a 700×900 window. The frame sampler on macOS is a 120 Hz timer on the main run loop, so its hitch count is only indicative.

ScenarioPlain SwiftUICompose SwiftUI, LazyForEachCompose SwiftUI, Kotlin loop
1,000 rows: mount26.07 ms36.98 ms107.42 ms
1,000 rows: one row changes0.189 ms0.236 ms0.691 ms
1,000 rows: every row changes6.86 ms7.19 ms12.63 ms
1,000 rows: scrolling, per page8.97 ms14.64 ms20.47 ms
10,000 rows: mount26.46 ms37.84 ms1250.92 ms
10,000 rows: one row changes0.137 ms0.468 ms3.249 ms
10,000 rows: every row changes8.40 ms8.95 ms87.75 ms
10,000 rows: scrolling, per page12.39 ms20.95 ms40.89 ms
60 animated bars: apply one change4.40 ms7.09 ms—
60 animated bars: ticks / late ticks over 10 animations506 / 3515 / 8—