JavaScript • Software Craft and Communication

Refactoring — Refactor to reduce duplication and improve clarity

Learn to reshape working code so it becomes easier to read, maintain, and extend without changing what it does.

JavaScript emblem with code brackets, braces, and a code file.
Inside this lesson 10 sections 5 questions 5 figures

SECTION 1

Historical Context & Motivation

Every programmer eventually faces the same frustrating moment: you look at code you wrote last week, and you can barely understand it. Worse, you notice you copied and pasted the same block of logic in three different places. When early software teams in the 1970s and 1980s started building larger programs, this kind of mess became a serious obstacle. Projects slowed down, bugs multiplied, and developers spent more time reading old code than writing new features.

The idea of refactoring — restructuring existing code without changing its external behavior — grew out of that frustration. Pioneering computer scientists realized that code quality is not just about getting the right answer once; it is about making sure the code stays understandable and changeable over time. The timeline below traces how refactoring went from an informal habit to a recognized discipline.

1984 • Structured Programming Takes Hold

Languages like C and Pascal encourage breaking programs into small functions, planting the seed for eliminating duplicated logic.

1992 • Ward Cunningham & the Wiki Way

Smalltalk developers like Ward Cunningham and Kent Beck begin sharing techniques for cleaning up code in small, safe steps, coining the term 'refactoring.'

1999 • Martin Fowler Publishes 'Refactoring'

Fowler's book catalogs dozens of named refactoring moves — like Extract Function and Rename Variable — turning informal cleanup into a repeatable craft.

2015 • Modern JavaScript & ES6

Arrow functions, template literals, destructuring, and modules give JavaScript developers powerful new tools for writing cleaner, less duplicated code.

2020s • AI-Assisted Refactoring

IDEs and AI tools now suggest refactoring moves automatically, but understanding the 'why' behind each change remains a uniquely human skill.

The central question refactoring answers is straightforward: how do you improve the internal structure of code that already works, so it stays correct while becoming easier to read and change? That is exactly what this lesson will teach you to do.

SECTION 2

Core Principles of Refactoring

Before you start rearranging code, you need a mental framework. Refactoring rests on a handful of guiding ideas that keep you safe and productive. These principles apply whether you are cleaning up a ten-line script or a thousand-line project.

1

Behavior Stays the Same

Refactoring changes how the code is organized, never what it produces. If a function returned 42 before, it still returns 42 after.
2

DRY — Don't Repeat Yourself

Every piece of knowledge should live in exactly one place. Duplicated code means duplicated bugs and duplicated maintenance.
3

Small, Safe Steps

Make one tiny change, then test. This way you always know exactly which edit introduced a problem if something breaks.
4

Meaningful Names

Variables and functions should say what they mean. Renaming x to studentAge is a refactoring move that costs nothing and pays back every time someone reads the code.
5

Single Responsibility

Each function should do one thing well. If you struggle to name a function, it is probably doing too much.
✦

KEY TAKEAWAY

Think of refactoring like reorganizing your locker. Everything you own stays the same — same books, same supplies — but after reorganizing, you can find what you need in seconds instead of digging through a pile. Refactoring reorganizes your code's 'locker' so future-you (and your teammates) can work faster.

SECTION 3

Visualizing Duplication and Extraction

The diagram below shows the most common refactoring move you will use: Extract Function. On the left, three different places in a program contain the same duplicated logic (shown in matching colors). On the right, that logic has been pulled into a single shared function, and each original location now calls that function instead. Notice how the total amount of unique logic shrinks dramatically.

BEFORE (Duplicated)AFTER (Extracted)function showUserCard() {const name = formatName(u);const label = name + ' - ' + u.role;console.log(label);}function showAdminCard() {const name = formatName(a);const label = name + ' - ' + a.role;console.log(label);}function showGuestCard() {const name = formatName(g);const label = name + ' - ' + g.role;console.log(label);}9 duplicated lines across 3 functionsfunction printLabel(person) {const name = formatName(person);const label = name+' - '+person.role;console.log(label);}function showUserCard() {printLabel(u);}function showAdminCard() {printLabel(a);}function showGuestCard() {printLabel(g);}Logic lives in 1 place; 3 call sites= duplicated / shared logic= single-line call
Left: three functions each contain identical logic (pink). Right: the shared logic is extracted into printLabel() (pink border), and each original function now contains a single call (cyan).

In the "before" column, every function repeats the same three lines that build and print a label. If you later need to change the label format — say, adding an email address — you would have to edit all three places and hope you do not miss one. In the "after" column, you edit the single printLabel function and the change automatically applies everywhere. This is the DRY principle in action: one change, one place, zero risk of inconsistency.

SECTION 4

How Refactoring Works Step by Step

Refactoring is not a wild rewrite. It is a disciplined sequence of micro-changes. Each change is small enough that you can verify correctness immediately. Below is the general recipe for the Extract Function refactoring, the most fundamental move in the catalog.

  1. Identify the duplication. Look for blocks of code that appear in more than one place, or a single block that does too many things.
  2. Create a new function with a descriptive name. The name should explain what the block does, not how it does it.
  3. Move the duplicated code into the new function. Identify which values the code needs (these become parameters) and which value it produces (this becomes the return value).
  4. Replace every original occurrence with a call to the new function. Pass the appropriate arguments.
  5. Test. Run the program or your test suite to confirm the output has not changed.

Beyond Extract Function, several other common refactoring moves deserve attention. Rename Variable swaps a vague name like d for a clear one like daysSinceLastLogin. Inline Variable removes a variable that adds no clarity — if const temp = price * quantity; is used exactly once, you can replace temp with the expression itself. Replace Magic Number with Named Constant turns a mysterious 0.0825 into TAX_RATE so readers instantly know its purpose.

⚠

The Golden Rule of Refactoring

If you can't easily test that your code still works after a change, the change was too big. Break it into smaller steps.
Refactoring Flowchart: The Safe-Change Cycle1. Spot a Code Smell2. Pick a Refactoring Move3. Make One Small Change4. Test / VerifyPASS →continueFAIL → Undo change, try smaller step
The safe-change cycle: spot a smell, choose a move, make a small edit, and test. If the test passes, loop back for another move. If it fails, undo and break the change into something smaller.

SECTION 5

Recognizing Code Smells

Programmers use the term code smell to describe a surface-level indicator that something deeper might be wrong. A code smell is not a bug — your program still works — but it signals that the code could be clearer or more maintainable. Learning to recognize smells is the first step in every refactoring session.

Code SmellWhat It Looks LikeRefactoring Fix
Duplicated CodeThe same (or nearly the same) block appears in multiple places.Extract Function — move shared logic into one reusable function.
Magic NumbersLiteral values like 3.14 or 60 sit inside expressions with no explanation.Replace Magic Number with Named Constant.
Long FunctionA function stretches across many lines and does several unrelated tasks.Extract Function — split into smaller, focused helpers.
Unclear NamingVariables or functions have names like x, temp, or doStuff.Rename Variable / Rename Function.
Dead CodeLines that are never executed or variables that are never read.Delete the unreachable code safely.

Common code smells and the refactoring moves that address them.

💡

Pro Tip

A quick test for duplication: select a block of code and use your editor's search feature. If the same logic appears elsewhere, you have found a candidate for Extract Function.

SECTION 6

Worked Example — Refactoring a Grade Calculator

Imagine you have a JavaScript program that calculates letter grades for three different classes. The original code repeats the grading logic inside each function. We will walk through refactoring it step by step.

Original Code (Before Refactoring)

Here is the messy starting point. Notice that the grading if/else block is copied three times:

function getMathGrade(score) { if (score >= 90) return 'A'; else if (score >= 80) return 'B'; else if (score >= 70) return 'C'; else return 'F'; } function getScienceGrade(score) { if (score >= 90) return 'A'; else if (score >= 80) return 'B'; else if (score >= 70) return 'C'; else return 'F'; } function getHistoryGrade(score) { if (score >= 90) return 'A'; else if (score >= 80) return 'B'; else if (score >= 70) return 'C'; else return 'F'; }

Refactoring the Grade Calculator

  1. 1

    Step 1 — Identify the Duplication

    All three functions contain the exact same if/else chain. The only difference is the function name — the parameter score and the return values are identical.
    Smell detected: Duplicated Code (×3)
  2. 2

    Step 2 — Create a Shared Function

    We create a single function called getLetterGrade that accepts a numeric score and returns the corresponding letter. The name clearly describes what the function does.
    function getLetterGrade(score) { if (score >= 90) return 'A'; if (score >= 80) return 'B'; if (score >= 70) return 'C'; return 'F'; }
  3. 3

    Step 3 — Replace Original Calls

    Every place that previously held the duplicated logic now simply calls getLetterGrade. If we still want subject-specific wrappers (for example, to add logging), they become one-liners.
    function getMathGrade(score) { return getLetterGrade(score); } function getScienceGrade(score) { return getLetterGrade(score); } function getHistoryGrade(score) { return getLetterGrade(score); }
  4. 4

    Step 4 — Test

    We run the same test inputs as before. getMathGrade(92) still returns 'A'. getScienceGrade(74) still returns 'C'. Nothing has changed in behavior — only in structure.
    All tests pass ✓ — Refactoring complete.
  5. 5

    Step 5 — Bonus: Remove Unnecessary Wrappers

    Since the three wrapper functions add no unique logic, we can go further and simply call getLetterGrade directly wherever we need a grade. This reduces the codebase from ~18 lines to ~5 lines.
    Final version: 1 function, 0 duplication, maximum clarity.

SECTION 7

Strengths & Limitations of Refactoring

Refactoring is powerful, but like any technique, it comes with trade-offs. Understanding both sides helps you decide when to refactor and when to leave working code alone.

StrengthsLimitations
Reduces bugs by eliminating duplicated logic — fix once, fixed everywhere.Takes time upfront that could be spent adding new features.
Makes code easier for teammates (and future-you) to read and understand.Without tests, you risk accidentally changing behavior.
Makes future changes faster because logic lives in well-named, single-purpose functions.Over-refactoring can create too many tiny functions, making the code harder to follow.
Builds your skill at recognizing patterns and thinking about code architecture.Renaming variables or restructuring can cause merge conflicts when working on a team.

Balancing the benefits and risks of refactoring.

✦

KEY TAKEAWAY

Think of refactoring like tuning a car engine. A well-tuned engine does not go to a new destination — it gets you to the same destination more smoothly and efficiently. But if you overtighten a bolt, you can create new problems. Refactor in small, tested steps and stop when the code reads clearly.

SECTION 8

Connection to Advanced Software Design

The refactoring moves you have learned here — Extract Function, Rename Variable, Replace Magic Number — are the foundational vocabulary of a much larger discipline called software design. As you advance, you will encounter more sophisticated patterns that build directly on these basics.

What You Learned TodayWhere It Leads
Extract Function to remove duplicationDesign Patterns — reusable solutions to common coding problems (Strategy, Factory, Observer).
Meaningful namingClean Code principles — entire books and courses on writing expressive, self-documenting code.
Single Responsibility per functionSOLID Principles — five design guidelines used in professional software engineering.
Test after every changeTest-Driven Development (TDD) — writing tests first, then writing code to pass them.

How today's refactoring skills connect to professional software practices.

Professional developers refactor constantly — not as a separate task, but as part of their daily workflow. Every time you add a feature, you clean up the code around it. This habit, sometimes called the Boy Scout Rule ("leave the code cleaner than you found it"), is what separates good codebases from unmaintainable ones. The skills you are building now are the same ones used at companies like Google, Microsoft, and every open-source project on GitHub.

SECTION 9

Practice Problems

Question 1: CONCEPTUAL

In your own words, explain what it means to say that refactoring does not change a program's behavior. Why is this distinction important?
Show answer
Refactoring restructures the internal organization of code — how functions are named, where logic lives, how variables are labeled — while keeping every input-output relationship exactly the same. If a function returned 'A' for a score of 95 before refactoring, it must still return 'A' for 95 afterward. This distinction matters because it allows you to improve code quality without the risk of introducing new bugs. You can confidently refactor knowing that if your tests still pass, nothing is broken.

Question 2: BASIC CALCULATION

Consider these two functions. Identify the duplicated code and write a single shared function that eliminates the duplication. function areaOfSquare(side) { const result = side * side; console.log('Area: ' + result); return result; } function areaOfCircle(radius) { const result = Math.PI * radius * radius; console.log('Area: ' + result); return result; }
Show answer
The duplicated part is the console.log('Area: ' + result) pattern that appears in both functions. We can extract a helper called logAndReturn: function logAndReturn(value) { console.log('Area: ' + value); return value; } function areaOfSquare(side) { return logAndReturn(side * side); } function areaOfCircle(radius) { return logAndReturn(Math.PI * radius * radius); } Now the logging logic lives in one place. If we later want to change the message format, we edit only logAndReturn.

Question 3: INTERMEDIATE

Refactor the following code to eliminate the magic numbers and improve variable names. Explain each change you make. function calc(p, q) { let t = p * q; let t2 = t * 0.0825; let t3 = t + t2; if (t3 > 100) { t3 = t3 * 0.9; } return t3; }
Show answer
This function calculates a total with tax and applies a discount for orders over $100. Here is the refactored version: const TAX_RATE = 0.0825; const DISCOUNT_THRESHOLD = 100; const DISCOUNT_MULTIPLIER = 0.9; function calculateTotal(price, quantity) { const subtotal = price * quantity; const tax = subtotal * TAX_RATE; let total = subtotal + tax; if (total > DISCOUNT_THRESHOLD) { total = total * DISCOUNT_MULTIPLIER; } return total; } We made four changes: (1) renamed calc to calculateTotal and parameters to price/quantity; (2) replaced t, t2, t3 with subtotal, tax, total; (3) extracted magic numbers into named constants; (4) the behavior is identical but the code now documents itself.

Question 4: APPLIED

You are building a student dashboard. Three different parts of the app display a student's full name by concatenating firstName, a space, and lastName. Later, your teacher asks you to display names in "Last, First" format instead. Explain how having a single formatName function (instead of duplicated code) makes this change safer and faster.
Show answer
If the name-formatting logic is duplicated in three places, you need to find and update each one, and you risk missing one or introducing a typo in one copy. With a single formatName(student) function, you change the template from student.firstName + ' ' + student.lastName to student.lastName + ', ' + student.firstName in exactly one place. All three parts of the dashboard instantly show the new format because they all call the same function. This is the DRY principle at work — a single source of truth means a single point of change and zero risk of inconsistency.

Question 5: CRITICAL THINKING

A classmate argues: "I should refactor my code to be as short as possible — fewer lines always means better code." Do you agree or disagree? Write a paragraph explaining your reasoning and provide a code example where making the code shorter actually makes it harder to understand.
Show answer
You should respectfully disagree. The goal of refactoring is clarity, not brevity. Sometimes shorter code is clearer, but not always. For example, compare const canVote = age >= 18; (clear and concise) with const r = a>=18?b.filter(x=>x.s>90).map(x=>x.n).join(','):'N/A'; — this one-liner packs too many operations into a single expression with cryptic variable names. Splitting it into multiple lines with descriptive names would be longer but far more readable. A good rule of thumb: refactor until adding or removing another line would make the code harder to understand. The target is maximum clarity, which sometimes requires a few extra lines for well-named intermediate variables.

SUMMARY

Lesson Summary

Summary

Refactoring is the practice of restructuring existing code without changing its external behavior. The core motivation is to eliminate duplication and improve clarity so that code is easier to read, maintain, and extend. You learned to spot code smells — surface indicators like duplicated logic, magic numbers, long functions, and unclear names — and to address them with targeted moves such as Extract Function, Rename Variable, and Replace Magic Number with Named Constant.

The key discipline is to work in small, safe steps — make one change, test, repeat — following the DRY principle (Don't Repeat Yourself) and the Single Responsibility principle. These foundational skills connect directly to professional practices like design patterns, SOLID principles, and test-driven development. Remember: the goal is not the shortest code, but the clearest code.

Varsity Tutors • JavaScript • Refactoring — Refactor to reduce duplication and improve clarity