Quick answer: Replace clock.tick_busy_loop(60) with clock.tick(60). Or if you have no clock at all, add one. tick yields to the OS between frames; tick_busy_loop spins, eating a whole CPU core.

Game runs fine. Laptop fan revs to maximum. Battery life halves. Top shows your Python process at 100% CPU on one core. The cause is almost always a busy loop in the main game loop.

The Symptom

Pygame game pegs CPU. Frame rate looks normal in-game (60-120 fps depending on what you set). But the OS says one core is fully saturated even when the game shows a static menu.

What Causes This

Three common patterns burn CPU:

  1. No frame cap. A loop with no Clock.tick runs as fast as Python can. With nothing to render, that’s thousands of iterations per second.
  2. tick_busy_loop instead of tick. tick_busy_loop spins on time.time() until the deadline. Accurate but wastes the CPU.
  3. Constant input polling. A loop calling pygame.event.get() and pygame.key.get_pressed() in a tight loop without sleeping.

The Fix

Step 1: Use Clock.tick.

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 + draw
    pygame.display.flip()

    clock.tick(60)   # cap at 60, yield to OS in between

tick(60) sleeps the thread until the next 60 Hz boundary. CPU usage drops to whatever your update + draw actually need.

Step 2: Don’t use tick_busy_loop. Reserve tick_busy_loop only for cases where Clock.tick’s sleep granularity is too coarse and you genuinely need sub-millisecond accuracy. For a game loop, never.

Step 3: Sleep harder on idle screens. For pause menus or title screens that only need to react to input:

while running:
    event = pygame.event.wait(timeout=1000)   # block up to 1 second
    if event.type == pygame.QUIT:
        running = False
    elif event.type == pygame.KEYDOWN:
        handle_key(event.key)

    # Optionally redraw on tick:
    # pygame.display.flip()

event.wait blocks the OS thread until input arrives. CPU usage drops to near zero on a quiet menu.

Vsync Alternative

Setting pygame.display.set_mode(size, vsync=1) caps frame rate at the monitor refresh rate. The display driver handles the wait, which is generally OS-friendly but doesn’t replace Clock.tick if vsync is off or the display refresh is high.

Verifying

Run top (or Activity Monitor / Task Manager) with the game open. Idle menu should show single-digit CPU. Active gameplay 30–80% on one core depending on logic. If idle stays at 100%, your menu loop is busy-spinning.

“Clock.tick(60) on the loop. event.wait on idle screens. The fan stops.”

Related Issues

For mixer channel cuts, see channel overflow. For fullscreen surface lost, see fullscreen surface lost.

tick. wait. The CPU breathes.