Go preempts goroutines by inserting forced context switches at safe points identified during compilation. The runtime marks specific instructions (like function calls or memory accesses) as safe points where a goroutine can be paused without corrupting state; if a goroutine runs too long without hitting a safe point, the runtime injects a preemption check to force a yield. You can inspect these safe points and unsafe blocks using the GODEBUG environment variable or compiler flags like -d=checkptr=0 to debug scheduling behavior.
export GODEBUG=gctrace=1
# Run your program to see GC and preemption traces
your-go-program