Skip to content

Optimization Tips

PythonExtra is fast enough for real-time games — if you’re careful.
But if your app starts lagging, there are three usual culprits:

  1. Drawing too much
  2. Inefficient algorithms
  3. Memory allocation & GC abuse

This page shows how to spot those issues and fix them, with concrete examples.


You can easily get 20+ FPS with full-screen redraws — as long as you’re not redrawing what doesn’t need to change.

❌ Bad: Redrawing things you’re about to overwrite

Section titled “❌ Bad: Redrawing things you’re about to overwrite”
# Clear the screen, then draw a full tileset right over it
drect(0, 0, DWIDTH-1, DHEIGHT-1, C_WHITE)
for y in range(0, DHEIGHT, 16):
for x in range(0, DWIDTH, 16):
drect(x, y, x+15, y+15, C_GRAY) # Overwrites previous draw

You’re wasting time filling pixels with white only to overwrite them immediately.

for y in range(0, DHEIGHT, 16):
for x in range(0, DWIDTH, 16):
drect(x, y, x+15, y+15, C_GRAY)

Unless you need to clear parts of the screen (like UI or old objects), don’t redraw them.


Constantly creating new lists/tuples/objects every frame leads to spikes in garbage collection, especially if they’re small.

# Builds a new list of coords every time
pixels = [(x * 10, y * 10) for x, y in snake]

This creates N tuples per frame, and GC hates it.

✅ Better: Use preallocated buffers (but still not perfect)

Section titled “✅ Better: Use preallocated buffers (but still not perfect)”
# Still makes tuples, but reuses the list
pixels = [(0, 0)] * 100
for i, (x, y) in enumerate(snake):
pixels[i] = (x * 10, y * 10)

✅✅ Even better: Use flat bytes or bytearray

Section titled “✅✅ Even better: Use flat bytes or bytearray”
# 100 pairs = 200 bytes
buf = bytearray(200)
for i, (x, y) in enumerate(snake):
buf[i*2] = x
buf[i*2+1] = y
  • bytes/bytearray are 4x cheaper per value than tuples
  • Especially useful when values fit in 0–255 range (like grid coordinates or sprite IDs)
  • You get CPU cache friendliness and less GC overhead

3. Avoid Allocating Thousands of Small Values

Section titled “3. Avoid Allocating Thousands of Small Values”

If you’re building a huge array of tiny tuples, you’re feeding the garbage collector a buffet of objects it has to track and clean.

positions = [(x, x) for x in range(1000)]

That’s 1,000 allocations — and it gets worse if you do this in a loop.

✅ Better: Use a flat array or shared buffer

Section titled “✅ Better: Use a flat array or shared buffer”
positions = bytearray(2000)
for i in range(1000):
positions[i*2] = i % 256
positions[i*2+1] = i // 4
  • Uses one object instead of 1,000
  • Fits better in memory
  • No GC required

This trick is essential for tilemaps, image buffers, path grids, etc.


def find_closest(entities):
return sorted(entities, key=distance)[0]
# In the main loop
closest = find_closest(enemies)

Even for 30–50 items, this adds up fast.

✅ Better: Cache or stagger expensive work

Section titled “✅ Better: Cache or stagger expensive work”
if frame % 10 == 0:
closest = min(enemies, key=distance)

Spread expensive calculations across frames, or only update when data changes.


ProblemAvoid thisDo this instead
OverdrawingClearing then overdrawingOnly draw what’s visible/changed
GC abuseAllocating lists/tuples constantlyPreallocate or use bytearray
Algorithm bottlenecksSorting/pathfinding every frameUpdate on timer or when needed only

  • drect() includes both x2/y2 — so full-screen is drect(0, 0, DWIDTH-1, DHEIGHT-1, color)
  • dupdate() is mandatory to display changes. Call it once per frame.
  • For input-heavy loops, clearevents() and cleareventflips() are your friends.
  • Partial redraws (only updating specific screen regions) can improve performance but are tricky. Unless you’re drawing fewer than 3 things per frame, full redraw is simpler and usually fast enough.

Need help diagnosing your frame drops?
Ask the 👨‍💻 PythonExtra ChatGPT — just paste your loop or logic!