Python GUI benchmarks — Pyzed vs tkinter, Qt, Kivy, Flet…
These numbers were measured, not estimated: every chart on this page is drawn from the JSON file the benchmark script wrote, and you can run the same script on your machine. They come from one machine without a GPU, so read them as a comparison between toolkits under the same conditions, not as absolute numbers for your computer.
Key findings
Section titled “Key findings”- Startup is fast everywhere except Flet and, with many widgets, Kivy and Toga. Pyzed shows a 10-row window in about 0.16 s from launch and a 1,000-row window in about 0.24 s: building and laying out 2,000 more widgets costs it some 80 ms. tkinter, Dear PyGui and Qt start sooner; Kivy, Toga and Flet need close to a second for 1,000 rows.
- Updating 1,000 rows takes Pyzed about 18 ms from state change to screen, even though it rebuilds the whole view from Python each time. That is a little faster than Qt Widgets, about 3× faster than Kivy and 9× faster than Flet; tkinter and Dear PyGui are faster still. Toga (GTK) needed about 37 s per update in this test, because every label change triggers a new layout.
- Memory depends on the renderer. Through the software Vulkan driver used here, Pyzed’s GPU renderer
takes about 151 MB; the same app on Pyzed’s CPU renderer (tiny-skia, set with
ICED_BACKEND=tiny-skia) takes about 48 MB, between tkinter (25 MB) and Qt (72 MB). The other GPU toolkits pay a similar driver cost here (Dear PyGui 123 MB, Kivy 146 MB, Flet 268 MB). - Install size: about 30 MB for Pyzed, against 238 MB for Qt’s essentials, 83 MB for Kivy and 51 MB for Flet with its client. tkinter, Dear PyGui, Toga (which uses the system’s GTK) and CustomTkinter (which uses tkinter) are smaller.
- Code: the to-do app takes 31 lines in Pyzed, half of PySide6 (62) and well under Kivy (81). Flet’s is the shortest (22).
Startup
Section titled “Startup”Import is the time to import the toolkit in a fresh Python process. First frame is the time from
launching the app’s process until its window is on the screen with its content: the script watches the
screen for a colored bar at the bottom of the window.
- tkinterTk 9.0.35.3 ms
- Dear PyGui2.3.117 ms
- Toga0.5.729 ms
- Pyzed1.2.035 ms
- Flet1.0.440 ms
- CustomTkinter6.0.040 ms
- PySide6 (Qt)6.12.068 ms
- Kivy2.3.199 ms
- tkinterTk 9.0.396 ms
- Pyzed (CPU renderer)1.2.0 · tiny-skia118 ms
- Dear PyGui2.3.1133 ms
- PySide6 (Qt)6.12.0135 ms
- CustomTkinter6.0.0162 ms
- Pyzed1.2.0165 ms
- Toga0.5.7221 ms
- Kivy2.3.1258 ms
- Flet1.0.4579 ms
- Dear PyGui2.3.1152 ms
- tkinterTk 9.0.3157 ms
- Pyzed (CPU renderer)1.2.0 · tiny-skia175 ms
- PySide6 (Qt)6.12.0217 ms
- Pyzed1.2.0243 ms
- Toga0.5.7887 ms
- Flet1.0.4994 ms
- Kivy2.3.11,101 ms
One real widget per row (a label and a button), no virtualized lists. CustomTkinter is missing: its 1,000-row app crashed (see the caveats).
Memory
Section titled “Memory”Resident memory (RSS) of the app’s processes one second after the first frame. For Flet it includes the Flutter client process that draws the window.
- tkinterTk 9.0.325 MB
- CustomTkinter6.0.031 MB
- Pyzed (CPU renderer)1.2.0 · tiny-skia48 MB
- Toga0.5.758 MB
- PySide6 (Qt)6.12.072 MB
- Dear PyGui2.3.1123 MB
- Kivy2.3.1146 MB
- Pyzed1.2.0151 MB
- Flet1.0.4268 MB
- tkinterTk 9.0.334 MB
- Pyzed (CPU renderer)1.2.0 · tiny-skia57 MB
- PySide6 (Qt)6.12.083 MB
- Toga0.5.785 MB
- Dear PyGui2.3.1125 MB
- Pyzed1.2.0161 MB
- Kivy2.3.1282 MB
- Flet1.0.4395 MB
Updating 1,000 rows
Section titled “Updating 1,000 rows”Every 250 ms the app changes the text of all 1,000 labels and the color of the bottom bar in the same update. The latency is the time from the app changing its state to the new color being on the screen; 10 updates per launch, 7 launches.
- Dear PyGui2.3.16.6 ms
- tkinterTk 9.0.37.7 ms
- Pyzed (CPU renderer)1.2.0 · tiny-skia15 ms
- Pyzed1.2.018 ms
- PySide6 (Qt)6.12.024 ms
- Kivy2.3.159 ms
- Flet1.0.4166 ms
- Toga0.5.736,619 ms
The axis is cut at 250 ms: Toga's bar is broken and its real value is shown on the right.
- tkinterTk 9.0.31.9 ms
- Pyzed (CPU renderer)1.2.0 · tiny-skia2.7 ms
- CustomTkinter6.0.02.8 ms
- PySide6 (Qt)6.12.02.8 ms
- Dear PyGui2.3.13.5 ms
- Pyzed1.2.03.8 ms
- Kivy2.3.14.1 ms
- Toga0.5.79.1 ms
- Flet1.0.425 ms
Install size
Section titled “Install size”How much a fresh virtual environment grows when the toolkit is installed with its dependencies. tkinter counts the Tcl/Tk files that come with Python. Flet includes the desktop client it downloads on first run. Toga does not include GTK, which comes from the operating system. NiceGUI is shown for reference only: it draws in a web browser, so it was not part of the window benchmarks.
- CustomTkinter6.0.01.4 MB
- Toga0.5.72.9 MB
- tkinterTk 9.0.39.3 MB
- Dear PyGui2.3.110.0 MB
- Flet1.0.451 MB
- NiceGUI3.18.077 MB
- Kivy2.3.183 MB
- PySide6 (Qt)6.12.0238 MB
Lines of code
Section titled “Lines of code”The same counter and to-do apps written in each toolkit (source), counting lines that are not blank, comments or docstrings. See Developer experience for the charts and what the lines do.
Raw numbers
Section titled “Raw numbers”Median in bold; below it the minimum–maximum and the number of samples.
| Library | Import (ms) | First frame, 10 rows (ms) | First frame, 1,000 rows (ms) | RSS, 10 rows (MB) | RSS, 1,000 rows (MB) |
|---|---|---|---|---|---|
| Pyzed 1.2.0 | 35 34–70 · n=10 | 165 154–691 · n=7 | 243 188–363 · n=7 | 151 150–158 · n=7 | 161 160–161 · n=7 |
| tkinter Tk 9.0.3 | 5.3 5.1–5.7 · n=10 | 96 85–100 · n=7 | 157 152–186 · n=7 | 25 25–25 · n=7 | 34 33–34 · n=7 |
| CustomTkinter 6.0.0 | 40 40–42 · n=10 | 162 152–181 · n=7 | – | 31 31–31 · n=7 | – |
| PySide6 (Qt) 6.12.0 | 68 65–76 · n=10 | 135 127–151 · n=7 | 217 204–247 · n=7 | 72 72–72 · n=7 | 83 83–83 · n=7 |
| Kivy 2.3.1 | 99 96–133 · n=10 | 258 249–276 · n=7 | 1,101 1,022–1,153 · n=7 | 146 146–147 · n=7 | 282 281–282 · n=7 |
| Dear PyGui 2.3.1 | 17 17–19 · n=10 | 133 123–139 · n=7 | 152 145–178 · n=7 | 123 123–123 · n=7 | 125 125–125 · n=7 |
| Flet 1.0.4 | 40 38–42 · n=10 | 579 543–589 · n=7 | 994 923–1,007 · n=7 | 268 265–270 · n=7 | 395 392–403 · n=7 |
| Toga 0.5.7 | 29 28–30 · n=10 | 221 216–259 · n=7 | 887 837–955 · n=7 | 58 58–58 · n=7 | 85 85–85 · n=7 |
| Pyzed (CPU renderer) 1.2.0 · tiny-skia | – | 118 94–163 · n=7 | 175 118–201 · n=7 | 48 47–48 · n=7 | 57 57–58 · n=7 |
| Library | Update, 10 rows (ms) | Update, 1,000 rows (ms) | Install size (MB) | Counter (lines) | To-do (lines) |
|---|---|---|---|---|---|
| Pyzed 1.2.0 | 3.8 2.4–21 · n=70 | 18 12–46 · n=70 | – | 19 | 31 |
| tkinter Tk 9.0.3 | 1.9 1.1–5.9 · n=70 | 7.7 6.6–12 · n=70 | 9.3 | 15 | 39 |
| CustomTkinter 6.0.0 | 2.8 1.7–5.4 · n=70 | – | 1.4 | 14 | 32 |
| PySide6 (Qt) 6.12.0 | 2.8 1.8–9.2 · n=70 | 24 21–38 · n=70 | 238 | 28 | 62 |
| Kivy 2.3.1 | 4.1 2.9–12 · n=70 | 59 46–259 · n=70 | 83 | 31 | 81 |
| Dear PyGui 2.3.1 | 3.5 2.6–6.3 · n=70 | 6.6 4.0–9.2 · n=70 | 10.0 | 19 | 29 |
| Flet 1.0.4 | 25 14–40 · n=70 | 166 145–194 · n=70 | 51 | 17 | 22 |
| Toga 0.5.7 | 9.1 8.4–14 · n=70 | 36,619 36,055–37,464 · n=70 | 2.9 | 19 | 35 |
| Pyzed (CPU renderer) 1.2.0 · tiny-skia | 2.7 1.5–12 · n=70 | 15 13–50 · n=70 | – | – | – |
| NiceGUI 3.18.0 | – | – | 77 | – | – |
The complete results, with every sample, are in
benchmarks/results/.
Machine and versions
Section titled “Machine and versions”- Date: 2026-10-19
- OS: Linux-6.18.44-fc-v139-x86_64-with-glibc2.39
- CPU: Intel(R) Xeon(R) Processor, 4 cores; 15.7 GB of RAM (a cloud container)
- Display: Xvfb (X virtual framebuffer, no GPU); OpenGL/Vulkan through Mesa llvmpipe/lavapipe; Mesa 25.2.8-0ubuntu0.24.04.4
- Python 3.13.14 (python-build-standalone, via uv); Tk 9.0.3
Toolkits:
Pyzed 1.2.0, tkinter Tk 9.0.3, CustomTkinter 6.0.0, PySide6 (Qt) 6.12.0, Kivy 2.3.1, Dear PyGui 2.3.1, Flet 1.0.4, Toga 0.5.7
Method
Section titled “Method”- Same app everywhere. Each toolkit’s
bench.pybuilds a 640×480 window with a scrollable list of N rows (a label and an “Open” button each) and a 40 px bar at the bottom painted in a sentinel color. Lists are built the plain way each toolkit documents, with one widget per row: noRecycleViewin Kivy, no lazyListViewin Flet, novirtual_listin Pyzed. - First frame is measured from outside: the harness starts the process and grabs the screen about every millisecond (with mss) until 8,000 pixels of the sentinel color are visible.
- Memory is the RSS of the process and its children (psutil), one second after the first frame.
- Updates are timed between the app’s own timestamp (
time.monotonic(), printed right before it changes its state) and the moment the harness sees the bar’s new color. - Repetitions: 7 launches per toolkit and size, 10 updates per launch, 10 imports; the charts show the median and a thin line from the minimum to the maximum.
- Pyzed (CPU renderer) is the same Pyzed app started with
ICED_BACKEND=tiny-skia, which makes Iced use its CPU renderer instead of wgpu: what Pyzed falls back to on machines without a working GPU driver. - Install size: a fresh
uv venv,uv pip install --link-mode=copy <package>, size difference.
Reproduce it
Section titled “Reproduce it”git clone https://github.com/albakore/pyzedcd pyzeduv venv --python 3.13 .venvsource .venv/bin/activateuv pip install pyzed-ui pyside6 kivy dearpygui "flet[desktop]" toga customtkinter psutil mss numpypython benchmarks/run.pygit clone https://github.com/albakore/pyzedcd pyzeduv venv --python 3.13 .venv.venv\Scripts\Activate.ps1uv pip install pyzed-ui pyside6 kivy dearpygui "flet[desktop]" toga customtkinter psutil mss numpypython benchmarks/run.pygit clone https://github.com/albakore/pyzedcd pyzedpython3 -m venv .venvsource .venv/bin/activatepip install pyzed-ui pyside6 kivy dearpygui "flet[desktop]" toga customtkinter psutil mss numpypython benchmarks/run.pygit clone https://github.com/albakore/pyzedcd pyzedpy -m venv .venv.venv\Scripts\Activate.ps1pip install pyzed-ui pyside6 kivy dearpygui "flet[desktop]" toga customtkinter psutil mss numpypython benchmarks/run.pyWithout a display it starts Xvfb by itself; on a desktop, keep the benchmark windows uncovered while it
runs. See benchmarks/README.md for the
options and the system packages some toolkits need. Results on other machines are very welcome in an
issue.
Caveats
Section titled “Caveats”- One machine, no GPU (see above). Results on Windows and macOS, and with a real GPU, will differ.
- The harness shares the CPU with the app: grabbing the screen costs about one core while it polls.
- Tk draws widget by widget, so for tkinter the bar may appear a moment before every row has been drawn; the GPU toolkits present whole frames.
- Dear PyGui redraws continuously, so its update latency includes waiting for its next frame; it also uses the CPU while idle, which is not measured here.
- CustomTkinter has no 1,000-row results: its app died under Xvfb with an X error (BadAlloc or BadValue
from
CreatePixmap) once its scrollable frame held more than about 100 rows. - Not measured: wxPython has no Linux wheels on PyPI and building it from source was not practical here; NiceGUI runs in a web browser, so a desktop window timing would measure the browser.
- Lines of code depend on style; the apps follow each toolkit’s documented idioms, and improvements are welcome.