Quick answer: Call pygame.init() (which includes pygame.joystick.init()), then pump events at least once with pygame.event.pump() before checking pygame.joystick.get_count(). For hot-plug support, handle JOYDEVICEADDED events in your main loop instead of checking count at startup only.
Here is how to fix pygame joystick not detected on startup. You plug in your gamepad, run your pygame game, and pygame.joystick.get_count() returns 0. The controller works in other applications. You call pygame.joystick.init() explicitly and it still returns 0. The joystick is physically connected but pygame cannot see it. This is almost always an initialization timing issue.
The Symptom
pygame.joystick.get_count() returns 0 despite a gamepad being physically connected and recognized by the operating system. Creating a pygame.joystick.Joystick(0) object raises an IndexError or pygame.error. The controller works in other games and shows up in the OS device manager.
What Causes This
Checking count before event pump. Pygame’s joystick subsystem relies on SDL’s event system to detect devices. Until events are pumped at least once after initialization, the device list may be empty. Calling get_count() immediately after init() without pumping returns stale (empty) data.
joystick.init() not called. While pygame.init() initializes the joystick subsystem, if you use selective initialization (e.g., only pygame.display.init()), the joystick subsystem remains uninitialized.
Joystick subsystem quit and not restarted. Some code patterns from older tutorials call pygame.joystick.quit() and pygame.joystick.init() every frame to detect hot-plugged devices. If quit is called without a matching init, the subsystem stays dead.
Platform permissions (Linux). On Linux, joystick devices are at /dev/input/js*. The user running the game needs read access. Without permissions, SDL cannot open the device and reports 0 joysticks.
Controller not supported by SDL. Very old or exotic controllers without SDL Game Controller DB mappings may not be recognized. They exist as raw HID devices but SDL does not enumerate them as joysticks.
The Fix
Step 1: Initialize and pump before checking.
import pygame
pygame.init()
pygame.event.pump() # Process initial device events
joystick_count = pygame.joystick.get_count()
print(f"Joysticks found: {joystick_count}")
if joystick_count > 0:
joy = pygame.joystick.Joystick(0)
joy.init()
print(f"Controller: {joy.get_name()}")
The pygame.event.pump() call processes pending SDL events including device enumeration. Without it, get_count() may return 0 on the first frame.
Step 2: Handle hot-plug with JOYDEVICEADDED.
import pygame
pygame.init()
joysticks = {}
def game_loop():
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.JOYDEVICEADDED:
joy = pygame.joystick.Joystick(event.device_index)
joy.init()
joysticks[joy.get_instance_id()] = joy
print(f"Connected: {joy.get_name()}")
elif event.type == pygame.JOYDEVICEREMOVED:
if event.instance_id in joysticks:
del joysticks[event.instance_id]
print("Controller disconnected")
elif event.type == pygame.JOYBUTTONDOWN:
print(f"Button {event.button} pressed")
game_loop()
JOYDEVICEADDED fires both at startup (for already-connected devices) and when new devices are plugged in. This pattern handles both cases without polling get_count().
Step 3: Avoid the quit/init anti-pattern.
# WRONG: Old hot-plug pattern (causes detection issues)
def update():
pygame.joystick.quit() # Kills all joystick state
pygame.joystick.init() # Re-enumerates (unreliable)
count = pygame.joystick.get_count()
# CORRECT: Use events for hot-plug (see Step 2 above)
The quit/init pattern was a workaround for SDL 1.x. Modern pygame (SDL 2.x) uses events for hot-plug. The old pattern can actually cause detection failures.
Step 4: Check Linux permissions. On Linux, joystick devices at /dev/input/js* require read access. Add your user to the input group: sudo usermod -a -G input $USER, then log out and back in.
“Pygame joystick detection is event-driven. Pump events first, then query. For hot-plug, listen for JOYDEVICEADDED instead of polling get_count every frame.”
Related Issues
For audio initialization issues in pygame, see general pygame initialization patterns. For game loop timing issues that can affect event processing, ensure your loop calls pygame.event.get() or pygame.event.pump() every frame.
Init, pump events, then check count. Use JOYDEVICEADDED for hot-plug. Never quit/init per frame.