This question tests whether you can trace a thread from a language call to the scheduler — and whether you know where the real costs are. The interviewer wants the syscall boundary, the stack, and the scheduler; the trap is thinking threads are cheap.
The walkthrough. When you call thread::spawn or new Thread(...):
- The runtime asks the OS via a syscall (
cloneon Linux,pthread_createunderneath) to create a kernel-visible execution entity. This is a transition from user mode to kernel mode, not a tiny operation: the kernel allocates a task structure, assigns a thread ID, and inserts the thread into the run queue. - A stack is allocated. A new thread needs its own stack — on Linux, typically 8 MB of virtual address space (RSS grows on touch). The stack is mapped with guard pages at the bottom so overflow faults cleanly.
- Thread state is initialized: registers, instruction pointer pointing at the thread entry function, thread-local storage block (TLS), signal mask, and the thread's scheduling properties (policy, priority, cgroup/CPU affinity).
- The thread becomes runnable. From here the scheduler decides when it actually runs — it may sit in the run queue while the current CPU is busy. The spawn call returns before the new thread necessarily executes anything.
The real costs — this is the part people miss:
- Creation is expensive: tens of microseconds to ~milliseconds depending on OS and allocator state, because it involves syscalls, kernel structures, and an mmap for the stack.
- Memory commitment: each thread reserves stack and TLS; thousands of threads reserve gigabytes of virtual memory, which can exhaust address space even when physical usage is low — and every touched page becomes RSS.
- Context switches: switching threads means a kernel mode round trip plus TLB flushes and cache misses — the threads' shared L1/L2 state doesn't transfer.
- Lock and cache contention: threads share the heap, so allocating concurrently means atomic operations, and false sharing of adjacent fields in one cache line becomes a real slowdown.
Tradeoffs and edge cases worth naming:
- User threads (goroutines, Java virtual threads) sidestep this: the runtime multiplexes many cheap logical threads onto a few kernel threads, so spawn is a heap allocation plus a queue push, not a syscall — but any blocking syscall in a user thread can block its carrier kernel thread.
- Thread pools exist because spawn/teardown is expensive; you reuse carriers.
A strong closing: "Creating a thread is a syscall, a stack, and a scheduler entry — not a heap object. It's expensive enough that pools exist, and cheap enough that a well-designed pool is the answer to 'how many threads'."