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
1958 • LISP & List Processing
1995 • JavaScript Is Born
2009 • ES5 Adds forEach, map, filter
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.
Source Array
Result Array
push(). When the loop ends, this array holds your output.Transform Pattern
Filter Pattern
if statement, and push only the elements that pass the test.Immutability Mindset
KEY TAKEAWAY
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.
[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
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
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
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.
| Feature | Transform | Filter |
|---|---|---|
| Purpose | Change every element | Keep only elements that pass a test |
| Result length | Same as source | ≤ source length |
| Uses if statement? | No (every element is pushed) | Yes (only matching elements) |
| Element values | Different from source | Identical 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
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
Step 2 — Create an Empty Result Array
Declare the result:const passingGrades = [];This starts empty and will grow inside the loop. - 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
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
Step 5 — Read the Result
After the loop completes,passingGradescontains the final result.["B", "A", "C", "A"]— four passing grades from seven original scores. The source arrayscoresis unchanged.
Common Mistake
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.
| Aspect | Strength | Limitation |
|---|---|---|
| Flexibility | You can combine filter + transform + any custom logic in a single loop | More lines of code than built-in methods like .map() or .filter() |
| Readability | Easy to trace step by step; great for learning | Can become lengthy for simple operations |
| Performance | Slightly faster than chaining multiple built-in methods | Performance difference is negligible for most high school projects |
| Safety | Builds a new array—original data stays intact | If you accidentally modify source[i] (an object), the original mutates too |
| Debugging | You can add console.log inside the loop to inspect each step | Off-by-one errors (wrong loop bounds) are a common pitfall |
Strengths and limitations of the loop-based build pattern
KEY TAKEAWAY
.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 Pattern | Built-In Method | What 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
const result = []; inside the loop body?Show answer
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
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
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
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
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
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
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
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
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.