A naive approach to optimization would operate instruction-by-instruction across the whole program. Why do compiler writers instead build a CFG and work with basic blocks as the unit of analysis?
Think about your answer, then reveal below.
Model answer: Within a basic block, control flow is linear and predictable: every instruction executes in sequence whenever any instruction in the block executes. This lets optimizations like constant propagation, common subexpression elimination, and liveness analysis make guarantees that would require tracking branching for individual instructions. The CFG then lifts these per-block results to the whole program, connecting blocks via edges so inter-block (global) analyses like reaching definitions and dominator trees can be computed with standard graph algorithms.
Basic blocks partition the program into chunks where local reasoning is safe, and the CFG provides the graph structure for global reasoning. This two-level decomposition — local optimization within blocks, global analysis on the CFG — is the core architecture of almost every optimizing compiler, from GCC to LLVM. The alternative (working instruction-by-instruction across arbitrary control flow) would require re-analyzing every possible execution path at every step.