The full picture
- Why 1:1 won for 20 years: simplicity and honesty — the kernel schedules everything, blocking just works, debuggers/profilers see reality. Java dropped its original green threads for exactly this.
- What changed: Go and the JDK rewrote their I/O layers so 'blocking' calls park the user-level thread and release the carrier (Java: unmount + continuation to heap; JEP 444). The N:M scheduler lives in the runtime (ForkJoin carriers ≈ cores).
- Remaining leaks in the abstraction: syscalls the runtime can't intercept (some file ops, JNI/FFI) still pin/consume a carrier — Go spawns extra Ms, Java compensates the FJ pool; and CPU-bound user threads don't preempt as fairly as the kernel would.
- Interview framing: thread-per-request coding model at event-loop scalability — the runtime moved the async complexity below the programming model instead of into your code (contrast callbacks/reactive).