Lodash _.thru() Method

Beginner
⏱️ 7 min read
📚 Updated: Jul 2026
🎯 6 Examples + 3 Try It
Seq & chaining

What You’ll Learn

By the end of this tutorial, you’ll use _.thru() to run custom transforms inside Lodash chains and replace the pipeline value with your function’s return.

01

Custom step

.thru(fn)

02

Return matters

New chain value.

03

Mid-chain

After map, etc.

04

vs _.tap()

Transform vs observe.

05

Conditional

Branch in fn.

06

Compose helpers

Pipeline stages.

What Is _.thru()?

_.thru() is Lodash’s custom transform slot inside a chain. Your interceptor receives the current value, and whatever you return becomes the value for the next step. Use it when no single Lodash method fits—inline filtering, reshaping objects, or calling your own helper functions while keeping a fluent pipeline.

💡
Return value drives the chain

Unlike _.tap(), which ignores your return, _.thru() replaces the pipeline value. Always return the data shape the next step expects.

Use _.thru(value, interceptor) standalone, or .thru(interceptor) inside _.chain() / _(value) pipelines. Finish with .value() to unwrap.

📝 Syntax

Standalone and chained forms:

javascript
// Standalone
_.thru(value, interceptor)

// Inside a chain
_(value)
  .map(fn)
  .thru(interceptor)
  .value()

Syntax Rules

  • value — initial data (standalone) or current chain value (chained).
  • interceptor — function that receives the value and returns the next value.
  • Return value — becomes the new wrapped value for subsequent steps.
  • Chain form.thru(fn) returns a wrapper for more Lodash methods.
  • vs _.tap() — tap is for side effects only; thru is for transforms.
javascript
import _ from "lodash";

const result = _([1, 2, 3])
  .map(function (x) { return x * 2; })
  .thru(function (collection) {
    console.log("Intermediate:", collection);
    return collection.filter(function (x) { return x > 3; });
  })
  .value();

console.log("Result:", result);
// Intermediate: [2, 4, 6]
// Result: [4, 6]

⚡ Quick Reference

TaskCode patternResult
Custom filter step.thru(function (arr) { return arr.filter(fn); })Filtered array continues
Call helper.thru(myHelper)Helper return is new value
Standalone_.thru(data, fn)Fn return directly
Side effect only.tap(fn)See _.tap()
Start chain_.chain(data)See _.chain()
Unwrap.value()See .value()
Thru
.thru(fn)

Custom transform

Return
return newValue

Drives next step

Tap
.tap(fn)

Observe only

🧰 Parameters

_.thru() accepts a value and an interceptor function:

value Required

The current value to transform. In chains, this is the result from prior steps.

_.thru([1, 2, 3], fn)
interceptor Required

Function that receives the value and returns the replacement for the next chain step.

function (v) { return v.filter(fn); }
return Interceptor result

Standalone: returns interceptor result directly. Chained: wraps it for further steps.

return transformedData
chain .thru() Wrapper

.thru(fn) returns a wrapper so map, filter, sum, and other methods can follow.

_.chain(x).thru(fn).map(...)

For logging without changing data, use _.tap(). For built-in transforms, prefer dedicated methods like map and filter when they read clearer.

Examples Gallery

Practical _.thru() patterns with sample output and interactive Try It Yourself labs.

📚 Getting Started

Insert a custom transform after map and let thru return the filtered array.

Example 1 — Map, then custom filter in thru

Double values, then use thru to filter—the return becomes the next chain value.

javascript
const result = _([1, 2, 3])
  .map(function (x) { return x * 2; })
  .thru(function (collection) {
    console.log("Intermediate:", collection);
    return collection.filter(function (x) { return x > 3; });
  })
  .value();

console.log("Result:", result);
// Intermediate: [2, 4, 6]
// Result: [4, 6]
Try It Yourself

How It Works

The thru callback receives [2, 4, 6] and returns [4, 6]—that filtered array is what .value() ultimately unwraps.

Example 2 — Standalone _.thru(value, fn)

Apply a function and get its return directly, without a full chain.

javascript
const scores = [40, 55, 72, 88];

const passed = _.thru(scores, function (arr) {
  return arr.filter(function (n) { return n >= 60; });
});

console.log(passed);
// -> [72, 88]
Try It Yourself

📈 Practical Patterns

Compare with tap, branch conditionally, and compose named pipeline stages.

Example 3 — _.thru() vs _.tap()

Same interceptor return—thru uses it, tap ignores it.

javascript
const viaTap = _.chain([1, 2, 3])
  .tap(function () { return [99]; })
  .value();

const viaThru = _.chain([1, 2, 3])
  .thru(function () { return [99]; })
  .value();

console.log(viaTap);
// -> [1, 2, 3]

console.log(viaThru);
// -> [99]
Try It Yourself

Example 4 — Conditional processing

Branch inside thru based on intermediate data.

javascript
const useFilter = true;

const result = _([1, 2, 3, 4, 5])
  .thru(function (data) {
    return useFilter
      ? data.filter(function (n) { return n % 2 === 0; })
      : data;
  })
  .map(function (n) { return n * 10; })
  .value();

console.log(result);
// -> [20, 40]

🚀 Beyond the Basics

Stack helper functions and validate before transforming.

Example 5 — Pipeline of named helpers

Chain multiple thru steps, each delegating to a focused function.

javascript
function normalize(arr) {
  return arr.map(function (n) { return n.trim ? n.trim() : n; });
}

function toNumbers(arr) {
  return arr.map(Number).filter(function (n) { return !isNaN(n); });
}

const result = _(["10", " 20 ", "x", "30"])
  .thru(normalize)
  .thru(toNumbers)
  .sum()
  .value();

console.log(result);
// -> 60

Example 6 — Validate then transform

Guard data shape inside thru before continuing the chain.

javascript
const result = _([1, 2, 3])
  .map(function (x) { return x * 2; })
  .thru(function (collection) {
    if (!Array.isArray(collection)) {
      throw new Error("Expected an array");
    }
    return collection.filter(function (x) { return x > 3; });
  })
  .value();

console.log(result);
// -> [4, 6]

🧠 How _.thru() Works

1

Value reaches thru

A prior step (or the initial wrap) produces the current value passed to your interceptor.

Input
2

Interceptor runs

Your function transforms, filters, validates, or reshapes the value and returns the result.

Transform
3

Return replaces value

Lodash wraps the return value so the next chain method sees the new data.

Replace
=

Chain continues

More Lodash steps or .value() run on the transformed value—not the pre-thru input.

📝 Notes

  • _.thru() is for transforms—your interceptor’s return value always drives the next step.
  • Use _.tap() when you only need to log or observe without changing data.
  • Prefer built-in methods like filter and map when they express the intent clearly; use thru for custom logic.
  • Return the data type the next chain step expects—array, object, number, etc.
  • Stacking multiple .thru() calls is a clean way to compose named pipeline stages.
  • Next section: Lodash String utilities for text manipulation.

Conclusion

_.thru() is Lodash’s escape hatch for custom transforms inside fluent chains. Your interceptor’s return becomes the new pipeline value—use it for inline filters, conditional steps, and composing helper functions while keeping code readable.

That completes the core Seq chaining trio: _.chain(), _.tap() for observation, and _.thru() for transformation. Continue with Lodash String methods next.

💡 Best Practices

✅ Do

  • Return the value explicitly from every thru interceptor
  • Extract complex thru logic into named functions for readability
  • Use thru when a single Lodash method cannot express your transform
  • Pair with .tap() for logging and .thru() for transforming
  • Validate data shape inside thru when pipelines receive external input

❌ Don’t

  • Use thru for side effects only—that is what _.tap() is for
  • Forget to return a value from the interceptor (undefined will break the chain)
  • Replace clear .filter() calls with thru unless the custom logic is genuinely needed
  • Return the wrong type for the next chain step without intending a reshape
  • Nest heavy business logic inside anonymous thru callbacks—name and test helpers instead

Key Takeaways

Knowledge Unlocked

Five things to remember about _.thru()

Use these when adding custom transforms to Lodash chains.

5
Core concepts
02

Return drives chain

New value.

Critical
🔄 03

Mid-chain slot

After map, etc.

Pattern
👁️ 04

vs tap

Transform vs observe.

Compare
🛠️ 05

Compose helpers

Pipeline stages.

Guideline

❓ Frequently Asked Questions

It calls your interceptor with the current chain value and uses the interceptor's return value as the new value for the next step. It is a custom transform slot inside a Lodash pipeline.
_.tap() runs a side-effect function and keeps the original value. _.thru() replaces the value with whatever your function returns.
When you need a custom inline transform that does not map cleanly to a single Lodash method—conditional filtering, composing helper functions, or reshaping data mid-chain.
Yes. The return value becomes the new wrapped value for subsequent chain steps. Always return the shape the next step expects.
Yes. _.chain(data).map(...).thru(fn).value() is a common pattern—the thru step can return a new array, object, or other value.
Yes. _.thru(value, interceptor) applies the function and returns its result directly, without starting a full chain.
Did you know?

_.thru() is the transform counterpart to _.tap()—together they let you keep pipelines fluent while mixing observation (tap) and custom logic (thru) without breaking out of the chain.

Practice _.thru() in the Live Editor

Run custom filters inside chains and compare thru with tap behavior.

Open Try It editor →

About the author

Mari Selvan M P
Mari Selvan M P 🔗

Developer, cloud engineer, and technical writer

  • Experience 12 years building web and cloud systems
  • Focus Full Stack Development, AWS, and Developer Education

I write practical tutorials so students and working developers can learn by doing—from databases and APIs to deployment on AWS.

6 people found this page helpful