Programming Fundamentals
Programming is the act of giving precise instructions to a machine. Every program, from a one-line script to a distributed system, is built from a small set of fundamental constructs: variables that name values, control flow that directs execution, functions that encapsulate logic, types that constrain data, and error handling that manages the unexpected. This skill catalogs these constructs with emphasis on the mental models that make them learnable and the pitfalls that make them treacherous.
Agent affinity: hopper (practical language implementation, debugging), papert (pedagogical scaffolding, constructionist learning)
Concept IDs: code-variables-data-types, code-control-flow, code-input-output, code-syntax-style
Part 1 -- Variables and Data Types
A variable is a name bound to a value. The binding may be mutable (the name can be rebound to a different value) or immutable (the binding is permanent). The value itself has a type that determines what operations are valid.
Primitive Types
| Type | Examples | Key operations | Gotchas |
|---|---|---|---|
| Integer | 42, -7, 0 | Arithmetic, comparison, bitwise | Overflow (wrapping vs saturating vs panic) |
| Float | 3.14, -0.001, NaN | Arithmetic, comparison | IEEE 754 precision: 0.1 + 0.2 != 0.3 |
| Boolean | true, false | AND, OR, NOT, short-circuit | Truthy/falsy coercion in dynamic languages |
| Character | 'a', '\n', Unicode code point | Comparison, encoding/decoding | Char != string of length 1 in all languages |
| String | "hello", "" | Concatenation, slicing, search | Mutable vs immutable, encoding (UTF-8 vs UTF-16) |
The floating-point trap. IEEE 754 floats represent real numbers in binary. Most decimal fractions (0.1, 0.2, 0.3) have no exact binary representation. Comparing floats with == is almost always wrong. Use an epsilon tolerance: abs(a - b) < epsilon. For money, use integers (cents) or decimal types.
Composite Types
Arrays / Lists. Ordered, indexed collections. Fixed-size arrays (C, Rust) vs dynamic arrays (Python list, JavaScript array, Java ArrayList). Index from 0 in most languages (Lua and MATLAB from 1).
Objects / Records / Structs. Named fields grouping related data. In OOP languages, objects also carry methods. In functional languages, records are plain data without behavior.
Tuples. Fixed-size, heterogeneous, ordered collections. Useful for returning multiple values from a function. Destructuring assignment extracts components.
Maps / Dictionaries. Key-value pairs with O(1) average lookup. Keys must be hashable (immutable in Python). Ordered by insertion in Python 3.7+ and JavaScript, unordered in most other languages.
Scope and Lifetime
Lexical (static) scope. A variable is visible in the block where it is defined and all nested blocks. This is the default in most modern languages. The scope is determined by the program text, not the runtime call chain.
Dynamic scope. A variable is visible to the function that defined it and all functions it calls (transitively). Rare in modern languages. Emacs Lisp uses dynamic scope by default; Common Lisp offers it via special variables.
Block scope vs function scope. JavaScript's var is function-scoped; let and const are block-scoped. This distinction is the source of many bugs involving closures and loops.
Lifetime. How long a value exists in memory. Stack-allocated values live until the enclosing function returns. Heap-allocated values live until freed (manual in C, automatic via garbage collection or ownership in Rust).
The closure capture question. When a closure captures a variable, does it capture the variable itself (by reference) or its current value (by value)? This matters critically in loops. In JavaScript, var in a loop captures by reference (all closures see the same variable); let captures by value per iteration.
Part 2 -- Control Flow
Conditionals
if/else is the fundamental branch. Every programming language has it. The condition must evaluate to a boolean (in statically typed languages) or a truthy/falsy value (in dynamically typed languages).
Switch/match. Multi-way branching. C-style switch requires explicit break (fall-through by default). Modern languages (Rust match, Python match, Kotlin when) do not fall through and support pattern matching.
Pattern matching. Destructures values while testing conditions. Rust's match and Haskell's case are exhaustive -- the compiler ensures all patterns are covered. This eliminates entire classes of bugs.
Ternary operator. condition ? true_value : false_value. Syntactic sugar for a simple if/else that returns a value. Overuse reduces readability.
Loops
for loop. Iterate a known number of times or over a collection. C-style for (init; test; step) vs for-each for (item of collection).
while loop. Iterate while a condition holds. Checked before each iteration. The condition must eventually become false, or the loop never terminates.
do-while. Like while but the body executes at least once. Less common in modern code.
Loop invariants. A property that is true before the loop, maintained by each iteration, and true after the loop. Stating the invariant explicitly is the most reliable way to reason about loop correctness. Dijkstra emphasized this throughout his career.
Infinite loops. while (true) with an internal break. Used in event loops, servers, and REPL implementations. Always ensure there is a reachable exit condition.
The off-by-one problem. The most common loop bug. "Should I use < or <=? Start at 0 or 1?" The fix is to state the loop invariant and verify the boundary conditions: does the first iteration do the right thing? Does the last iteration do the right thing? What happens on empty input?
Iteration vs Recursion
Every loop can be rewritten as recursion and vice versa. Loops are typically more efficient (no call stack overhead) and more readable for sequential traversal. Recursion is more natural for tree structures, mathematical definitions, and divide-and-conquer algorithms.
Part 3 -- Functions
A function takes inputs (parameters), performs computation, and produces an output (return value). Functions are the primary unit of abstraction in programming.
Parameters and Arguments
Positional vs keyword. Positional arguments are matched by order; keyword arguments by name. Python and Ruby support both. Keyword arguments improve readability for functions with many parameters.
Default values. Parameters with defaults can be omitted at call sites. Common pitfall in Python: mutable default arguments are shared across calls (use None as sentinel, create the mutable value inside the function).
Variadic functions. Accept a variable number of arguments. Python *args, JavaScript ...rest, C va_list. Use sparingly -- they make the function signature less informative.
Pass by value vs pass by reference. In pass-by-value, the function receives a copy. In pass-by-reference, it receives an alias to the original. Most languages use pass-by-value for primitives and pass-by-reference (or pass-by-object-reference) for objects. Rust's ownership system makes this explicit: borrow (&T, &mut T) vs move.
Return Values
Every function returns something. Functions that return "nothing" return a unit type (void in C/Java, () in Rust, None in Python). Multiple return values are supported via tuples (Python, Go), destructuring, or out parameters.
Early return. Returning from the middle of a function when a condition is met. Reduces nesting and makes guard clauses possible. Dijkstra disliked it; modern style embraces it for readability.
Closures
A closure is a function that captures variables from its enclosing scope. Closures are the mechanism behind callbacks, event handlers, higher-order function patterns, and module privacy.
Mental model. A closure is a function