Computer Programming 1 (JavaScript) • Core Data Structures

Transforming & Filtering Arrays — I can transform/filter an array using loop-based patterns (build a new array) at my level.

Learn to build new arrays by looping through existing ones—transforming and filtering data one element at a time.

A JavaScript data file with code brackets and a laptop.
Inside this lesson 10 sections 5 questions 8 figures

SECTION 1

Historical Context & Motivation

Every app you use daily—from social media feeds to music playlists—works by taking large collections of data and reshaping them for display. A photo app might shrink all your images to thumbnails; a search engine filters millions of results to show only the relevant ones. At the core of these operations is a simple idea: loop through a list, and build a new list that contains only the items (or transformed versions of items) you actually need.

This pattern is one of the oldest and most universal ideas in programming. Long before JavaScript existed, programmers in languages like FORTRAN and LISP were iterating over arrays and constructing new ones. The pattern has survived because it solves a problem that never goes away: raw data rarely looks the way we want it to, so we must transform values into a useful form and filter out the noise.

1957 • FORTRAN & DO Loops

IBM's FORTRAN introduced the DO loop, giving programmers a structured way to iterate over arrays of numbers for scientific computation.

1958 • LISP & List Processing

John McCarthy created LISP, a language built around transforming and filtering lists—the intellectual ancestor of JavaScript's array methods.

1995 • JavaScript Is Born

Brendan Eich designed JavaScript with C-style for loops, making array iteration accessible in web browsers for the first time.

2009 • ES5 Adds forEach, map, filter

ECMAScript 5 introduced built-in higher-order methods, but understanding the underlying loop pattern remains essential for every developer.

The central question this lesson addresses is straightforward: given an existing array, how do you use a loop to produce a brand-new array that contains either modified versions of the original elements (transformation) or only those elements that meet a specific condition (filtering)? Mastering this loop-based pattern gives you the foundation for every data-processing task you will encounter.

SECTION 2

Core Principles & Definitions

Before diving into code, let's nail down the vocabulary you'll use throughout this lesson and beyond. Two operations sit at the heart of array processing: transforming (changing every element in some way) and filtering (keeping only the elements that pass a test). Both operations share a common structure: start with an empty result array, loop through the source, and push items into the result.

1

Source Array

The original array you read from. It is never modified—you treat it as read-only so the original data stays intact.
2

Result Array

A new, initially empty array that you build up inside the loop using push(). When the loop ends, this array holds your output.
3

Transform Pattern

Loop through every element, apply a change (e.g., multiply by 2, convert to uppercase), and push the changed value into the result array.
4

Filter Pattern

Loop through every element, check a condition with an if statement, and push only the elements that pass the test.
5

Immutability Mindset

Building a new array instead of changing the original is safer and more predictable. Other parts of your program that reference the source array are unaffected.
✦

KEY TAKEAWAY

Think of a conveyor belt at a factory. The source array is the raw material rolling along the belt. A transform is a machine that reshapes every item (like stamping each piece of metal into a gear). A filter is a quality inspector who only lets the good parts through. In both cases, the conveyor belt (source) keeps moving untouched—you collect the output in a separate bin (result array).

SECTION 3

Visual Explanation — The Loop-Based Build Pattern

The following diagram shows how the transform pattern works step by step. The source array [2, 5, 8] is processed by a for loop that doubles each value and pushes it into a new result array. Follow the numbered steps from left to right to see how the result array grows with each iteration.

Transform Pattern: Double Every ValueSOURCE ARRAY258LOOP ITERATIONSIteration 0 (i = 0)2 × 2 → push(4)Iteration 1 (i = 1)5 × 2 → push(10)Iteration 2 (i = 2)8 × 2 → push(16)RESULT ARRAY GROWS[4][4, 10][4, 10, 16]The source array [2, 5, 8] is unchanged — the result is a brand-new array.
Each iteration reads one element from the source, doubles it, and pushes the result into a new array. After all three iterations, the result array [4, 10, 16] is complete, while the source [2, 5, 8] remains untouched.

Notice the three-part structure that stays the same every time you use this pattern. First, you declare an empty result array before the loop. Second, inside the loop body you compute a new value (or check a condition). Third, you push() the new value into the result. This three-step rhythm is the backbone of both transforming and filtering.

SECTION 4

How It Works — Code Patterns in Detail

The Transform Pattern

The transform pattern visits every element in the source array, applies some operation, and pushes the modified value into the result. Here is the general template:

TRANSFORM TEMPLATE

constresult=[];for(leti=0;i<source.length;i++)constnewValue=transform(source[i]);result.push(newValue);const result = []; for (let i = 0; i < source.length; i++) { const newValue = transform(source[i]); result.push(newValue); }

source — the original array. transform() — any expression that converts the current element to its new form. result — the new array that accumulates transformed values.

The Filter Pattern

The filter pattern also visits every element, but instead of transforming it, the loop checks a boolean condition. Only elements that satisfy the condition are pushed into the result.

FILTER TEMPLATE

constresult=[];for(leti=0;i<source.length;i++)if(condition(source[i]))result.push(source[i]);const result = []; for (let i = 0; i < source.length; i++) { if (condition(source[i])) { result.push(source[i]); } }

condition() — a boolean expression (e.g., source[i] > 10). Only elements where this is true make it into the result.

Combining Transform + Filter

You can combine both patterns in a single loop. For example, you might want to double only the even numbers in an array. In that case, you place the if check first, and inside the if block you transform and push. This shows the real power of understanding the loop-based approach: you can mix and match however your problem demands.

COMBINED TEMPLATE

const result = []; for (let i = 0; i < source.length; i++) { if (source[i] % 2 === 0) { result.push(source[i] * 2); } }

This loop filters for even numbers (% 2 === 0) and doubles them in one pass. The result contains only the doubled even values.

SECTION 5

Detailed Breakdown — Transform vs. Filter Side by Side

The diagram below places the transform and filter patterns side by side so you can see exactly where they differ. Both start with the same source array [3, 7, 12, 4, 18]. The left branch transforms every element (adds 10), while the right branch filters for elements greater than 5. Pay attention to the result array sizes—transforms always produce the same number of elements, while filters may produce fewer.

Transform vs. Filter — Same Source, Different ResultsSOURCE ARRAY[3, 7, 12, 4, 18]TRANSFORM (add 10)i=0: 3 + 10 → push(13)i=1: 7 + 10 → push(17)i=2: 12 + 10 → push(22)i=3: 4 + 10 → push(14)i=4: 18 + 10 → push(28)RESULT[13, 17, 22, 14, 28]5 in → 5 outFILTER (keep if > 5)i=0: 3 > 5? NO — skipi=1: 7 > 5? YES → push(7)i=2: 12 > 5? YES → push(12)i=3: 4 > 5? NO — skipi=4: 18 > 5? YES → push(18)RESULT[7, 12, 18]5 in → 3 outTransforms keep the same length; filters may shrink the array.
The transform branch (left, pink) always produces a result with the same number of elements as the source. The filter branch (right, cyan) may produce fewer elements because items that fail the condition are skipped.
FeatureTransformFilter
PurposeChange every elementKeep only elements that pass a test
Result lengthSame as source≤ source length
Uses if statement?No (every element is pushed)Yes (only matching elements)
Element valuesDifferent from sourceIdentical to source

Key differences between transform and filter patterns

SECTION 6

Worked Example — Building a Filtered & Transformed Array

Suppose you have an array of student test scores and you need to produce a new array that contains only the passing scores (70 or above), each converted to a letter grade. This combines both filtering and transforming in a single loop.

Filtering Passing Scores & Converting to Letter Grades

  1. 1

    Step 1 — Identify the Source Array

    Start with the scores array: const scores = [88, 42, 95, 67, 73, 58, 100]; This is our source—we will not change it.
  2. 2

    Step 2 — Create an Empty Result Array

    Declare the result: const passingGrades = []; This starts empty and will grow inside the loop.
  3. 3

    Step 3 — Write the Loop with Filter + Transform

    Loop through every score. Inside the loop, check if the score is ≥ 70 (filter). If it passes, convert the number to a letter grade (transform) and push the grade into the result. for (let i = 0; i < scores.length; i++) { if (scores[i] >= 70) { let grade; if (scores[i] >= 90) { grade = 'A'; } else if (scores[i] >= 80) { grade = 'B'; } else { grade = 'C'; } passingGrades.push(grade); } }
  4. 4

    Step 4 — Trace Through Each Iteration

    i=0: 88 ≥ 70? Yes → 88 ≥ 90? No → 88 ≥ 80? Yes → push 'B'. i=1: 42 ≥ 70? No → skip. i=2: 95 ≥ 70? Yes → 95 ≥ 90? Yes → push 'A'. i=3: 67 ≥ 70? No → skip. i=4: 73 ≥ 70? Yes → 73 ≥ 90? No → 73 ≥ 80? No → push 'C'. i=5: 58 ≥ 70? No → skip. i=6: 100 ≥ 70? Yes → 100 ≥ 90? Yes → push 'A'.
  5. 5

    Step 5 — Read the Result

    After the loop completes, passingGrades contains the final result.
    ["B", "A", "C", "A"] — four passing grades from seven original scores. The source array scores is unchanged.
⚠

Common Mistake

Forgetting to create the empty result array before the loop is the most common error. If you write const result = []; inside the loop, the array resets to empty on every iteration and you'll end up with only the last item!

SECTION 7

Strengths, Limitations & When to Use Each Pattern

The loop-based build pattern is extremely flexible, but like any tool it comes with trade-offs. Understanding these will help you decide when to use a manual loop and when a built-in method might be more appropriate.

AspectStrengthLimitation
FlexibilityYou can combine filter + transform + any custom logic in a single loopMore lines of code than built-in methods like .map() or .filter()
ReadabilityEasy to trace step by step; great for learningCan become lengthy for simple operations
PerformanceSlightly faster than chaining multiple built-in methodsPerformance difference is negligible for most high school projects
SafetyBuilds a new array—original data stays intactIf you accidentally modify source[i] (an object), the original mutates too
DebuggingYou can add console.log inside the loop to inspect each stepOff-by-one errors (wrong loop bounds) are a common pitfall

Strengths and limitations of the loop-based build pattern

✦

KEY TAKEAWAY

Think of the loop-based pattern as driving a manual-transmission car: you control every gear shift, which means more work but also more control. Built-in methods like .map() and .filter() are like an automatic transmission—convenient, but they only do what they're designed for. Learning the manual approach first ensures you truly understand what's happening under the hood.

SECTION 8

Connection to Built-In Methods & Advanced Patterns

Once you're confident with loop-based patterns, you'll be ready to use JavaScript's higher-order array methods. These methods do the same work as the patterns you've learned, but they express it more concisely. The table below shows the direct relationship between each loop pattern and its built-in counterpart.

Loop-Based PatternBuilt-In MethodWhat It Does
Transform with for loop.map(callback)Returns a new array with each element transformed by the callback
Filter with for loop + if.filter(callback)Returns a new array with only elements where callback returns true
Accumulate to single value.reduce(callback, init)Reduces the array to a single value (sum, count, object, etc.)
Check every element.every(callback)Returns true only if every element passes the test

Loop patterns map directly to built-in methods

When you eventually write const doubled = nums.map(n => n * 2);, you'll understand that JavaScript is running a loop behind the scenes, creating an empty array, pushing transformed values, and returning the result—exactly the pattern you've practiced in this lesson. That deep understanding is what separates a programmer who uses tools from one who truly understands them.

SECTION 9

Practice Problems

Question 1: CONCEPTUAL

Explain in your own words why we create an empty result array before the loop, rather than inside it. What would happen if you placed const result = []; inside the loop body?
Show answer
The result array must be declared before the loop so it persists across all iterations. Each pass of the loop adds one more item to the same array. If you placed const result = []; inside the loop, the array would be reset to empty at the start of every iteration. After the loop finishes, the result would contain only the value from the very last iteration—everything else would have been discarded. This is one of the most common beginner bugs when using the build-a-new-array pattern.

Question 2: BASIC CALCULATION

Given const temps = [32, 68, 95, 50, 77]; (Fahrenheit), write a loop that builds a new array called celsius containing each temperature converted to Celsius. Use the formula C = (F − 32) × 5 / 9, rounded to one decimal place.
Show answer
Solution: const celsius = []; for (let i = 0; i < temps.length; i++) { celsius.push(Math.round(((temps[i] - 32) * 5 / 9) * 10) / 10); } This is a pure transform—every element is converted and pushed. The resulting array is [0, 20, 35, 10, 25]. The source array temps remains unchanged. Note that Math.round(x * 10) / 10 is a standard trick for rounding to one decimal place.

Question 3: INTERMEDIATE

Given const words = ["apple", "Banana", "cherry", "Date", "elderberry"];, write a loop that builds a new array containing only the words that start with a lowercase letter, and converts each of those words to uppercase.
Show answer
Solution: const result = []; for (let i = 0; i < words.length; i++) { if (words[i][0] === words[i][0].toLowerCase()) { result.push(words[i].toUpperCase()); } } This combines filter (check if the first character equals its lowercase version) with transform (convert the whole word to uppercase). The result is ["APPLE", "CHERRY", "ELDERBERRY"]. "Banana" and "Date" are excluded because they start with uppercase letters. The check words[i][0] === words[i][0].toLowerCase() works because bracket notation accesses the first character of the string.

Question 4: APPLIED

You are building a simple online store. You have an array of product objects: const products = [{name: "Notebook", price: 3.50}, {name: "Pen", price: 1.25}, {name: "Backpack", price: 45.00}, {name: "Eraser", price: 0.75}, {name: "Calculator", price: 12.99}]; Write a loop that builds a new array of strings for items under $5.00. Each string should read like: "Notebook - $3.50".
Show answer
Solution: const deals = []; for (let i = 0; i < products.length; i++) { if (products[i].price < 5) { deals.push(products[i].name + " - $" + products[i].price.toFixed(2)); } } The result is ["Notebook - $3.50", "Pen - $1.25", "Eraser - $0.75"]. This problem requires accessing object properties (dot notation), filtering based on a numeric condition, and transforming each object into a formatted string. The .toFixed(2) method ensures prices always display two decimal places.

Question 5: CRITICAL THINKING

A classmate writes this code to get only the even numbers from an array, but it doesn't work correctly. Find the bug, explain why it causes a problem, and fix it: const nums = [4, 9, 12, 7, 6]; const evens = []; for (let i = 1; i <= nums.length; i++) { if (nums[i] % 2 === 0) { evens.push(nums[i]); } }
Show answer
There are two bugs. First, the loop starts at i = 1 instead of i = 0, so the first element (4) is skipped and never checked. Second, the condition uses i <= nums.length instead of i < nums.length. When i equals 5 (the length), nums[5] is undefined, and undefined % 2 evaluates to NaN, which is falsy, so no error is thrown but the behavior is incorrect. The fix is: for (let i = 0; i < nums.length; i++). This is called an off-by-one error, and it's one of the most frequent mistakes in all of programming.

SUMMARY

Lesson Summary

Summary

In this lesson you learned the fundamental loop-based build pattern for creating new arrays from existing ones. The pattern has three consistent steps: declare an empty result array before the loop, iterate through the source array with a for loop, and push values into the result. When you change each element before pushing, you are transforming; when you use an if statement to decide whether to push, you are filtering.

Key ideas to remember: transforms always produce a result array of the same length as the source, while filters may produce a shorter array. You can combine both patterns in a single loop for more complex tasks. The source array is never modified, keeping your data safe. This loop-based approach is the foundation for understanding JavaScript's built-in methods like .map() and .filter(), which you'll learn next.

Varsity Tutors • Computer Programming 1 (JavaScript) • Transforming & Filtering Arrays