Advanced · Lesson 15 of 22

Advanced Formulas: Filter(), Map(), Reduce()

Coda's most powerful formula functions work on lists. Once you understand list processing, you can build rollups, dashboards, and transformations that would require a database in any other tool.

⏱ ~32 min 🧮 Formula Deep-Dive ✅ Prerequisite: Lesson 14
01 — Lists vs Scalars 02 — Filter() 03 — Map() 04 — Reduce() 05 — SortBy / Unique / First 06 — Dashboard Formulas Practice
01 — The Foundation

Lists vs scalars: know which you have.

In most spreadsheet tools, a formula operates on one cell at a time. Coda is different — the majority of its powerful formula functions operate on lists: tables, columns, or ranges of multiple values. Understanding whether you're working with a list or a single value (a "scalar") is the key to unlocking Coda formulas and avoiding type errors.

A scalar is a single value: a number, a text string, a date, a checkbox. A list is an ordered collection — a whole table, a filtered subset of rows, or the result of another list-returning function.

1️⃣

Scalar (single value)

thisRow.[Task Name] — the name of one row. Today() — one date. 42 — one number. Functions like Upper(), Len(), and arithmetic operators work on scalars.

📋

List (multiple values)

Tasks — all rows in the Tasks table. Tasks.[Name] — a column list. Filter() result — a filtered subset. Functions like Count(), Filter(), and Map() work on lists.

How functions relate to lists

List → Scalar

Takes a list, returns one value. Count(), Sum(), Average(), Min(), Max().

List → List

Takes a list, returns a list. Filter(), Map(), SortBy(), Unique().

List → One Item

Plucks from a list. First(), Last(), Nth(n) — returns a single item, not a list.

Why this matters for chaining If a function returns a list, you can chain another list function after it with a dot: Tasks.Filter(...).Count(). If a function returns a scalar, you can't chain a list function after it — that's the source of most "type error" messages in Coda.
02 — Filter()

Return rows where condition is true.

Filter(table, condition) returns the subset of rows where the condition evaluates to true. The result is always a list — even if zero or one rows match. You can chain filters, aggregate the result, or use it inside formula columns to create per-row rollups.

Filter() — four patterns
// 1. All incomplete tasks
Tasks.Filter([Status] != "Done")

// 2. Overdue open tasks (chained filters)
Tasks.Filter([Status] != "Done").Filter([Due Date] < Today())

// 3. In a formula column on Projects table — tasks for THIS project row
Tasks.Filter([Project] = thisRow)

// 4. Aggregate the filtered list
Tasks.Filter([Project] = thisRow).Count()
Tasks.Filter([Project] = thisRow).Sum([Estimate])
Tasks.Filter([Project] = thisRow).Min([Due Date])
Projects table — formula columns using Filter+Count
Project Text Total Tasks Formula Open Tasks Formula Overdue Formula Next Due Formula
Website Redesign 12 7 3 Mar 20
Onboarding Flow 8 2 0 Apr 1
Q2 Campaign 5 5 1 Mar 18
Open Tasks formula: Tasks.Filter([Project] = thisRow).Filter([Status] != "Done").Count()
Overdue formula: Tasks.Filter([Project] = thisRow).Filter([Status] != "Done").Filter([Due Date] < Today()).Count()
Filter() always returns a list Even if only one row matches, you get a list of one row — not a scalar. To get the row's field value, chain .First().[Column Name] after the filter. To get a count, chain .Count().
03 — Map()

Transform each item in a list.

Map() applies a formula to every item in a list and returns a new list of the same length. The placeholder CurrentValue stands in for each item as Map processes the list one element at a time. You can navigate related columns from CurrentValue, concatenate fields, or run any formula that works on a single value.

Map() — from simple transforms to complex compositions
// 1. Multiply every price by 1.2 (add 20% markup)
[Prices].Map(CurrentValue * 1.2)

// 2. Build a text label for each task in a relation column
thisRow.[Tasks].Map(
  CurrentValue.[Name] & " (" & CurrentValue.[Status] & ")"
)
// → ["Write brief (Done)", "Design mockup (In Progress)", ...]

// 3. Chain Filter + Map: names of open tasks for this project
Tasks.Filter([Project] = thisRow).Map(CurrentValue.[Name])
// → ["Write spec", "Review designs", "Update docs"]
📥

Input → Output length

Map always returns a list the same length as the input. 10 rows in, 10 values out. This makes it predictable — unlike Filter(), which can shrink the list.

🔑

CurrentValue is the current item

Think of CurrentValue as "this item in the loop." When the list is a table's rows, CurrentValue.[Column] accesses that row's field.

Map() produces a list — join it for display A Map() result is a list of values. To display it as a single text string, wrap it: Tasks.Filter([Project]=thisRow).Map(CurrentValue.[Name]).Join(", ") — the Join() function collapses a list to one delimited string.
04 — Reduce()

Fold a list to a single value.

Reduce() walks through a list and accumulates a running result. It takes two arguments: an initial value (where the accumulator starts) and a formula combining Accumulator (the running result so far) with CurrentValue (the current item). Reduce is the general-purpose tool — Sum(), Count(), and even Join() are all special cases of Reduce.

Reduce() — three patterns
// 1. Sum prices explicitly (same as Sum() but transparent)
[Prices].Reduce(0, Accumulator + CurrentValue)

// 2. Join names with commas, handling the empty-first-item edge case
[Names].Reduce("",
  Accumulator & If(Accumulator = "", "", ", ") & CurrentValue
)
// → "Alice, Bob, Carol"

// 3. Count done tasks (alternative to Filter + Count)
Tasks.Reduce(0,
  Accumulator + If(CurrentValue.[Done], 1, 0)
)

Formula anatomy — comma-join with Reduce()

[Names].Reduce("", Accumulator & If(Accumulator = "", "", ", ") & CurrentValue)
Input list [Names] — a list column or any list expression
Function Reduce() — folds the list to one value using an accumulator
Arg 1 — initial value "" — start with an empty string; accumulator begins here
Separator logic If(Accumulator = "", "", ", ") — skip the comma on the very first item
CurrentValue Each name in the list, one at a time — appended to the accumulator
When to use Reduce() vs Filter()+Count() For simple counting or summing, Filter().Count() is clearer and more readable. Use Reduce() when you need a custom accumulation that no built-in aggregation function provides — like building a comma-joined string, a weighted score, or a conditional running total.
05 — SortBy / Unique / First / Last / Nth

Order, deduplicate, and pluck.

After filtering and transforming a list, you often need to sort it, remove duplicates, or extract specific items. These functions compose naturally with Filter() and Map() — each takes a list and returns a list or a single value.

Sorting and plucking — practical patterns
// Sort tasks by due date ascending (true = oldest first)
Tasks.Filter([Project] = thisRow)
     .SortBy([Due Date], true)

// Sort descending — most recent first (false = newest first)
Tasks.Filter([Status] = "Done")
     .SortBy([Completed], false)

// Most recently completed task — single row result
Tasks.Filter([Status] = "Done")
     .SortBy([Completed], false)
     .First()

// Second item in a sorted list
Tasks.SortBy([Due Date], true).Nth(2)

// Deduplicate: all unique tag values across all task rows
Tasks.[Tags].Unique()
🔃

SortBy(list, key, asc)

Returns the list sorted by the key formula. true = ascending (A→Z, 1→9, oldest first). false = descending.

🎯

First() / Last() / Nth(n)

Pluck one item from a list. Returns a scalar — the actual row or value, not a list. Chain .First().[Column] to get a field from the first row.

♻️

Unique()

Returns the list with duplicates removed. Useful for tag clouds, category counts, or ensuring exactly one reference per item.

06 — Composition

A real dashboard summary formula.

The true power of Coda's list functions appears when you compose them together. A single formula column can summarize hundreds of task rows into a human-readable status line — no pivot table, no external report, no manual update needed.

"📋 Open: " & Tasks.Filter([Status] != "Done").Count() &
" | 🔴 Overdue: " & Tasks.Filter([Status] != "Done").Filter([Due Date] < Today()).Count() &
" | ✅ Done this week: " & Tasks.Filter([Done] = true).Filter([Completed] >= DateAdd(Today(), -7, "days")).Count()
Segment 1 — open count Tasks.Filter([Status] != "Done").Count() — all tasks not yet done
Segment 2 — overdue count .Filter([Status] != "Done").Filter([Due Date] < Today()) — chained filters narrow to overdue open tasks
Segment 3 — done this week Filter([Completed] >= DateAdd(Today(),-7,"days")) — tasks completed in the last 7 days
Output A single text scalar: "📋 Open: 7 | 🔴 Overdue: 2 | ✅ Done this week: 4"
Use this pattern anywhere Put this formula in a canvas cell at the top of a project hub page, in a formula column on a Portfolio table, or inside a button tooltip. The same filter composition works in all three contexts — Coda re-evaluates it live whenever underlying task data changes.
Practice

Test your knowledge.

Filter, Map & Reduce

Fill in the Blank
Question 1
Filter() always returns a ______ — never a single row directly.
Question 2
Inside a Map() formula, the placeholder for the current item in the list is: ____________
Question 3
Reduce()'s first argument sets the _____________ for the Accumulator.
Question 4
In SortBy(), passing true as the third argument sorts the list in _________ order.
Question 5
To get the single first item from a list (not the whole list), call: _______
← Prev Packs: Connecting External Apps Lesson 14