Orientation · Lesson 2 of 2
From source code to a running process
What happens between writing code and having it serve a request: compiling, linking, the executable file, how the kernel loads it into a process with its own address space, and how interpreters and JIT compilers change the picture.
Why it matters
"What happens when you run a program?" is the question that ties the whole track together, and it shows up in practice more than people expect. Cold starts, slow first requests after a deploy, a container that will not start because a shared library is missing, a process killed for using too much memory: each is a step in the journey from source to running process going wrong. Knowing the steps lets you place the problem instantly.
The idea
Source code is text. To run, it must become machine instructions, be packaged into a file the operating system understands, and be loaded into a process: a running program with its own memory, registers and resources. There are three main routes from source to instructions.
| Route | When code becomes machine instructions | Examples | Consequence |
|---|---|---|---|
| Ahead-of-time compiled | Before deployment, by a compiler | C, C++, Rust, Go | Fast start, fast steady state, one binary per platform |
| Interpreted | Never fully; an interpreter executes the program line by line or from bytecode | CPython, Ruby (classic) | Simple to ship, slower execution |
| Just-in-time compiled | At runtime, for the hot parts, by the runtime watching what runs often | Java, C#, JavaScript (V8) | Slow at first, very fast once warm |
The compiled path, step by step
- 1
Compile
The compiler turns each source file into an object file of machine instructions, with placeholders for functions and data defined elsewhere. - 2
Link
The linker resolves those placeholders. Static linking copies library code into the binary; dynamic linking leaves references to shared libraries (like libc) to be resolved when the program starts. - 3
Produce an executable
The result is a file in a format the kernel understands (ELF on Linux, Mach-O on macOS, PE on Windows): instructions, initial data, and a header saying where to start. - 4
Start a process
A shell or service manager asks the kernel to create a process (on Unix, fork to copy the current process, then exec to replace it with the new program). The kernel sets up a fresh address space and maps the executable's sections into it. - 5
Load and run
For dynamically linked programs, the dynamic loader maps the shared libraries and fixes up references. Then execution jumps to the entry point, runtime initialisation runs, and finally your main function.
The process's address space
Every process sees its own private range of memory addresses, laid out in regions. The kernel and hardware translate these virtual addresses to physical memory, which the memory management module covers in depth.
Interpreters and JITs
An interpreted or JIT-compiled program is really the interpreter's or virtual machine's process running your code as data. The JVM, for example, starts, loads your classes as bytecode, interprets them, measures which methods are hot, and compiles those to optimised machine code in the background. That is why a Java or .NET service is slower for its first seconds or minutes after a deploy: it is literally still compiling itself. Mitigations are warming the service with synthetic traffic before it takes real load, ahead-of-time compilation (GraalVM native image, ReadyToRun), and class data sharing to speed up startup.
Trade-offs
Ahead-of-time compilation gives fast starts and predictable performance at the cost of per-platform builds. Interpretation gives portability and simplicity at the cost of speed. JIT gives excellent steady-state speed, sometimes beating static compilation because it optimises for the real workload, at the cost of warm-up time and memory. Static linking makes a binary self-contained and container-friendly; dynamic linking shares memory between processes and lets security patches to a library apply without rebuilding.
In an interview
Use it when cold starts or deploys come up: "The service is on the JVM, so new instances are slow until the JIT has compiled the hot paths; we warm each instance with replayed traffic before the load balancer marks it healthy." Or for containers: "We build a statically linked Go binary into a minimal image, so startup is milliseconds and there are no shared libraries to go missing."
Check yourself
After every deploy, a Java service's p99 latency is poor for about two minutes and then settles, with no change in traffic. What is the most likely cause?