This site starts with refactoring on purpose. Before substitutions, before truth tables, before lambda calculus, you take working code and make it clearer without changing what it does. That constraint is the point. Refactoring changes the shape of code while leaving its behavior exactly as it was. It comes first because everything later builds on it: substitution exercises are refactoring on expressions, function exercises are refactoring on compositions, and lambda calculus is refactoring reduced to its barest form.
The contract: same behavior, new shape
Refactoring has one rule, and the site enforces it mechanically. When you submit a refactor, the grader runs your version against the original on a hundred generated inputs. One different result makes it wrong, no matter how much prettier it is. Execution decides correctness. Code appearance does not.
That is the discipline. A refactoring may change structure: how the result is built, what the variables are called, and whether a loop or a function call does the work. It may not change behavior: the same input must produce the same output every time. If tests would catch your change, you are refactoring. If they would not, you are rewriting. That is a different activity with different risks.
This is why the exercises give you a spec and a grader instead of a style guide. A style guide tells you what looks good. The grader tells you what behaves identically. The first is taste. The second is truth. The site grades the second.
The unit of refactoring: the loop
Most refactor exercises start with the same raw material: a loop that builds a result by hand. Consider the first exercise:
function double(a)
{
const o = [];
for (const x of a)
{
o[o.length] = x * 2;
}
return o
}
This works. It is also mostly scaffolding: the empty array o = [], the
index bookkeeping, and the push at the end of each iteration. The actual
transformation is one line: “multiply each element by 2”. The loop machinery
hides it. Replace the recipe with the transformation:
function double(a)
{
return a.map(x => x * 2);
}
The behavior and output are the same for every input. The loop is gone. The
exercise’s framing says it plainly: “a pattern so common it
has a name: map. Watch the empty o = [] and the push disappear into a
single expression.” That is the skill: seeing the named shape inside the
loop.
The big four, and how to spot them
The refactor exercises use a handful of named loop shapes. Learn to recognize the shape, and the rewrite becomes mechanical.
-
map - the loop builds a new array with one output per input, same shape, no elements dropped. One-to-one, order preserved:
map. -
filter - the loop keeps some elements and drops others based on an
ifinside. Theif‘s condition becomes the callback’s predicate:filter. The exercise “Conditionally collect with filter” shows theifinside the loop becoming the predicate, and the pushed array appearing “out of nowhere”. -
some / every / find - the loop carries a boolean flag that flips when something happens, with a
breakto stop early. The flag is really a question: “does any element satisfy this?” (some), “do all of them?” (every), or “which one?” (find). The exercise “A found-flag is really some” showsfound = falseand thebreakvanishing intoa.some(x => x < 0). -
reduce - the loop carries an accumulator that is updated each iteration and returned at the end. The accumulator and the loop body become the reducer:
reduce. Sums, counts, maxima, frequency tables, and grouped buckets are all this shape wearing different costumes.
The name is the insight. A loop tells you how the result is built; the higher-order function tells you what the result is. “The loop builds a new array one element at a time” describes machinery. “This is a map” describes meaning. Look at a loop and name its shape. Then you understand the code, not just its mechanics.
Why the shape is worth having
The refactored version is not merely shorter. It states its intention and composes. A loop that filters and then transforms must nest its machinery:
let o = [];
for (const x of a)
{
if (x % 2 === 0)
{
o[o.length] = x * 2;
}
}
The two higher-order steps read left to right, in the order they happen:
return a.filter(x => x % 2 === 0).map(x => x * 2);
The exercises push exactly this: “Combine: filter then map”, “Chain filter -> map -> reduce”, “Sum of squares: map then reduce”. Each chained step has a name, so you can read the pipeline as a sentence. You can change, remove, or reorder one step without touching the others. In the loop version, the same changes are surgical operations inside nested machinery.
The gate is the point
The refactor grader makes this practice safe. Try a rewrite. If it “behaves differently on input X”, you get the exact input, the expected output, and what your version produced. Then iterate. The counterexample is the lesson. The grader never judges how your code looks; it checks whether behavior survived. That is the precise meaning of refactoring: structure may change freely, behavior must not, and the machine decides whether you kept the promise.
Between translating a loop and writing from a blank spec sits the completion tier: the reference solution with its last steps masked out, which you finish yourself. It is the gentlest bridge from “I can read this” to “I can write it”.
The harder exercises - the from_spec tier - remove the scaffolding entirely: no loop to translate, just a spec and a blank page. Recognizing the shape with no loop in front of you is the point. Once you can do that, you no longer need the machinery to tell you what the code does. You can see the shape directly.
Why this site starts here
Refactoring is the first exercise family for three reasons. First, it is the lowest-risk practice: execution fixes the behavior, so failure is cheap and informative. Second, the vocabulary transfers: the shapes you learn here - map, filter, reduce, and friends - are the same shapes in every functional language, and the reading-elixir-as-a-js-developer article picks up exactly where the refactoring exercises stop. Third, refactoring is the gateway to every other kind of equivalence on the site. Substitution exercises ask whether two expressions mean the same thing. Function exercises ask whether two functions are the same mapping. Lambda calculus reduces expressions while preserving meaning. All of those are refactoring in other costumes: change the shape, keep the meaning, and verify that you did - not by inspection, but by the guarantee the grader, the oracle, or the reduction provides.
Small steps. Identical behavior. Verified by execution. That is the skill the site starts with, and every other exercise family assumes you have it.
Where to go next
-
a monad is a monoid in the category of endofunctors
- refactoring’s abstract cousin: when the “same meaning” you preserved is itself a law, and the shape you kept is called structure.