Skip to content

tkinter Module Complexity

The tkinter module wraps Tk. Almost every method on a widget, variable, image or font is one call into the Tcl interpreter: Python marshals the arguments, Tk does the work in C, and the result is converted back. A row's bound is therefore the Python marshalling plus what Tk's own data structure costs; drawing is never part of it, because Tk redraws at idle time and charges the visible area, not the call. Only Tk() needs a display: a Tcl() interpreter runs the after timers, variables and traces without one.

Complexity Reference

Size variables: k = options passed in one call, each callable value registering one Tcl command; o = options a widget or item has, so a query with no arguments returns o of them; c = Tcl commands a widget has registered and not deleted - bound handlers, pending after calls, callable options; d = descendants of a widget; p = components in a window path name; n = entries, items or children a widget holds; m = characters, items or matches an operation touches or returns; L = lines in a Text widget; ℓ = characters on the line a Text edit lands on; t = traces on a variable; b = handlers bound to one event sequence; s = selected items; W = widgets in a window tree; D = the layout and drawing Tk has pending, proportional to the widgets relaid and the area redrawn; i = position of an item among its siblings; u = entries on an undo stack; P = pixels in an image; F = bytes in a file read; E = entries in a directory; w = time spent blocked in an event loop; e = events processed; f = the caller's own callback or profile code, whose cost is its own. n is per widget, not per application. A name in a bound that is not listed here, such as pending, tags or themes, counts the things it names.

Widget options reach Tk through one routine that rebuilds the argument tuple once per option, so every row below that takes widget options is O(k²) in Python. Option values are copied into Tcl at their own size, a list-valued one joined into one string first; the rows leave that size out, as they leave out the size of a value read back. k never exceeds o, and o is a few dozen at most, so this is never the term that matters; it is written out because the ttk.Style methods and the ttk item methods such as Treeview.insert() format their options in O(k), and that is the one place the two spellings differ in cost.

Tk and Tcl

Operation Time Space Notes
Tk() O(F + f) O(F) Starts a Tcl interpreter and loads Tk, then runs the profile files .Tk.tcl, .Tk.py, .<basename>.tcl and .<basename>.py from the home directory, F bytes between them, at their own code's cost, unless Python runs with -E. The first Tk() becomes the default root that StringVar(), Button() and the module functions fall back to
Tcl() O(F + f) O(F) The same interpreter without Tk: no display, no widgets, no bind, but after, update(), variables, traces and register() all work; mainloop() returns at once because no Tk window exists
Tk.loadtk() O(1) O(1) Loads Tk into a Tcl() interpreter; a no-op once loaded
Tk.readprofile(baseName, className) O(F + f) O(F) Sources the two .tcl files and execs the two .py files, in a namespace holding every tkinter name
Tk.destroy() O(d + c + n) O(d) Destroys every descendant first, c and n counted over the whole tree and a copy of each children list held on the way down, and forgets the default root if this was it
Tk.report_callback_exception(exc, val, tb) O(depth) O(depth) Prints the traceback to stderr and stores it in the sys.last_* attributes; every exception but SystemExit that a callback raises ends here, not in the caller
Tk.tk / Tk.master / Tk.children O(1) O(1) The interpreter object, None, and the dict of direct children by name; attributes Tk lacks are looked up on Tk.tk
mainloop() / Misc.mainloop() O(w + e·(f + c) + D) O(1) Dispatches events until quit() or the last window closes; an after callback also deletes its command when it runs. The module function needs a default root
Misc.quit() O(1) O(1) Sets a flag the loop checks after the current event
Misc.update() O(e·(f + c) + D) O(1) Runs every pending event, timer and idle callback, then returns; the idle work includes the pending relayout and redraw
Misc.update_idletasks() O(e·(f + c) + D) O(1) Idle callbacks only, which is where the relayout and redraws happen
Misc.wait_variable(var) / Misc.wait_window(w) / Misc.wait_visibility(w) O(w + e·(f + c) + D) O(1) A nested event loop until the variable is written, the window destroyed or its visibility changed
NoDefaultRoot() O(1) O(1) After it, every widget, variable or image created without a master raises RuntimeError
getboolean(s) / getint / getdouble O(m) O(m) Parse m characters; the module function asks the default root's interpreter, and getint and getdouble are int and float
Misc.getboolean(s) / Misc.getint(s) / Misc.getdouble(s) / Misc.getvar(name) / Misc.setvar(name, value) O(m + t·f) O(m) One conversion or one Tcl variable of m characters; getvar() and setvar() run the variable's traces
Misc.info_patchlevel() O(1) O(1) Python 3.11+
TclError / TkVersion / TclVersion / wantobjects O(1) O(1) The exception every Tk error raises, the version floats, and the flag that makes results come back typed rather than as strings

Misc: callbacks and events

Operation Time Space Notes
Misc.register(func) O(1) O(1) Creates one Tcl command whose name embeds id() and the function name, appended to the widget's registry list
Misc.deletecommand(name) O(c) O(1) Deletes the command and removes it from the list, which is scanned
Misc.destroy() O(c) O(1) Deletes every command the widget registered; the widget classes override it to destroy the window and its descendants first
Misc.after(ms, func, *args) O(pending) O(1) Registers one command and inserts the timer into Tcl's list, kept sorted by due time; the callback deletes its command when it fires, at O(c). after(ms) with no function blocks for ms without processing events
Misc.after_idle(func, *args) O(1) O(1) Registers one command and appends it to the idle list
Misc.after_cancel(id) O(pending + c) O(1) One after info round trip, which Tcl answers by walking its pending timers, then the registry scan; a falsy id raises ValueError
Misc.after_info(id) O(pending) O(pending) Python 3.13+; Tcl walks its pending timers for the id, and without one returns them all
Misc.bind(sequence, func, add) / Misc.bind_all() / Misc.bind_class(className, ...) O(b) O(b) One command per bound function; with add Tk copies the sequence's script to append the line, otherwise b is 1
Misc.bind(sequence) / Misc.bind() O(b + m) O(b + m) The sequence's script, one line per handler, or the widget's m bound sequences
Misc.unbind(sequence, funcid) / Canvas.tag_unbind() / Text.tag_unbind() O(b + c) O(b) Python 3.13+ reads the sequence's script and rewrites it without funcid's line, then deletes the command; before 3.13 every handler on the sequence goes. Without funcid, one call clears the sequence
Misc.unbind_all(sequence) / Misc.unbind_class(className, sequence) O(1) O(1) Clear the sequence's script; the commands stay registered
Misc.bindtags(tagList) O(tags) O(tags)
Misc.event_add(virtual, *sequences) / Misc.event_delete() / Misc.event_generate(sequence, **k) O(m + k² + b·f) O(m + k) event_generate delivers the event before returning unless when= defers it, so its handlers run inside the call
Misc.event_info(virtual) O(m) O(m) The sequences behind one virtual event, or every virtual event
Misc.focus_set() / Misc.focus_force() / Misc.tk_focusFollowsMouse() O(1) O(1)
Misc.focus_get() / Misc.focus_displayof() / Misc.focus_lastfor() / Misc.grab_current() O(p) O(p) Resolve the returned path name to its widget
Misc.tk_focusNext() / Misc.tk_focusPrev() O(W²) O(W) Walk the window tree from the widget until a widget accepts focus, listing the parent's children and searching them for the current widget at every step
Misc.grab_set() / Misc.grab_set_global() / Misc.grab_release() / Misc.grab_status() O(1) O(1)
Misc.clipboard_get() / Misc.clipboard_append(string) / Misc.clipboard_clear() O(m) O(m) The selection owner supplies the m characters, so clipboard_get() waits on it
Misc.selection_get() / Misc.selection_handle(func) / Misc.selection_own() / Misc.selection_own_get() / Misc.selection_clear() O(m) O(m) selection_handle registers one command that Tk calls with an offset and a length, so f is charged per chunk
Misc.option_add(pattern, value) / Misc.option_get(name, className) O(1) O(1)
Misc.option_clear() O(m) O(1) Frees every entry of the option database and reloads the display's defaults
Misc.option_readfile(fileName) O(F) O(F) Adds every entry of an F-byte resource file
Misc.send(interp, cmd, *args) O(w) O(1) A round trip to another interpreter on the same display, blocked until it replies
Misc.bell() / Misc.lower() / Misc.tkraise() / Misc.lift() O(1) O(1)
Misc.tk_setPalette(*args, **k) / Misc.tk_bisque() O(W) O(1) Recolour every widget under the root through the option database
Misc.tk_strictMotif(boolean) O(1) O(1)
Misc.tk_busy_hold() / Misc.tk_busy_forget() / Misc.tk_busy_configure() / Misc.tk_busy_cget() / Misc.tk_busy_status() O(1) O(1) Python 3.13+
Misc.tk_busy_current(pattern) O(m) O(m) Python 3.13+; the m busy windows matching the pattern
Misc.image_names() / Misc.image_types() / image_names() / image_types() O(images) O(images) image_types() is a fixed list; the module functions need a default root

Misc: configuration and window information

Operation Time Space Notes
Misc.configure(**k) / Misc.config() / Misc.__setitem__ O(k² + t·f) O(k) Applies k options in one Tk call; each callable value registers a command that lives until the widget is destroyed, and a variable or textvariable is read at once, firing its traces
Misc.configure() / Misc.keys() O(o) O(o) Tk returns every option's specification; keys() keeps the names
Misc.cget(key) / Misc.__getitem__ O(m) O(m) The value's m characters, converted
Misc.nametowidget(name) O(p) O(p) Splits the name, then one dict lookup per component from the root or from this widget
Misc.winfo_children() O(n·p) O(n) Asks Tk for the child names and resolves each from the root, skipping ones Tk owns such as menus
Misc.winfo_x() / Misc.winfo_y() / Misc.winfo_width() / Misc.winfo_height() / Misc.winfo_reqwidth() / Misc.winfo_reqheight() / Misc.winfo_geometry() / Misc.winfo_vrootx() / Misc.winfo_vrooty() / Misc.winfo_vrootwidth() / Misc.winfo_vrootheight() O(1) O(1) Read from the window record; a size is 1 until the widget is mapped and update_idletasks() has run
Misc.winfo_rootx() / Misc.winfo_rooty() O(p) O(1) Sum the offsets of every ancestor
Misc.winfo_id() / Misc.winfo_name() / Misc.winfo_class() / Misc.winfo_parent() / Misc.winfo_pathname(id) / Misc.winfo_exists() / Misc.winfo_ismapped() / Misc.winfo_manager() O(1) O(1)
Misc.winfo_toplevel() / Misc.winfo_viewable() O(p) O(1) Walk up the path
Misc.winfo_screen() / Misc.winfo_screenwidth() / Misc.winfo_screenheight() / Misc.winfo_screenmmwidth() / Misc.winfo_screenmmheight() / Misc.winfo_screendepth() / Misc.winfo_screencells() / Misc.winfo_screenvisual() / Misc.winfo_server() / Misc.winfo_depth() / Misc.winfo_cells() / Misc.winfo_visual() / Misc.winfo_visualid() / Misc.winfo_colormapfull() O(1) O(1)
Misc.winfo_pointerx() / Misc.winfo_pointery() / Misc.winfo_pointerxy() / Misc.winfo_containing(x, y) / Misc.winfo_pixels(number) / Misc.winfo_fpixels(number) / Misc.winfo_rgb(color) / Misc.winfo_atom(name) / Misc.winfo_atomname(id) O(1) O(1) The pointer queries and winfo_containing() are a round trip to the display server
Misc.winfo_visualsavailable() / Misc.winfo_interps() O(m) O(m) Every visual of the screen, or every Tk interpreter on the display
Misc.pack_slaves() / Misc.place_slaves() / Misc.grid_slaves(row, column) O(n) O(n) Every widget the manager holds; grid_slaves() filters by row or column after the scan
Misc.pack_propagate(flag) / Misc.grid_propagate(flag) / Misc.grid_anchor(anchor) / Misc.grid_columnconfigure(index, **k) / Misc.grid_rowconfigure(index, **k) O(k² + m) O(k) A list of m indices configures each one
Misc.grid_size() / Misc.grid_bbox() O(n) O(1) Tk recomputes the grid's extent from every managed widget
Misc.grid_location(x, y) O(rows + columns) O(1) Scans the slot offsets

Pack, Grid and Place

Operation Time Space Notes
Pack.pack_configure(**k) / Pack.pack() / Grid.grid_configure(**k) / Grid.grid() / Place.place_configure(**k) / Place.place() O(n + k²) O(k) Hands the widget to the manager, which walks the container's list of managed widgets to append it; the layout itself runs once at idle time for the whole container, however many widgets were added before it
Pack.pack_forget() / Grid.grid_forget() / Grid.grid_remove() / Place.place_forget() O(n) O(1) Unlinks the widget from the container's list of managed widgets, walked to find it; grid_remove() keeps the grid options for a later grid() with none
Pack.pack_info() / Grid.grid_info() / Place.place_info() O(o) O(o) A dict of the manager's options for this widget
Pack.propagate() / Pack.slaves() / Grid.bbox() / Grid.columnconfigure() / Grid.rowconfigure() / Grid.propagate() / Grid.size() / Grid.slaves() / Place.slaves() O(n + k²) O(n) The Misc container methods under their manager-local names, with the bounds above
Grid.location(x, y) O(rows + columns) O(1) Misc.grid_location() under its manager-local name

Wm

Operation Time Space Notes
Wm.wm_title(string) / Wm.wm_geometry(newGeometry) / Wm.wm_iconname() / Wm.wm_state() / Wm.wm_withdraw() / Wm.wm_deiconify() / Wm.wm_iconify() / Wm.wm_resizable() / Wm.wm_minsize() / Wm.wm_maxsize() / Wm.wm_aspect() / Wm.wm_overrideredirect() / Wm.wm_transient() / Wm.wm_group() / Wm.wm_client() / Wm.wm_command() / Wm.wm_focusmodel() / Wm.wm_positionfrom() / Wm.wm_sizefrom() / Wm.wm_grid() / Wm.wm_frame() / Wm.wm_iconbitmap() / Wm.wm_iconmask() / Wm.wm_iconposition() / Wm.wm_iconwindow() / Wm.wm_manage() / Wm.wm_forget() O(1) O(1) One request to the window manager; each is also available without the wm_ prefix, and the manager applies it asynchronously
Wm.wm_attributes(**k) O(k) O(k) The query with no arguments returns every attribute, as a dict with return_python_dict=True from Python 3.13
Wm.wm_protocol(name, func) O(1) O(1) Registers one command; WM_DELETE_WINDOW defaults to destroy() on Tk and Toplevel
Wm.wm_colormapwindows(*windows) O(m) O(m) One argument per window
Wm.wm_iconphoto(default, *images) O(P) O(P) Converts every pixel of every image given into the window manager's icon format

Variable, StringVar, IntVar, DoubleVar and BooleanVar

Operation Time Space Notes
Variable(master, value, name) / StringVar() / IntVar() / DoubleVar() / BooleanVar() O(m + t·f) O(m) One Tcl variable, named PY_VAR<n> from a module counter unless a name is given; a value is copied in, firing the write traces an existing variable of that name has, and without a value an existing variable keeps its value and a new one gets the class default
Variable.get() / Variable.set(value) / Variable.initialize(value) / StringVar.get() / IntVar.get() / DoubleVar.get() / BooleanVar.get() / BooleanVar.set() O(m + t·f) O(m) Copies the value across the interpreter boundary and runs the read or write traces; IntVar.get() accepts a float-valued string, BooleanVar.get() raises ValueError on anything Tcl cannot read as a boolean
Variable.trace_add(mode, callback) O(1) O(1) Registers one command and one trace; the name it returns is the command
Variable.trace_remove(mode, cbname) O(t² + c) O(t) Removes the trace, whose mode list must be the one given to trace_add() or nothing is removed, then lists the remaining traces at trace_info()'s cost before deleting the command
Variable.trace_info() O(t²) O(t) Tcl restarts its walk of the trace list for each trace it reports; t is rarely more than one
Variable.trace_variable(mode, callback) / Variable.trace() / Variable.trace_vdelete(mode, cbname) / Variable.trace_vinfo() O(t² + c) O(t) The same three operations through Tcl's old trace variable interface; deprecated in Python 3.14, and Tcl 9 dropped the interface, so under it they raise TclError
del a variable O(c + t·f) O(1) Unsets the Tcl variable, firing its unset traces, and deletes the trace commands it registered, so a variable a widget still uses must be kept alive by the caller

Widgets

Operation Time Space Notes
Widget(master, **k) / BaseWidget(master, widgetName, cnf, kw) / Frame() / Label() / Message() / Menubutton() / LabelFrame() / Button() / Checkbutton() / Radiobutton() / Scale() / Scrollbar() / Entry() / Spinbox() / Listbox() / Menu() / Canvas() / Text() / PanedWindow() O(k² + p + t·f) O(k + p) One Tk window; the default name is the class name with a per-master counter, !frame, !frame2, appended to the master's path, a name already in the master's children destroys the old widget first at that widget's destroy() cost, and a variable or textvariable is read at once, firing its traces
Toplevel(master, **k) O(k²) O(k) Also copies the root's title and icon name and registers the WM_DELETE_WINDOW protocol
BaseWidget.destroy() O(d + c + n) O(d) Recurses through every descendant, holding a copy of each children list, then destroys the window, freeing its n items, entries or lines, and deletes every command registered on the subtree
Button.invoke() / Checkbutton.invoke() / Radiobutton.invoke() O(f + t·f) O(1) Runs the command as a click would; the two with a variable write it first, firing its traces
Button.flash() / Checkbutton.flash() / Radiobutton.flash() O(1) O(1) Blocks the caller while Tk redraws the button four times with a fixed delay between them
Checkbutton.select() / Checkbutton.deselect() / Checkbutton.toggle() / Radiobutton.select() / Radiobutton.deselect() O(t·f) O(1) Set the widget's variable, which fires its write traces
Scale.get() / Scale.coords(value) / Scale.identify(x, y) O(1) O(1)
Scale.set(value) O(t·f) O(1) Writes the linked variable, firing its traces
Scrollbar.get() / Scrollbar.set(first, last) / Scrollbar.activate() / Scrollbar.delta(dx, dy) / Scrollbar.fraction(x, y) / Scrollbar.identify(x, y) O(1) O(1) set() is what a scrolled widget calls on every view change, so it is the one to keep cheap
OptionMenu(master, variable, value, *values, command) O(n²) O(n) A menubutton plus a menu built by one add_command() per value, each copying the entry array and registering a command; name= is accepted from Python 3.14
OptionMenu.destroy() O(d + c) O(1) The menu is a child, so the widget row covers it

Entry and Spinbox

Operation Time Space Notes
Entry.get() / Spinbox.get() O(m) O(m) The whole string
Entry.insert(index, string) / Spinbox.insert() / Entry.delete(first, last) / Spinbox.delete() O(n + m + f + t·f) O(n + m) Tk keeps the text as one array and rebuilds it, then runs the validatecommand if one is set and writes a linked textvariable, firing its traces
Entry.index(index) / Entry.icursor(index) / Spinbox.index() / Spinbox.icursor() / Spinbox.bbox(index) / Spinbox.identify(x, y) O(1) O(1) A numeric index; @x and insert resolve against the display layout
Entry.selection_range(start, end) / Entry.selection_from() / Entry.selection_to() / Entry.selection_adjust() / Entry.selection_clear() / Entry.selection_present() / Spinbox.selection() / Spinbox.selection_range() / Spinbox.selection_from() / Spinbox.selection_to() / Spinbox.selection_adjust() / Spinbox.selection_clear() / Spinbox.selection_present() / Spinbox.selection_element() O(1) O(1) Also spelled select_*; selection_present() returns a bool
Entry.scan_mark(x) / Entry.scan_dragto(x) / Spinbox.scan(*args) / Spinbox.scan_mark() / Spinbox.scan_dragto() O(1) O(1)
Spinbox.invoke(element) O(n + m + f + t·f) O(n + m) Presses the up or down button: the text is rebuilt with the next value, validated, written to a linked textvariable, and command runs

Listbox

Operation Time Space Notes
Listbox.insert(END, *elements) O(m) O(m) Appends to the Tcl list Tk keeps, amortized; with a listvariable the list is shared and copied first, O(n + m)
Listbox.insert(index, *elements) / Listbox.delete(first, last) O(n + m) O(n) Shifts the entries after the index and renumbers the selection and per-item option tables
Listbox.index(index) / Listbox.size() O(1) O(1)
Listbox.get(index) / Listbox.get(first, last) O(m) O(m) The entry's m characters, or a tuple of the m entries in the range
Listbox.curselection() O(n) O(s) Scans every entry against the selection table
Listbox.selection_set(first, last) / Listbox.selection_clear(first, last) O(m) O(m) One table entry per index in the range
Listbox.selection_includes(index) / Listbox.selection_anchor(index) / Listbox.activate(index) / Listbox.see(index) / Listbox.nearest(y) / Listbox.bbox(index) O(1) O(1) Every line has the same height, so nearest() is arithmetic; bbox() is None off screen
Listbox.itemcget(index, option) / Listbox.itemconfigure(index, **k) O(k²) O(k) Per-item colours live in a table keyed by index, so an item with none costs nothing
Listbox.scan_mark(x, y) / Listbox.scan_dragto(x, y) O(1) O(1)
Operation Time Space Notes
Menu.add_command(**k) / Menu.add_cascade() / Menu.add_checkbutton() / Menu.add_radiobutton() / Menu.add_separator() / Menu.add(itemType, **k) O(n + k²) O(n) Tk reallocates the entry array on every add, so building a menu of n entries is O(n²); a command registers one Tcl command
Menu.insert_command(index, **k) / Menu.insert_cascade() / Menu.insert_checkbutton() / Menu.insert_radiobutton() / Menu.insert_separator() / Menu.insert(index, itemType, **k) O(n + k²) O(n) The same reallocation, with the entries after the index shifted
Menu.delete(index1, index2) O(m·(o + c) + n) O(o) Fetches every option of each of the m entries to find its command, deletes the command, then shifts the array
Menu.index(index) O(n) O(1) An integer or end is O(1); a label is matched against every entry, and every other entry method that takes an index accepts a label at the same cost
Menu.entrycget(index, option) / Menu.entryconfigure(index, **k) O(k²) O(k) The query with no options returns all o of them
Menu.invoke(index) O(f + t·f) O(1) A checkbutton or radiobutton entry writes its variable first, firing its traces
Menu.post(x, y) / Menu.tk_popup(x, y, entry) O(f) O(1) Run the postcommand first
Menu.unpost() / Menu.activate(index) / Menu.type(index) / Menu.xposition(index) / Menu.yposition(index) O(1) O(1)

Canvas

Operation Time Space Notes
Canvas.create_line(*coords, **k) / Canvas.create_rectangle() / Canvas.create_oval() / Canvas.create_arc() / Canvas.create_polygon() / Canvas.create_text() / Canvas.create_image() / Canvas.create_bitmap() / Canvas.create_window() O(m + k²) O(m + k) m coordinates, or characters of a text item; the item is appended to the display list and entered in the id table, so item count does not matter
Canvas.find_withtag(id) / Canvas.bbox(id) / Canvas.type(id) O(1) O(1) An integer id is one hash lookup
Canvas.coords(id) / Canvas.gettags(id) / Canvas.itemcget(id, option) O(m) O(m) The item's m coordinates, tags or characters of the option's value, after the same lookup
Canvas.find_withtag(tag) / Canvas.find_all() / Canvas.find_enclosed(x1, y1, x2, y2) / Canvas.find_overlapping() / Canvas.find_closest(x, y, halo, start) / Canvas.find_above(tagOrId) / Canvas.find_below(tagOrId) / Canvas.find(*args) O(n + m) O(m) A tag or a search walks the display list, testing every item, the geometric searches against each item's m coordinates; there is no spatial index
Canvas.itemconfigure(tag, **k) / Canvas.move(tag, dx, dy) / Canvas.moveto(tag, x, y) / Canvas.scale(tag, x, y, xs, ys) / Canvas.delete(tag) / Canvas.dtag(tag, tagToDelete) / Canvas.tag_raise(tag) / Canvas.tag_lower(tag) / Canvas.bbox(*tags) O(n + m + k²) O(k) Every item matching the tag, found by the same walk, is updated, m coordinates in all; by id the walk is one hash lookup
Canvas.coords(tagOrId, *xy) O(n + m) O(m) Sets the m coordinates of the first item matching a tag, or of the item by id without the walk
Canvas.addtag(newtag, *args) / Canvas.addtag_withtag() / Canvas.addtag_all() / Canvas.addtag_enclosed() / Canvas.addtag_overlapping() / Canvas.addtag_closest() / Canvas.addtag_above() / Canvas.addtag_below() O(n + m) O(m) The same searches, adding the tag to each of the m matches
Canvas.tag_bind(tag, sequence, func, add) O(b) O(b) One command per bound function, on the tag, not per item; add copies the script as bind() does
Canvas.canvasx(screenx) / Canvas.canvasy(screeny) / Canvas.scan_mark(x, y) / Canvas.scan_dragto(x, y) O(1) O(1)
Canvas.focus(id) / Canvas.icursor(id, index) / Canvas.index(id, index) / Canvas.insert(id, index, string) / Canvas.dchars(id, first, last) / Canvas.select_from(id, index) / Canvas.select_to() / Canvas.select_adjust() / Canvas.select_clear() / Canvas.select_item() O(m) O(m) Text-item editing, m characters of the item; by tag, insert() and dchars() edit every matching item after the O(n) walk, the others the first
Canvas.postscript(**k) O(n + m) O(m) Renders every item, m coordinates and characters between them, into the output

Text

Operation Time Space Notes
Text.index(index) / Text.compare(index1, op, index2) / Text.mark_set(name, index) / Text.see(index) O(log L + m) O(1) Lines live in a B-tree; m is the characters before a line.char index on its line, walked to find it
Text.get(index1, index2) O(log L + m) O(m) The range's m characters, after resolving each index as in the row above
Text.insert(index, chars, *tags) / Text.delete(index1, index2) / Text.replace(index1, index2, chars) O(log L + m + ℓ) O(m + ℓ) Tk keeps each line's text in runs it splits and merges around the edit, copying the ℓ characters of the line; insert() with tags also adds each tag over the new text
Text.count(index1, index2, *options) O(log L + m) O(1) lines and indices come from the tree's counts, chars and displaychars walk the range; return_ints=True is Python 3.13+, where a single option also returns a plain int
Text.search(pattern, index, stopindex, **k) O(m) O(ℓ) An exact search scans the m characters between the indices, a line at a time through a buffer of that line; regexp=True matches the pattern against each line at the pattern's own cost instead
Text.bbox(index) / Text.dlineinfo(index) O(log L + m) O(1) One index resolution; None when the index is off screen
Text.mark_unset(*names) / Text.mark_gravity(name, direction) / Text.mark_next(index) / Text.mark_previous(index) / Text.mark_names() O(marks) O(marks) Marks are segments in the line they sit on; mark_names() lists all of them
Text.tag_add(tag, index1, index2) / Text.tag_remove(tag, index1, index2) O(log L + m) O(1) Inserts toggle segments at the ends of the range and updates the tag counts up the tree
Text.tag_ranges(tag) / Text.tag_nextrange(tag, index1, index2) / Text.tag_prevrange() / Text.tag_names(index) O(log L + m) O(m) Walk the toggles of the tag, m of them in the range; tag_names() with no index lists every tag
Text.tag_configure(tag, **k) / Text.tag_cget(tag, option) O(k²) O(k) Per tag, not per character
Text.tag_bind(tag, sequence, func, add) O(b) O(b) One command per bound function, on the tag; add copies the script as bind() does
Text.tag_raise(tag, aboveThis) / Text.tag_lower(tag, belowThis) O(tags) O(1) Renumbers the priorities of the tags in between
Text.tag_delete(*tags) O(L + m) O(1) Removes each tag's m toggles from the whole text
Text.image_create(index, **k) / Text.window_create(index, **k) O(log L + m + k²) O(k) One embedded segment at the resolved index; window_create() with a create callback runs it at first display
Text.image_configure(index, **k) / Text.image_cget(index, option) / Text.window_configure(index, **k) / Text.window_cget(index, option) O(log L + m + k²) O(k)
Text.image_names() / Text.window_names() / Text.peer_names() O(m) O(m)
Text.peer_create(newPathName, **k) O(k²) O(k) A second widget over the same B-tree, so its edits are the same edits
Text.edit_undo() / Text.edit_redo() O(r·(log L + ℓ) + m) O(m + ℓ) Replays the r edits since the last separator, m characters in all, each as an edit of the line it touches; raises TclError when the stack is empty
Text.edit_reset() / Text.edit_separator() / Text.edit_modified(arg) / Text.edit(*args) O(u) O(1) edit_reset() discards the u undo entries; the others are O(1)
Text.dump(index1, index2, command, **k) O(m·f + c) O(m) One Tcl callback per item in the range, through a command registered for the call and deleted after it, collected into a list of triples unless command is given
Text.debug(boolean) / Text.yview_pickplace(*what) / Text.scan_mark(x, y) / Text.scan_dragto(x, y) O(1) O(1) With debug on, every edit also checks the whole tree

XView and YView

Operation Time Space Notes
XView.xview() / XView.xview_moveto(fraction) / XView.xview_scroll(number, what) / YView.yview() / YView.yview_moveto(fraction) / YView.yview_scroll(number, what) O(log L + m) O(1) The query returns the two visible fractions; for every widget but Text all six are O(1), while a Text finds the fraction's line through its pixel-height tree and walks the m lines a scroll passes

PanedWindow

Operation Time Space Notes
PanedWindow.add(child, **k) / PanedWindow.remove(child) / PanedWindow.forget() O(n + k²) O(n) Tk rebuilds its pane array
PanedWindow.panes() O(n) O(n)
PanedWindow.paneconfigure(child, **k) / PanedWindow.panecget(child, option) O(n + k²) O(k) The child is found by scanning the panes
PanedWindow.identify(x, y) O(n) O(1)
PanedWindow.sash(*args) / PanedWindow.sash_coord(index) / PanedWindow.sash_mark(index, x, y) / PanedWindow.sash_place(index, x, y) / PanedWindow.proxy(*args) / PanedWindow.proxy_coord() / PanedWindow.proxy_forget() / PanedWindow.proxy_place(x, y) O(1) O(1) sash_place() moves every pane after the sash at the next idle layout

Image, PhotoImage and BitmapImage

Operation Time Space Notes
Image(imgtype, name, cnf, master, **k) O(k²) O(k) Names default to pyimage<n> from a class counter; the same quadratic option marshalling as widgets
PhotoImage(file=path) / PhotoImage(data=bytes) / BitmapImage(file=path) O(P) O(P) Decodes the whole image of P pixels into Tk's 32-bit buffer, whatever the file's depth, or the bitmap into one bit per pixel
Image.configure(**k) / Image.__setitem__ / Image.__getitem__ / PhotoImage.cget(option) O(k²) O(k) file= or data= reloads the image, O(P)
Image.height() / Image.width() / Image.type() / PhotoImage.get(x, y) / PhotoImage.transparency_get(x, y) / PhotoImage.transparency_set(x, y, boolean) O(1) O(1)
PhotoImage.blank() O(P) O(1) Clears every pixel to transparent
PhotoImage.copy(from_coords, zoom, subsample) / PhotoImage.zoom(x, y) / PhotoImage.subsample(x, y) / PhotoImage.copy_replace(source, **k) O(P) O(P) P is the destination's pixel count; the first three create a new image, copy_replace() and the keyword arguments are Python 3.13+
PhotoImage.put(data, to) O(m + P) O(P) m pixels given as rows of colour names; a region outside the image grows it to P pixels
PhotoImage.read(filename, format, **k) / PhotoImage.write(filename, format, from_coords) O(P) O(P) read(), and write()'s background and grayscale, are Python 3.13+
PhotoImage.data(format, from_coords, **k) O(P) O(P) Python 3.13+; the image encoded, a tuple of row strings for the default format
del an image O(m) O(1) Deletes the Tk image and tells each of the m widgets and canvas items showing it, which go blank, so keep a reference

Event, EventType and CallWrapper

Operation Time Space Notes
Event.serial / Event.num / Event.focus / Event.height / Event.width / Event.keycode / Event.state / Event.time / Event.x / Event.y / Event.x_root / Event.y_root / Event.char / Event.send_event / Event.keysym / Event.keysym_num / Event.type / Event.widget / Event.delta O(1) O(1) Filled in once per delivered event from Tk's substitutions whether or not the handler reads them, except focus and send_event, which are left unset when Tk supplies no boolean; widget is looked up by path name, O(p), and type is an EventType
EventType O(1) O(1) A str enum of the numeric event type codes
CallWrapper(func, subst, widget) O(f) O(1) What every registered command runs: subst then func, with any exception but SystemExit sent to report_callback_exception() rather than raised

tkinter.ttk

Operation Time Space Notes
ttk.Widget(master, widgetname, **k) / ttk.Frame() / ttk.Label() / ttk.Labelframe() / ttk.LabelFrame() / ttk.Menubutton() / ttk.Button() / ttk.Checkbutton() / ttk.Radiobutton() / ttk.Entry() / ttk.Combobox() / ttk.Spinbox() / ttk.Scale() / ttk.Scrollbar() / ttk.Separator() / ttk.Sizegrip() / ttk.Progressbar() / ttk.Notebook() / ttk.Panedwindow() / ttk.PanedWindow() / ttk.Treeview() O(k² + t·f) O(k) The classic widget constructor and its option marshalling; only the style and item methods below format options linearly
ttk.setup_master(master) O(1) O(1) The master given, or the default root
ttk.tclobjs_to_py(adict) O(m) O(m) Converts the dict's values in place, a list per tuple value, m elements in all, and returns the same dict
ttk.Widget.state(statespec) / ttk.Widget.instate(statespec, callback) / ttk.Widget.identify(x, y) O(m + f) O(m) m state flags; instate runs the callback only when they all hold
ttk.Button.invoke() / ttk.Checkbutton.invoke() / ttk.Radiobutton.invoke() O(f + t·f) O(1) The two with a variable write it first, firing its traces
ttk.Entry.bbox(index) / ttk.Entry.identify(x, y) / ttk.Scale.get(x, y) O(1) O(1)
ttk.Combobox.set(value) / ttk.Spinbox.set(value) O(m + t·f) O(m) The value's m characters, copied in, and the traces of a linked textvariable
ttk.Entry.validate() O(f) O(1) Runs the validation command
ttk.Combobox.current(newindex) O(n) O(1) The query scans the values for the current one
ttk.Scale.configure(**k) O(k² + b·f) O(k) The classic configure(), then generates <<RangeChanged>> when from_ or to is among the options, running its handlers, the LabeledScale label's among them, before returning
ttk.LabeledScale(master, variable, from_, to) / ttk.LabeledScale.value O(t·f) O(1) A frame, a scale, a label and one write trace on the variable; value reads or writes that variable, firing its traces
ttk.LabeledScale.destroy() O(t² + c + d) O(t) Removes its trace at trace_remove()'s cost, then destroys the three widgets
ttk.OptionMenu(master, variable, default, *values) / ttk.OptionMenu.set_menu(default, *values) / ttk.OptionMenu.destroy() O(n²) O(n) set_menu() deletes every entry and adds one radiobutton per value to a classic Menu, each add copying its entry array; name= is accepted from Python 3.14
ttk.Notebook.add(child, **k) / ttk.Notebook.insert(pos, child, **k) / ttk.Notebook.forget(tab_id) / ttk.Notebook.hide(tab_id) / ttk.Notebook.index(tab_id) / ttk.Notebook.select(tab_id) / ttk.Notebook.tab(tab_id, **k) / ttk.Notebook.identify(x, y) O(n + k) O(k) Tabs are an array Tk searches and shifts
ttk.Notebook.tabs() / ttk.Notebook.enable_traversal() O(n) O(n) enable_traversal() binds the keyboard shortcuts on the toplevel once
ttk.Panedwindow.insert(pos, child, **k) / ttk.Panedwindow.forget(pane) / ttk.Panedwindow.pane(pane, **k) / ttk.Panedwindow.sashpos(index, newpos) O(n + k) O(k)
ttk.Progressbar.step(amount) O(t·f) O(1) Writes the linked variable, firing its traces
ttk.Progressbar.start(interval) / ttk.Progressbar.stop() O(pending) O(1) start() schedules a repeating Tcl timer that steps the bar, and stop() cancels it, at the timer list's cost
ttk.Style(master) O(1) O(1)
ttk.Style.lookup(style, option, state, default) O(m) O(1) Scans the option's m state specifications from map() for the first that matches
ttk.Style.theme_names() O(themes) O(themes)
ttk.Style.configure(style, **k) / ttk.Style.map(style, **k) / ttk.Style.element_options(elementname) / ttk.Style.element_names() O(m) O(m) m is the total size of the options and their values, map()'s state specs included, formatted one by one; the query with no options returns all of them
ttk.Style.layout(style, layoutspec) / ttk.Style.element_create(elementname, etype, *args, **k) / ttk.Style.theme_create(themename, parent, settings) / ttk.Style.theme_settings(themename, settings) O(m·depth) O(m) m is the size of the Tcl script the spec is formatted into, which indents each nesting level of a layout, and each level's script is copied again into its parent's; vsapi elements are Python 3.13+
ttk.Style.theme_use(themename) O(W) O(1) Switching themes restyles every ttk widget
ttk.Treeview.insert(parent, index, iid, **k) O(i + m) O(m) Children are a linked list, walked i positions; END starts from a cached last child, so filling a level in order is linear, and only the first END after inserting elsewhere walks all n siblings. m is the size of the options and their values, and the item id is one hash entry
ttk.Treeview.exists(item) / ttk.Treeview.parent(item) / ttk.Treeview.next(item) / ttk.Treeview.prev(item) / ttk.Treeview.focus(item) O(1) O(1)
ttk.Treeview.item(item, option, **k) / ttk.Treeview.set(item, column, value) / ttk.Treeview.column(column, option, **k) / ttk.Treeview.heading(column, option, **k) / ttk.Treeview.tag_configure(tagname, option, **k) O(m) O(m) m is the total size of the options and their values, a values list included; the query with no option returns all of them, converted from Tcl objects
ttk.Treeview.index(item) / ttk.Treeview.move(item, parent, index) / ttk.Treeview.reattach() O(i + depth) O(1) Walk the siblings to or from the position; move() first walks the new parent's item ancestors to refuse a cycle
ttk.Treeview.get_children(item) O(n) O(n) n is that item's children
ttk.Treeview.set_children(item, *newchildren) O(n·depth) O(n) Checks each new child against the item's ancestors to refuse a cycle, then relinks them
ttk.Treeview.delete(*items) / ttk.Treeview.detach(*items) O(m + d) O(m + depth) delete() frees every descendant too, recursing down the item tree
ttk.Treeview.selection() / ttk.Treeview.selection_set(*items) / ttk.Treeview.selection_add() / ttk.Treeview.selection_remove() / ttk.Treeview.selection_toggle() O(n) O(s) The query and selection_set() walk every item in the tree; add, remove and toggle touch only the m given
ttk.Treeview.tag_has(tagname, item) O(n) O(m) With an item, one check; without, walks every item and returns the m carrying the tag
ttk.Treeview.identify(component, x, y) / ttk.Treeview.identify_row(y) / ttk.Treeview.identify_column(x) / ttk.Treeview.identify_region(x, y) / ttk.Treeview.identify_element(x, y) / ttk.Treeview.see(item) / ttk.Treeview.bbox(item, column) O(n) O(1) A row is found by walking the displayed items from the top; see() opens every ancestor
ttk.Treeview.tag_bind(tagname, sequence, callback) O(1) O(1) One command per bound function

tkinter.font

Operation Time Space Notes
font.Font(root, font, name, exists, **k) O(k) O(1) One named Tk font, font<n> from a class counter; font= copies the actual attributes of an existing font first, exists=True checks the name against every named font, O(fonts)
font.Font.actual(option) / font.Font.config(**k) / font.Font.configure() / font.Font.metrics(*options) O(o) O(o) actual() and metrics() with no arguments return every attribute, which forces the font to be loaded from the system the first time
font.Font.cget(option) / font.Font.__getitem__ / font.Font.__setitem__ O(1) O(1)
font.Font.measure(text) O(m) O(1) Lays out the m characters
font.Font.copy() O(o) O(o) A new named font from actual()
font.families() / font.names() O(m) O(m) Every family the display offers, or every named font in the interpreter
font.nametofont(name, root) O(fonts) O(fonts) Wraps an existing named font without copying it, after checking the name against every named font
del a font O(1) O(1) Deletes the named font if this object created it; Tk keeps the font alive while widgets still use it, the name is just gone for new ones

Dialogs

Operation Time Space Notes
commondialog.Dialog(master, **options) O(k) O(k) Stores the options; nothing is shown
commondialog.Dialog.show(**options) O(w + k²) O(1) Runs the platform dialog and blocks until it closes; without a master, creates a withdrawn temporary root and destroys it afterwards
messagebox.showinfo(title, message) / messagebox.showwarning() / messagebox.showerror() / messagebox.askquestion() / messagebox.askokcancel() / messagebox.askyesno() / messagebox.askyesnocancel() / messagebox.askretrycancel() / messagebox.Message O(w) O(1) Message.show() under a fixed icon and button set; the ask* functions but askquestion() convert the answer to a bool or None
filedialog.askopenfilename(**options) / filedialog.asksaveasfilename() / filedialog.askopenfilenames() / filedialog.askdirectory() / filedialog.Open / filedialog.SaveAs / filedialog.Directory O(w) O(m) The native dialog lists directories itself; askopenfilenames() returns the m chosen names
filedialog.askopenfile(mode, **options) / filedialog.askopenfiles() / filedialog.asksaveasfile() O(w) O(m) The same, then open() on each name
filedialog.FileDialog(master, title) / filedialog.LoadFileDialog() / filedialog.SaveFileDialog() O(1) O(1) The pure-Tk dialog: a toplevel with two listboxes, two entries and three buttons
filedialog.FileDialog.go(dir_or_file, pattern, default, key) O(w + E log E) O(E) Runs filter_command(), then mainloop() until a button ends it; key= remembers the directory and pattern per key in a module dict
filedialog.FileDialog.filter_command(event) O(E log E) O(E) Lists the directory, sorts its E names, stats each one to split directories from files, and refills both listboxes
filedialog.FileDialog.set_filter(dir, pat) / filedialog.FileDialog.get_filter() / filedialog.FileDialog.get_selection() / filedialog.FileDialog.set_selection(file) / filedialog.FileDialog.ok_event() / filedialog.FileDialog.ok_command() / filedialog.FileDialog.cancel_command() / filedialog.FileDialog.quit(how) / filedialog.FileDialog.dirs_select_event() / filedialog.FileDialog.files_double_event() / filedialog.FileDialog.files_select_event() O(1) O(1) ok_command() ends the dialog with the selection
filedialog.FileDialog.dirs_double_event(event) O(E log E) O(E) Enters the directory through filter_command()
filedialog.LoadFileDialog.ok_command() / filedialog.SaveFileDialog.ok_command() O(w) O(1) One or two stat calls, and a bell instead of ending the dialog for a missing file or a directory; the save variant asks for confirmation before overwriting, blocking until answered
simpledialog.Dialog(parent, title) / simpledialog.Dialog.result O(w) O(1) Builds body() and buttonbox(), grabs, and does not return from the constructor until the dialog is closed; result holds what apply() stored
simpledialog.Dialog.body(master) / simpledialog.Dialog.buttonbox() / simpledialog.Dialog.validate() / simpledialog.Dialog.apply() / simpledialog.Dialog.ok(event) / simpledialog.Dialog.cancel(event) / simpledialog.Dialog.destroy() O(f) O(1) Overridable hooks; ok() runs validate() then apply()
simpledialog.SimpleDialog(master, text, buttons, default, cancel, title, class_) O(n²) O(n) One button per entry of buttons, each packed after the ones before it
simpledialog.SimpleDialog.go() O(w) O(1) mainloop() until done(), then returns the button index
simpledialog.SimpleDialog.return_event(event) / simpledialog.SimpleDialog.wm_delete_window() / simpledialog.SimpleDialog.done(num) O(1) O(1)
simpledialog.askstring(title, prompt) / simpledialog.askinteger() / simpledialog.askfloat() O(w) O(1) A Dialog with an entry; the two numeric ones re-prompt on a value outside minvalue and maxvalue
colorchooser.askcolor(color, **options) / colorchooser.Chooser O(w) O(1) Returns the (r, g, b) tuple and the hex string, or (None, None)
dialog.Dialog(master, **options) O(w + k²) O(1) tk_dialog blocks in the constructor; num holds the button pressed
dialog.Dialog.destroy() O(1) O(1) Nothing: the window is gone when the constructor returns
scrolledtext.ScrolledText(master, **k) / scrolledtext.ScrolledText.frame / scrolledtext.ScrolledText.vbar O(k²) O(1) A Text in a Frame with a Scrollbar; the frame's geometry methods are copied onto the widget, so pack() and grid() manage frame, and vbar is the scrollbar
dnd.dnd_start(source, event) / dnd.DndHandler(source, event) O(1) O(1) Binds motion and release on the widget under the pointer; None while a drag is already in progress on that root
dnd.DndHandler.on_motion(event) O(p·f) O(1) Finds the widget under the pointer and walks up its path calling each dnd_accept, then the target's dnd_enter, dnd_leave or dnd_motion
dnd.DndHandler.on_release(event) / dnd.DndHandler.cancel(event) / dnd.DndHandler.finish(event, commit) O(b + c + f) O(b) Unbind the two handlers, restore the cursor, and call the target's dnd_commit or dnd_leave and the source's dnd_end

Callbacks Are Not Free to Register

Each command=, bind() and after() creates a Tcl command and appends its name to the widget's registry; trace_add() does the same on the variable's own registry, which its del clears. A registry is a list, so cancelling a timer or deleting a command scans it, and the callback a timer runs deletes its own command when it fires. Bindings and options never delete theirs until the widget is destroyed, so rebinding the same sequence in a loop leaks a command per iteration; unbind() with the id bind() returned is what frees it.

import tkinter as tk

root = tk.Tcl()  # no display needed for timers, variables and traces

fired = []
root.after(0, fired.append, "timer")  # O(1): registers one Tcl command
root.after_idle(fired.append, "idle")  # O(1)
cancelled = root.after(0, fired.append, "cancelled")
root.after_cancel(cancelled)  # O(c): finds and deletes the command
root.update()  # O(e·(f + c)): runs what is due; each callback deletes its command
print(fired)

counter = tk.IntVar(master=root, value=1)  # O(1): one Tcl variable
writes = []
name = counter.trace_add("write", lambda *args: writes.append(counter.get()))  # O(1)
counter.set(counter.get() + 1)  # O(m + t·f): the trace runs inside set()
counter.trace_remove("write", name)  # O(t² + c)
counter.set(10)
print(writes, counter.get())

Output:

['timer', 'idle']
[2] 10

Find by Id, Not by Tag

A canvas keeps its items in a display list with a hash table from id to item. find_withtag(3) is one lookup; find_withtag("dot") walks every item, and so does every other search and every operation applied through a tag. Keep the ids create_* returns when the same items are moved every frame.

import tkinter as tk

root = tk.Tk()
canvas = tk.Canvas(root, width=200, height=200)
canvas.pack()

ids = [
    canvas.create_oval(x, x, x + 5, x + 5, tags="dot")  # O(m + k²), m = 4 coordinates
    for x in range(0, 200, 10)
]

canvas.move(ids[0], 1, 1)  # O(m): one hash lookup, then its four coordinates
canvas.move("dot", 1, 1)  # O(n): every item is tested for the tag
print(len(canvas.find_withtag("dot")))  # O(n)
print(canvas.find_withtag(ids[0]) == (ids[0],))  # O(1)

root.destroy()

Output:

20
True

Text Indices Are Logarithmic, Ranges Are Linear

A Text widget's lines are a B-tree, so resolving "500.0" costs O(log L) however long the document is, and so does insert() at that index apart from the characters inserted and the line they land on. What is linear is the range: get("1.0", "end") copies the whole document, search() scans it, and tag_add() over the whole document leaves the tag's toggles for tag_ranges() to walk. A Listbox is the opposite shape: an array, O(1) to index, O(n) to insert anywhere but the end.

import tkinter as tk

root = tk.Tk()
text = tk.Text(root)
text.insert("end", "\n".join(f"line {i}" for i in range(1000)))  # O(log L + m)

print(text.index("500.0 lineend"))  # O(log L)
print(text.index("end"))  # O(log L): the line count comes from the tree
print(text.search("line 999", "1.0"))  # O(m): scans from the top

listbox = tk.Listbox(root)
listbox.insert("end", *range(1000))  # O(m), amortized
listbox.insert(0, "first")  # O(n + m): shifts every entry
print(listbox.size(), listbox.get(0), listbox.get(500))  # O(1) each

root.destroy()

Output:

500.8
1001.0
1000.0
1001 first 499