Skip to content

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.

  • 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).

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.

Import the toolkitms · lower is better
  1. tkinterTk 9.0.35.3 ms
  2. Dear PyGui2.3.117 ms
  3. Toga0.5.729 ms
  4. Pyzed1.2.035 ms
  5. Flet1.0.440 ms
  6. CustomTkinter6.0.040 ms
  7. PySide6 (Qt)6.12.068 ms
  8. Kivy2.3.199 ms
Launch to first frame: window with 10 rowsms · lower is better
  1. tkinterTk 9.0.396 ms
  2. Pyzed (CPU renderer)1.2.0 · tiny-skia118 ms
  3. Dear PyGui2.3.1133 ms
  4. PySide6 (Qt)6.12.0135 ms
  5. CustomTkinter6.0.0162 ms
  6. Pyzed1.2.0165 ms
  7. Toga0.5.7221 ms
  8. Kivy2.3.1258 ms
  9. Flet1.0.4579 ms
Launch to first frame: window with 1,000 rowsms · lower is better
  1. Dear PyGui2.3.1152 ms
  2. tkinterTk 9.0.3157 ms
  3. Pyzed (CPU renderer)1.2.0 · tiny-skia175 ms
  4. PySide6 (Qt)6.12.0217 ms
  5. Pyzed1.2.0243 ms
  6. Toga0.5.7887 ms
  7. Flet1.0.4994 ms
  8. 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).

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.

Memory: window with 10 rowsMB · lower is better
  1. tkinterTk 9.0.325 MB
  2. CustomTkinter6.0.031 MB
  3. Pyzed (CPU renderer)1.2.0 · tiny-skia48 MB
  4. Toga0.5.758 MB
  5. PySide6 (Qt)6.12.072 MB
  6. Dear PyGui2.3.1123 MB
  7. Kivy2.3.1146 MB
  8. Pyzed1.2.0151 MB
  9. Flet1.0.4268 MB
Memory: window with 1,000 rowsMB · lower is better
  1. tkinterTk 9.0.334 MB
  2. Pyzed (CPU renderer)1.2.0 · tiny-skia57 MB
  3. PySide6 (Qt)6.12.083 MB
  4. Toga0.5.785 MB
  5. Dear PyGui2.3.1125 MB
  6. Pyzed1.2.0161 MB
  7. Kivy2.3.1282 MB
  8. Flet1.0.4395 MB

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.

Update 1,000 labels: state change to screenms · lower is better
  1. Dear PyGui2.3.16.6 ms
  2. tkinterTk 9.0.37.7 ms
  3. Pyzed (CPU renderer)1.2.0 · tiny-skia15 ms
  4. Pyzed1.2.018 ms
  5. PySide6 (Qt)6.12.024 ms
  6. Kivy2.3.159 ms
  7. Flet1.0.4166 ms
  8. 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.

Update 10 labels: state change to screenms · lower is better
  1. tkinterTk 9.0.31.9 ms
  2. Pyzed (CPU renderer)1.2.0 · tiny-skia2.7 ms
  3. CustomTkinter6.0.02.8 ms
  4. PySide6 (Qt)6.12.02.8 ms
  5. Dear PyGui2.3.13.5 ms
  6. Pyzed1.2.03.8 ms
  7. Kivy2.3.14.1 ms
  8. Toga0.5.79.1 ms
  9. Flet1.0.425 ms

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.

Installed size, with dependenciesMB · lower is better
  1. CustomTkinter6.0.01.4 MB
  2. Toga0.5.72.9 MB
  3. tkinterTk 9.0.39.3 MB
  4. Dear PyGui2.3.110.0 MB
  5. Flet1.0.451 MB
  6. NiceGUI3.18.077 MB
  7. Kivy2.3.183 MB
  8. PySide6 (Qt)6.12.0238 MB

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.

Median in bold; below it the minimum–maximum and the number of samples.

LibraryImport (ms)First frame, 10 rows (ms)First frame, 1,000 rows (ms)RSS, 10 rows (MB)RSS, 1,000 rows (MB)
Pyzed 1.2.035
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.35.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.040
40–42 · n=10
162
152–181 · n=7
–31
31–31 · n=7
–
PySide6 (Qt) 6.12.068
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.199
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.117
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.440
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.729
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
LibraryUpdate, 10 rows (ms)Update, 1,000 rows (ms)Install size (MB)Counter (lines)To-do (lines)
Pyzed 1.2.03.8
2.4–21 · n=70
18
12–46 · n=70
–1931
tkinter Tk 9.0.31.9
1.1–5.9 · n=70
7.7
6.6–12 · n=70
9.31539
CustomTkinter 6.0.02.8
1.7–5.4 · n=70
–1.41432
PySide6 (Qt) 6.12.02.8
1.8–9.2 · n=70
24
21–38 · n=70
2382862
Kivy 2.3.14.1
2.9–12 · n=70
59
46–259 · n=70
833181
Dear PyGui 2.3.13.5
2.6–6.3 · n=70
6.6
4.0–9.2 · n=70
10.01929
Flet 1.0.425
14–40 · n=70
166
145–194 · n=70
511722
Toga 0.5.79.1
8.4–14 · n=70
36,619
36,055–37,464 · n=70
2.91935
Pyzed (CPU renderer) 1.2.0 · tiny-skia2.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/.

  • 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
  • Same app everywhere. Each toolkit’s bench.py builds 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: no RecycleView in Kivy, no lazy ListView in Flet, no virtual_list in 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.
macOS and Linux
git clone https://github.com/albakore/pyzed
cd pyzed
uv venv --python 3.13 .venv
source .venv/bin/activate
uv pip install pyzed-ui pyside6 kivy dearpygui "flet[desktop]" toga customtkinter psutil mss numpy
python benchmarks/run.py
Windows (PowerShell)
git clone https://github.com/albakore/pyzed
cd pyzed
uv venv --python 3.13 .venv
.venv\Scripts\Activate.ps1
uv pip install pyzed-ui pyside6 kivy dearpygui "flet[desktop]" toga customtkinter psutil mss numpy
python benchmarks/run.py

Without 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.

  • 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.