FILTER

FILTER returns the rows of a range that match a condition, spilling the result into the cells below. The modern way to subset a list without copy-paste, autofilter, or pivot tables.

ArrayIntermediate
Purpose
Return the rows of a range that match a condition, as a spilled dynamic array.
Returns
All matching rows, spilled into adjacent cells below and to the right
Syntax
=FILTER(array, include, [if_empty])
Excel version
Excel 365 and Excel 2021+

How FILTER works

FILTER takes a range and a TRUE/FALSE mask the same height. Wherever the mask is TRUE, the matching row from the range is kept. Wherever it's FALSE, the row is dropped. The kept rows spill into the cells below the formula.

The include argument is usually a comparison on one of the columns - like A2:A100="Active" or B2:B100>1000. Excel evaluates the comparison row by row, producing a TRUE/FALSE array, which FILTER uses to pick rows.

Combine multiple conditions by multiplying their TRUE/FALSE arrays (* for AND) or adding them (+ for OR). FILTER itself doesn't have multiple-criteria slots like SUMIFS - the criteria all live inside the second argument.

= FILTER(array, include, [if_empty])

Arguments

ArgumentTypeRequiredDescription
arrayrange✓ RequiredThe range whose rows you want to subset. Can be one column, one row, or a 2D block. The result spills with the same width.
includeboolean array✓ RequiredA TRUE/FALSE array the same height as array. Usually a comparison like B2:B100>1000 which produces an array of TRUE/FALSE per row. Combine with * for AND, + for OR.
if_emptyany✗ OptionalWhat to return when zero rows match. Default is #CALC!. Pass "" for blank, or a string like "No matches" for production reports.

FILTER basic: pull active employees

Same scenario as COUNTIF, different deliverable: instead of counting active employees, return the actual rows. FILTER walks the Status column, keeps rows where Status equals Active, and spills them.

=FILTER(A2:B11,B2:B11="Active")
  • A2:B11 -> the rows to subset (Name + Status, two columns)
  • B2:B11="Active" -> the mask: a TRUE/FALSE array, one per row
  • Spill -> the result fills as many rows as match, expanding cells below
  • Live -> add a new active row in source, the spilled output updates instantly
D2
fx
=FILTER(A2:B11,B2:B11="Active")
ABCDEF
1EmployeeStatusActive employees
2Sarah ChenActiveSarah ChenActive
3James MillerActiveJames MillerActive
4Maria SantosOn LeaveDavid BrownActive
5David BrownActiveAlex KimActive
6Emma DavisTerminatedJenny LiuActive
7Alex KimActiveMark ReillyActive
8Carlos GomezOn Leave
9Jenny LiuActive
10Mark ReillyActive
11Rachel WoodTerminated
Formula entered in cell D2 - result spills down through D-E for as many active rows as exist.
Six rows match Active. The formula sits in D2 (the green-bordered cell) and the result spills down through D7-E7. Only D2 holds the formula; the cells below it are read-only spill output.

FILTER with comparison: pull big-ticket sales

Same shape as the basic case, the mask is a numeric comparison instead of a text equality. Useful for any 'pull just the rows that meet this threshold' question.

=FILTER(A2:B11,B2:B11>1000,"No matches")
  • A2:B11 -> the order log (id + amount)
  • B2:B11>1000 -> mask: TRUE for amounts over $1,000
  • "No matches" -> graceful fallback when nothing matches - shown as a single cell
  • Spill behavior -> result is N rows tall, where N is the count of matches
D2
fx
=FILTER(A2:B11,B2:B11>1000,"No matches")
ABCDEF
1OrderAmountOrderAmount
2PO-1001$450PO-1002$2,400
3PO-1002$2,400PO-1004$3,150
4PO-1003$890PO-1005$1,250
5PO-1004$3,150PO-1007$5,020
6PO-1005$1,250PO-1009$2,000
7PO-1006$700
8PO-1007$5,020
9PO-1008$320
10PO-1009$2,000
11PO-1010$580
Formula entered in cell D2 - result spills down through D-E for as many rows as match.
Five orders cleared $1,000 (highlighted green in column B). The spill in D-E pulled exactly those five rows in their original order. The other five rows under $1,000 don't appear in the output.

FILTER with AND: active engineers only

Multiple conditions combine inside the include argument. Multiply the TRUE/FALSE arrays for AND - row-wise multiplication treats TRUE as 1 and FALSE as 0, so only rows where every condition is TRUE survive (1*1 = 1).

=FILTER(A2:C11,(B2:B11="Engineering")*(C2:C11="Active"))
  • A2:C11 -> the full roster: name, department, status
  • (B2:B11="Engineering") -> first mask: TRUE for engineers
  • (C2:C11="Active") -> second mask: TRUE for actives
  • * between them -> multiplies element-wise, giving TRUE only where both are TRUE
  • Operator pattern -> * = AND, + = OR. Wrap each comparison in parentheses.
E2
fx
=FILTER(A2:C11,(B2:B11="Engineering")*(C2:C11="Active"))
ABCDEFGH
1EmployeeDepartmentStatusEmployeeDepartmentStatus
2Sarah ChenEngineeringActiveSarah ChenEngineeringActive
3James MillerEngineeringActiveJames MillerEngineeringActive
4Maria SantosMarketingActiveAlex KimEngineeringActive
5David BrownEngineeringOn LeaveRachel WoodEngineeringActive
6Emma DavisSalesActive
7Alex KimEngineeringActive
8Carlos GomezOperationsActive
9Jenny LiuMarketingActive
10Mark ReillyEngineeringTerminated
11Rachel WoodEngineeringActive
Formula entered in cell E2 - result spills down through E-G.
Six rows are tagged Engineering, four are also Active (Sarah, James, Alex, Rachel). Those four spill into E-G in their original order. David is On Leave and Mark is Terminated, so they fall out of the AND filter.
If you forget the parentheses, Excel applies operator precedence in unexpected ways. Always wrap each comparison: (cond1)*(cond2), not cond1*cond2.

FILTER with OR: employees in two departments

Use + instead of * for OR: a row passes if any condition is TRUE. "Engineering OR Marketing" is two equality tests added together.

=FILTER(A2:B11,(B2:B11="Engineering")+(B2:B11="Marketing"))
  • (B2:B11="Engineering") -> first mask
  • (B2:B11="Marketing") -> second mask
  • + between them -> row-wise addition: TRUE+FALSE=1, TRUE+TRUE=2, FALSE+FALSE=0
  • Excel treats non-zero as TRUE -> so rows matching either condition are kept
D2
fx
=FILTER(A2:B11,(B2:B11="Engineering")+(B2:B11="Marketing"))
ABCDEF
1EmployeeDepartmentEmployeeDepartment
2Sarah ChenEngineeringSarah ChenEngineering
3James MillerEngineeringJames MillerEngineering
4Maria SantosMarketingMaria SantosMarketing
5David BrownEngineeringDavid BrownEngineering
6Emma DavisSalesAlex KimEngineering
7Alex KimEngineeringJenny LiuMarketing
8Carlos GomezOperationsMark ReillyEngineering
9Jenny LiuMarketingRachel WoodEngineering
10Mark ReillyEngineering
11Rachel WoodEngineering
12
13
14
Formula entered in cell D2 - eight rows match (six Engineering + two Marketing).
Eight rows match either Engineering or Marketing. The two left out are Emma (Sales) and Carlos (Operations). The spill preserves the source order, so Maria's Marketing row sits between the engineers in row 3.
If a row could match both conditions (impossible here, but possible in others), the + result is 2 - which is still truthy, so FILTER keeps it. Don't worry about double-counting.

Live filter: subset by a cell input

Reference a cell inside the comparison and FILTER becomes interactive. Type a department name in cell E1 and the filtered roster updates instantly. The classic "search box" pattern.

=FILTER(A2:C11,B2:B11=E1,"No matches")
  • B2:B11=E1 -> compare each department to whatever the user typed in E1
  • Live -> edit E1, the spill updates
  • Empty E1 -> matches blank departments only - usually returns the if_empty fallback
  • Combine with FIND -> use FIND for partial-match: ISNUMBER(FIND(E1,B2:B11)) returns TRUE for any row containing the substring
E2
fx
=FILTER(A2:C11,B2:B11=E1,"No matches")
ABCDEFG
1EmployeeDepartmentStatusEngineeringSearch dept
2Sarah ChenEngineeringActiveSarah ChenEngineeringActive
3James MillerEngineeringActiveJames MillerEngineeringActive
4Maria SantosMarketingActiveDavid BrownEngineeringOn Leave
5David BrownEngineeringOn LeaveAlex KimEngineeringActive
6Emma DavisSalesActiveMark ReillyEngineeringTerminated
7Alex KimEngineeringActiveRachel WoodEngineeringActive
8Carlos GomezOperationsActive
9Jenny LiuMarketingActive
10Mark ReillyEngineeringTerminated
11Rachel WoodEngineeringActive
12
13
14
E1 is the live search input. Formula sits in E2 and spills below. Change E1 to "Marketing" or "Sales" and the spill rewrites itself.
E1 holds the search term ("Engineering" today, yellow-highlighted). All six engineers spill in their original order across E-G. Type "Marketing" in E1 and the spill instantly rewrites itself to Maria + Jenny.
FILTER + a cell input is the easiest way to build a dashboard search box. No macros, no autofilter, no pivot table refresh - just type and watch.

Where people go wrong

  1. Forgetting the if_empty argument

    When zero rows match the condition, FILTER returns #CALC! by default. Reports look broken because of one stray edge case.

    Fix: Always pass the third argument in production: =FILTER(array, include, "No matches") or "" for blank.
  2. Wrong array shape between array and include

    If array has 50 rows and include has 49 (or 51), FILTER returns #VALUE!. The include array must match the array's height exactly.

    Fix: Use the same row range in both: =FILTER(A2:C100, B2:B100=...) - both 99 rows tall.
  3. Spill blocked by content below

    FILTER expands downward. If a cell underneath your formula already has data, you get a #SPILL! error. The function refuses to overwrite existing content.

    Fix: Clear the cells below the formula or move the formula somewhere with empty space below it. The spill range is a contiguous block from the formula's cell down/right.
  4. Using AND() / OR() inside include

    Writing =FILTER(A2:C11, AND(B2:B11="X", C2:C11="Y")) doesn't work the way you'd expect. AND() collapses the entire array to a single TRUE/FALSE - so FILTER either returns ALL rows or NONE.

    Fix: Use * for AND and + for OR with parentheses around each comparison: (B2:B11="X")*(C2:C11="Y"). These operate row-wise.

Notes

  • FILTER is a dynamic-array function. The result spills into adjacent cells - you can't put other content in the spill range without breaking it.
  • Available in Excel 365 and Excel 2021. Older versions don't have it; share files cautiously.
  • Combine criteria with * (AND) or + (OR), wrapping each comparison in parentheses.
  • The include argument must be a 1D boolean array the same height as the array's rows.
  • When zero rows match, FILTER returns #CALC! unless you supply if_empty.
  • Reference a cell in the include argument to make the filter live - typing in that cell re-runs the filter.
  • Combine with SORT (=SORT(FILTER(...))) to get sorted filtered results in one formula.

Now prove it

Reading about FILTER is one thing.

Writing one in front of an analyst dashboard at 8am, with a director typing department names into a cell and expecting the table to refresh, is completely different.

These exercises put FILTER in real workplace patterns: cohort views, search boxes, dynamic reports.

Here's the thing about FILTER.

You can read this page twice and still hesitate when the team needs a live dashboard cell that subsets a 5,000-row roster by whatever the user types in a search box.

That gap, between knowing what FILTER does and being able to write one fluently under pressure, is exactly what CellSkill is built to close.

Not with more reading.

With practice on scenarios that look like your actual job.

Start practicing FILTER for free →
Free account . No credit card . Cancel anytime
Browse exercises
FILTER in Excel: Extract Rows That Match a Condition · CellSkill