How Programs Are Built
Every program, in every language, shares a fundamental architecture. Code is composed of expressions that form a tree structure — larger expressions contain smaller ones as sub-clauses. Each expression evaluates to a value, and the computer’s job is to reduce these expressions down to their results, starting from the deepest nesting and working outward.
Expressions are grouped into statements, which are simply expressions whose return value is discarded. Most statements execute sequentially, and the syntax for separating them varies by language: semicolons in JavaScript, newlines in Ruby, indentation in Python, or special forms in Lisp dialects.
This syntax is the first thing every programmer learns, and it’s what translates text into the structured tree the computer executes. The tree is the same regardless of syntax — the surface forms differ but the underlying structure is universal.
Values vs. Identities
A value is a concrete, fixed thing: a specific desk with its particular scratches, a given body at one frozen moment. An identity is a label that refers to values over time: “my desk” might mean one desk today and another tomorrow. Identities are mutable; the values they point to are immutable by definition.
Languages differ in which types are treated as immutable versus mutable. Numbers and most primitive types are never mutable. Strings are immutable in Java but mutable in Ruby. Collections like lists and maps are mutable in most popular languages and immutable in Haskell, Erlang, and Clojure.
This distinction matters because real-world programs need to model change. Identities provide a handle for change — your variable points to a new value when something happens. But mutable things are unreliable by nature: if a value can change, you can’t make guarantees about what you’ll see when you read it later. Immutable values give you certainty, which is why many functional languages emphasize them heavily.
Functions and Calls
Functions take arguments (or parameters) and return a value. Defining a function gives you the potentiality of an operation; calling it with actual arguments produces a result.
(defn fly [bird]
(println "The " bird " is flying!"))
The distinction between a function itself and a call to that function is critical. You can pass functions around as values, store them in variables, and refer to them without executing their bodies. The moment you add arguments in parentheses, you trigger evaluation — the function runs, does its work, and gives you back a value.
Pure vs. Impure Functions
A pure function always returns the same output for the same inputs and has no visible effects on the outside world besides computing its return value. A simple mathematical function like addition qualifies. Because pure functions are deterministic and self-contained, they’re easy to test, easy to reason about, and safe to memoize, reorder, or parallelize.
Impure functions have side effects — they write to files, print to screens, modify shared state, or interact with the outside world. Such functions cannot be skipped or reordered casually, because those observable effects matter. Every program needs impure functions to do meaningful work, but the engineering challenge is controlling them: writing pure code where possible and isolating side effects where they’re unavoidable.
The Type System
Types describe the kind of a value — the taxonomic family it belongs to. Every language has a hierarchy of types and rules for combining them. The number 2 is an integer and therefore a number; an apple is neither. Trying to add an integer to an apple raises a type error.
Languages sit on a spectrum. Statically typed languages require you to declare types upfront, letting the compiler verify program correctness before execution. Dynamically typed languages postpone type checking until evaluation time, giving more flexibility at the cost of runtime errors.
Across languages, common types appear repeatedly:
- Integers — whole numbers like
-1,0,42. - Floats — decimal numbers:
0.5,-1.999. - Strings — sequences of characters:
"hi"or"音韻体系". - Keywords/symbols/atoms — lightweight, identifier-like strings (not the same as reserved words like
iforfunction). - Lists — ordered structures with fast first-element access:
(6, 4, 2). - Arrays/vectors — ordered structures with constant-time access at any position:
[6, 4, 2]. - Maps — key-value dictionaries:
{"cat": "meow"}. Also called hashmaps, associative arrays, or objects. - Functions — first-class values with callable behavior.
Identities themselves also have types — a pointer to a float has a different type than a pointer to a list. Most languages blur this distinction, letting programmers treat variables as if they were the values they hold.
Code Organization
Large systems require decomposition into understandable pieces. The smallest unit is the function, which should do exactly one thing and carry a descriptive name. If a function exceeds thirty lines or so, it’s a signal that it handles multiple responsibilities and should be split.
Above functions, languages offer community conventions. Namespaces — also called modules or packages — group related code and control which external functions are visible. Code within a namespace uses short local names; outside code must import or require it. Namespaces nest hierarchically for larger projects.
Object-oriented languages introduce objects: bundles of data (a map) with associated functions. A class defines the shared shape and methods; individual instances carry different data but share behavior. Classes hook into the type system, so a Rabbit index could inherit from Animal, gaining its methods. How to structure class hierarchies remains a contentious point of design.
Finally, code is distributed as libraries — standalone collections aimed at a particular problem, kept in a namespace, manageable through a package manager. Every language ships a standard library of fundamental features. Frameworks are larger, dictating the skeleton of a program that you fill with your logic. They take opinions about code organization off your hands, but also impose structural constraints — a web framework built for Ruby won’t fit a system designed differently, and vice versa. Selecting the right degree of structure for the problem is part of the design work.
Names, Values, and the Binding Problem
Concrete syntax gets a program only so far. Real problems demand abstraction, and abstraction demands names. In programming, these names are called symbols (or identifiers or variables).
Symbols live on a different level of language than values. Earlier we discussed actual swans and the act of flight — real things. Now we talk about the words themselves: "swan," "fly." Think of symbols as the pronouns of a programming language. Behind the word "she" stands a person — Amelia Earhart, say, or Grace Hopper. Context tells us which. In code, our range of pronouns is effectively infinite, because we generally need to reference many distinct ideas simultaneously.
subtotal = 5.25 + 1.40;
tax = subtotal * 0.07;
total = subtotal + tax;
Here, 5.25 is a literal value — the number 5.25. subtotal is a symbol: a pronoun that stands for the result of 5.25 + 1.40. We can subsequently call on subtotal as a shorthand for that computation. tax and total are symbols too. Choosing descriptive names like these makes the code legible.
Different languages treat the relationship между symbols and values differently. In Clojure, Erlang, and Haskell, a symbol refers directly to a value, and that value never changes — the symbol is immutable. In Ruby, JavaScript, and C, a symbol refers to an identity that points to a value. That identity can be reassigned to a new value later, making it mutable.
A symbol with no value attached is unbound — it holds the potential for a value. Once a value is attached, the symbol is bound. Functions are built on this concept.
function add(a, b) {
return a + b;
}
Inside the function body, a and b are unbound symbols. They have no concrete values yet, and that’s fine because the computer isn’t evaluating the body right now. When we call add(3, 5), those symbols become bound — a to 3 and b to 5 — and the expression a + b is evaluated with those bindings in place.
Scoping Rules
English solves ambiguity in pronouns by convention: "he" usually refers to the most recently mentioned male. But if a sentence starts with "he devoured the mouse whole," no reader can identify the subject. In code, the region of text where a symbol remains bound is called its scope.
A symbol bound everywhere is in global scope. Think of capitalized "He" or "She" in scripture: regardless of location in the text, the reference is unambiguous. We could call that a global variable.
Most modern languages additionally employ lexical scope: a symbol is bound only within a particular expression.
function add(a, b) {
// a and b are bound in this function expression
if (a > 2) {
// And also in nested expressions
return function() {
// For instance, this new function
return a + b;
}
}
}
// However, a and b are *not* bound here!
a + b; // Wrong!
Lexical scope applies to code as written, not as executed. Consider this:
function trouble() {
// x isn't bound here
return x + x; // Wrong!
}
function double(x) {
// x is bound here
return trouble();
}
double(2);
Within double, x is bound to 2. double invokes trouble, but trouble is defined outside the scope of x — so x is unbound inside it. The program fails.
Dynamic scope would make this program work. Under dynamic scope, a symbol stays bound not only through the rest of an expression, but also through any function calls that expression makes. The downside: you often can’t tell where a variable came from by reading the code, making reasoning much harder. Dynamic scope therefore appears sparingly.
Object-oriented languages introduce instance variables and class variables — symbols available within a given instance or shared across the class. You can view a class definition as a class expression wrapping an instance expression, in which case these are lexically scoped. Syntactically, most OO languages don’t present them that way.
Wrapping Up
This overview isn’t a tutorial for any single language; it’s a map of the underlying mechanics. When you pick up a new language, ask how it constructs expressions and combines them. What basic types exist, and how do they interact? How are functions declared and grouped? Are symbols references to values or identities? What scoping rules apply?
If these concepts haven’t fully clicked yet, don’t worry. They solidify with practice: copy small snippets, experiment with variations, and gradually write programs from scratch. The ideas will settle.



