Quick answer: Replace clock.tick_busy_loop(60) with clock.tick(60). Busy-loop spins to hit the deadline exactly; regular tick sleeps the thread and frees the core for other work.

Your turn-based game spends 90% of its time waiting for the player’s input. Yet a CPU core is permanently at 100%, the laptop’s fan is running, the battery drains in 2 hours. The culprit is the frame pacer not sleeping the thread.

How Pygame Schedules Frames

Pygame’s Clock object has two pacing methods:

Some online tutorials recommend tick_busy_loop because it produces smoother frame times in microbenchmarks. The reality is that on any modern OS, the 1ms jitter of tick is invisible to players, and the CPU savings are dramatic.

The Fix

import pygame

pygame.init()
screen = pygame.display.set_mode((800, 600))
clock = pygame.time.Clock()

running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    # update and draw...
    pygame.display.flip()
    clock.tick(60)   # NOT tick_busy_loop

pygame.quit()

clock.tick(60) sleeps the thread for whatever fraction of 1/60 second remains. The CPU is free for other processes (or for staying idle). Frame time variance increases from ~0.05ms to ~1ms, which is invisible on a 60 Hz display refresh.

VSYNC: Even Better

For lowest CPU and best visual smoothness, enable hardware vsync at display setup:

screen = pygame.display.set_mode(
    (800, 600),
    pygame.SCALED | pygame.DOUBLEBUF,
    vsync=1
)

With vsync=1, display.flip() blocks until the next monitor refresh. You can still call clock.tick(60) for delta-time measurement, but the actual frame pacing is dictated by the monitor — resulting in tear-free output and minimum CPU.

Profiling Frame Time

If you switched to tick but CPU is still high, your loop body is the bottleneck. Measure:

import time

frame_start = time.perf_counter()
# ... your update and draw ...
frame_end = time.perf_counter()
frame_ms = (frame_end - frame_start) * 1000
if frame_ms > 14:
    print(f"slow frame: {frame_ms:.1f}ms")

Frame times consistently >15ms mean tick has nothing to wait on; the work is filling the budget. Use cProfile to find slow code:

python -m cProfile -s cumtime your_game.py | head -30

When tick_busy_loop Might Make Sense

Niche case: a rhythm game that synchronizes audio to sub-millisecond input. Even there, audio-driven timing (callback from pygame.mixer.music) outperforms frame-locked timing for the same goal. The honest answer is: stop using tick_busy_loop for game frame pacing.

Verifying

Open Task Manager / Activity Monitor / top. The Python process CPU should drop from ~100% per core to under 10% when the game is idle on a menu screen. The fan should quiet down within seconds. Frame counter readouts should remain at 60 FPS.

“A CPU core at 100% when nothing’s happening on screen is a tick_busy_loop. Switch to tick and watch the temperature drop.”

Default to vsync=1 + clock.tick(60). Reserve tick_busy_loop for cases you can articulate a specific reason.