all_lessons/SICP JavaScript/01 · Elements of programminglesson 1 / 22

The elements of programming

We start from nothing. The whole 22-lesson arc rests on one sentence — programming is the control of complexity through abstraction — and on one question: what does a powerful language have to give you so you can build abstractions at all? The answer is three things, and only three: primitives (built-in atoms), means of combination (ways to build compound things from simpler ones), and means of abstraction (ways to name a compound thing and use it as a unit). This lesson defines those three, then introduces the substitution model — the first and simplest theory of how a program runs, the one every later evaluation model will be measured against and, eventually, forced to replace.

Book source
Maps to SICP §1.1 (The Elements of Programming) — expressions and evaluation, naming, function application and the substitution model, conditionals, block structure, and the square-root example as procedural abstraction. We follow that arc and its escalation but the explanations, the JavaScript code, and every trace below are original. Where the book uses Scheme we use plain JavaScript.
Linear position
Prerequisite: none — this is the floor of the entire series.
New capability: read any program as expressions evaluated against an environment of name bindings; name the three means every powerful language provides; and trace a function call by hand using the substitution model — the spine's first operational theory of evaluation.
The plan
Six moves. (1) Expressions, values, and the read-eval loop. (2) Names and bindings — the first abstraction. (3) Function definition and application, and the substitution model made precise. (4) Predicates and conditionals, so a process can branch. (5) Block structure — local names hide internal machinery. (6) Newton's square root: a real process sealed behind a clean boundary, and functions as black boxes — a contract, not an implementation. Then we name the pressure that forces lesson 02.

1 · Expressions, values, and the three means

You interact with a language by typing expressions; the interpreter evaluates each one and reports the value it denotes. The simplest expressions are primitive: the numeral 486 evaluates to the number 486; the name Math.PI to a number near 3.14159. You combine expressions with operators, which are themselves a means of combination: 137 + 349 is a compound expression whose subexpressions are evaluated and then combined by +.

137 + 349         // => 486   — combine two primitives
1000 - 334        // => 666
(3 * 5) + (10 - 6) // => 19   — combinations nest arbitrarily

That nesting is the first glimpse of the whole game. Three capabilities recur in every powerful language, and SICP names them once so we can spot them forever after:

primitives
The atoms the language hands you for free — numbers, the arithmetic operators, primitive functions. You do not build these; you build with them.
means of combination
Rules for building compound elements from simpler ones — operator combinations, function application. A combination's parts may themselves be combinations, so structure grows without limit.
means of abstraction
Rules for naming a compound element and then treating it as a single unit — const for values, function for processes. This is how a combination you built becomes a primitive you reuse.

2 · Names and bindings — the first abstraction

The means of abstraction begins with the ability to name things. A constant declaration associates a name with the value of an expression:

const size = 2;
size * 5;          // => 10  — the name stands for its value
const pi = 3.14159;
const radius = 10;
pi * radius * radius;   // => 314.159

The name size is now bound to 2. The interpreter keeps these name→value associations in an environment — for now, think of it as a single global table. To evaluate a name, look it up in the environment and return what it is bound to. Naming is genuine abstraction: pi lets us refer to a computed value without recomputing or even understanding it. We have hidden a detail behind a name — the smallest possible version of the move the entire series is about.

3 · Function definition, application, and the substitution model

Naming a value is the start; naming a process is the leap. A function declaration abstracts a compound operation under a name and gives it parameters:

function square(x) { return x * x; }
function sumOfSquares(a, b) { return square(a) + square(b); }

square(21);            // => 441
sumOfSquares(3, 4);    // => 25

How does sumOfSquares(3, 4) actually produce 25? Our first theory of evaluation — the substitution model — answers this. The rule for applying a function to arguments is:

to apply a function: evaluate the argument expressions, then evaluate the body of the function with each parameter replaced by its argument value.

"Replaced by" is literal: substitute the argument into the body, then keep reducing. Trace it fully — every step is just substitution and primitive arithmetic:

Worked example — substitution trace of sumOfSquares(3, 4)
sumOfSquares(3, 4)
  -- substitute 3 for a, 4 for b in the body:
square(3) + square(4)
  -- apply square to 3: substitute 3 for x in (x * x):
(3 * 3) + square(4)
  -- apply square to 4:
(3 * 3) + (4 * 4)
  -- reduce primitive operators:
9 + 16
25
At no point did we mention memory, frames, or "the state of the machine." A program is a tree of expressions, and evaluation is repeated replacement until only a value remains. This is the substitution model. Remember it precisely — it is the spine's first evaluation model, the one we will reason with for the next eight lessons, and the exact thing assignment will break in lesson 09.

One subtlety the model leaves open: do we evaluate the arguments before substituting (called applicative order — what JavaScript actually does), or substitute the unevaluated argument expressions and only reduce when forced (normal order)? For pure arithmetic the final value is identical, so we defer the distinction. Note that it exists — lesson 16 will deliberately switch orders and watch the meaning of programs change.

4 · Predicates and conditionals — letting a process branch

A process that always does the same thing is weak. Predicates (expressions returning a boolean) plus conditionals let a function choose:

function abs(x) {
  return x >= 0 ? x : -x;        // conditional expression: test ? then : else
}
function clamp(x, lo, hi) {
  if (x < lo) { return lo; }
  else if (x > hi) { return hi; }
  else { return x; }
}

The substitution model extends cleanly: to evaluate a conditional, evaluate the predicate; if it is true, evaluate the consequent, otherwise the alternative. Crucially, only the chosen branch is evaluated — a fact we will lean on when we build evaluators ourselves. Logical operators && and || compose predicates: x > 0 && x < 10.

5 · Block structure — hiding internal names

As functions grow, they need helper functions and intermediate names that callers should never see. A block (a function body) can contain its own local declarations, invisible outside it. This is block structure, and it is the means of abstraction applied to a function's own internals:

function sqrt(x) {
  // helpers and locals live INSIDE sqrt; the outside sees only sqrt(x)
  function isGoodEnough(guess) { return Math.abs(guess * guess - x) < 0.001; }
  function improve(guess)      { return (guess + x / guess) / 2; }
  function iter(guess) {
    return isGoodEnough(guess) ? guess : iter(improve(guess));
  }
  return iter(1.0);
}

Notice x is visible to every inner function without being passed — the inner functions are nested in the scope where x is bound. This is lexical scoping: a name refers to the binding in the textually enclosing block. The substitution model handles it informally (substitute x everywhere it appears), but you should already sense a crack: substitution into nested scopes gets delicate, and lesson 10's environment model exists partly to make scoping exact.

6 · Newton's square root — a process behind a boundary

The sqrt above is a real algorithm: Newton's method of successive approximation. Start with a guess; if the guess squared is close enough to x, accept it; otherwise improve the guess by averaging it with x/guess and repeat. The averaging step provably drives the guess toward the true root.

Worked example — sqrt(2) converging
guess      guess*guess    improve -> (guess + 2/guess)/2
1.0000     1.0000         1.5000
1.5000     2.2500         1.4167
1.4167     2.0069         1.41422
1.41422    2.00000...     good enough -> return 1.41422
Four steps reach 1.41422 (true value 1.41421...). The caller writes sqrt(2) and gets the answer; it never sees iter, improve, or the convergence test.

That last sentence is the whole point and the deepest idea in the lesson. sqrt is a black box: to use it you need to know only its contract — "give me a non-negative number, get back its approximate square root" — not its implementation. An abstraction is a contract, not an implementation. We could swap Newton's method for any other algorithm and no caller would notice or need to change. This procedural abstraction — sealing a process behind a name and a contract — is the lesson's payoff, and the template for every abstraction in the series.

Common mistakes / failure modes

Confusing a name with its value
After const size = 2, the name size is not "the box holding 2"; in this model it simply denotes 2. Treating names as mutable boxes is exactly the mental error lesson 09 will deliberately introduce — and pay for.
Thinking substitution is how the machine works
The substitution model is a theory for humans to predict values, not a description of registers. It is valid precisely while functions are pure; it is a tool, and lesson 09 shows the day it stops being true.
Leaking internals through the contract
If callers start depending on sqrt's guess sequence or its 0.001 tolerance, the black box has sprung a leak and you can no longer change the implementation freely. The contract must be the whole interface.
Ignoring evaluation order
"It's just substitution" hides that JavaScript evaluates arguments first (applicative order). Harmless for pure arithmetic now; lesson 16 shows expressions whose termination depends on the order.

Checkpoint exercise

Try it
Define cube(x) and then sumCubes(a, b, c) using it. Now hand-trace sumCubes(1, 2, 3) with the substitution model, writing every reduction step. Then answer: in sumOfSquares(square(2), square(3)), how many times is square applied, and in what order does JavaScript evaluate the two arguments?
function cube(x) { return x * x * x; }
function sumCubes(a, b, c) { return cube(a) + cube(b) + cube(c); }
Answers: the trace reduces sumCubes(1,2,3)cube(1)+cube(2)+cube(3)(1*1*1)+(2*2*2)+(3*3*3)1+8+2736. In sumOfSquares(square(2), square(3)), square is applied four times. Applicative order evaluates the two argument expressions square(2) then square(3) left-to-right (→ 4, then 9) before entering sumOfSquares, which then applies square to 4 and to 9 — so square runs four times. The point: the model makes the count and the order visible.

Where this points next

We can now name a process and seal it behind a contract — sqrt(x) is a black box. But the instant you can name a process, an unavoidable question appears that the substitution model answers only by accident: what does running it cost? Our two sqrt helpers, recursion in iter, the redundant calls in the checkpoint — these have wildly different time and space behavior, and "it returns the right value" says nothing about whether it returns in a millisecond or a millennium. The substitution trace already hints at it: the shape of the reduction (how long the chain of pending operations grows) is a property of the process, separate from the function's definition. Lesson 02 makes that separation precise — definition versus the process a definition generates — and gives us orders of growth to predict cost. Naming a process forces us to ask its price.

Takeaway
Programming is the control of complexity through abstraction, and every powerful language supplies exactly three means to do it: primitives (atoms like numbers and operators), means of combination (operator and function application, which nest without limit), and means of abstraction (const to name a value, function to name a process). Names are bound to values in an environment; a function declaration abstracts a process under a name, and you predict what a call produces with the substitution model — evaluate the arguments, substitute them into the body, reduce until a value remains. Predicates and conditionals let a process branch; block structure hides internal helper names via lexical scope. Newton's sqrt shows the destination: a real algorithm sealed as a black box whose users depend only on its contract, never its implementation. Hold the substitution model precisely — it is the spine's first evaluation theory, valid only while functions stay pure, and lesson 09 will detonate it on purpose.

Interview prompts