Jesse A. Green / notebook

Lodash User Guide

Generated April 2026 from lodash/lodash at 3a546c4

AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it.

Lodash is the JavaScript utility library that ships in millions of node_modules trees. It gives you a single, well-tested toolbox for the operations the language never bundled: deep cloning, debouncing, throttling, deep equality, deep merging, path-based property access, currying, function composition, group-by, chunking, and roughly three hundred more. If you have ever written if (obj && obj.user && obj.user.profile), reached for Object.assign({}, deep, deepNested) and watched it fail, or tried to build your own debounce in a search box, lodash is the canonical answer.

This guide is for JavaScript and TypeScript developers using lodash directly — through the full lodash package, the per-method packages such as lodash.debounce, the ESM-friendly lodash-es, or the auto-curried lodash/fp variant. It is task-driven: install, import, and a worked example come first; the full method catalog is grouped by category for fast lookup.

1. Quick Start

Install the full library and run a meaningful example in under a minute.

npm install lodash
added 1 package, and audited 1 package in 1s

found 0 vulnerabilities

Then in any Node.js or bundler-friendly file:

const _ = require('lodash');

const users = [
  { name: 'fred',    age: 40 },
  { name: 'barney',  age: 36 },
  { name: 'pebbles', age: 1  }
];

// Sort by age, take the youngest, format the name.
const youngest = _.chain(users)
  .sortBy('age')
  .head()
  .thru(u => `${_.upperFirst(u.name)} (${u.age})`)
  .value();

console.log(youngest);
// => 'Pebbles (1)'

That snippet exercises the four ideas you will use most: a category function (_.sortBy), a chain wrapper (_.chain), a thru transformer, and a string utility (_.upperFirst). Everything else in this guide is a refinement of those building blocks.

Tip

If you only need one method — say a debounced search input — install just that method: npm install lodash.debounce. The per-method packages are independent npm modules with no shared runtime, so your bundle pays only for what you import. See section 2 for the full set of install options.

What to try next:

  • The full method catalog by category is in section 4.2.
  • Common recipes (debounce, deep clone, group, pick/omit, deep equal, currying) are in section 5.
  • If you hit a ReferenceError after upgrading, jump to section 6.

2. Installation

Lodash supports five distinct install paths. Pick the one that matches your project.

The simplest path. One package, one import, every method available.

npm

npm install lodash

pnpm

pnpm add lodash

yarn

yarn add lodash

Then:

const _ = require('lodash');
// or, with ES module syntax:
import _ from 'lodash';
Detail Value
Package lodash
Current line 4.18.x
Node engine requirement >=4.0.0 (per package.json)
License MIT
Default export The full _ object with every method attached

Important

The official package.json declares engines.node >= 4.0.0. In practice every modern Node release (LTS 18, 20, 22) is supported, but if you ship to legacy environments check that engines field before assuming a newer baseline.

2.2 Per-method packages — lodash.<methodname>

Lodash also publishes every method as its own npm package. These are the modern, tree-shake-friendly way to use lodash from CommonJS or older bundlers, and many projects use them directly to keep deploy size tight.

npm install lodash.debounce
npm install lodash.clonedeep
npm install lodash.merge
npm install lodash.throttle
npm install lodash.groupby
npm install lodash.pick
npm install lodash.omit
npm install lodash.isequal

Each per-method package exports the function as its default export — the package name is always lodash. followed by the method name in lower case:

const debounce  = require('lodash.debounce');
const cloneDeep = require('lodash.clonedeep');
const merge     = require('lodash.merge');

// Identical behavior to _.debounce, _.cloneDeep, _.merge from the main package.
const onSearch = debounce(query => fetch(`/q?${query}`), 300);

Note

Per-method packages are independent npm modules with their own version histories. They share no runtime — installing both lodash and lodash.debounce ships the debounce code twice. Pick one strategy per app: full lodash, individual per-method packages, or lodash-es (next).

2.3 ESM build — lodash-es

lodash-es is the same library republished as native ES modules so that webpack, Rollup, esbuild, and Vite can tree-shake it. If you import only debounce and cloneDeep from lodash-es, only those two functions end up in your bundle.

npm install lodash-es
import { debounce, cloneDeep, merge } from 'lodash-es';

Tip

If your bundler supports tree-shaking (any modern setup does), lodash-es plus named imports gives you the same per-method savings as the per-method packages — but with a single dependency in package.json and a single version to keep in sync.

2.4 Functional variant — lodash/fp

The functional build re-exports every method as auto-curried, data-last, immutable. It ships inside the main lodash package — no extra install required.

const fp = require('lodash/fp');
// or: import fp from 'lodash/fp';

See section 4.4 for the full FP semantics. Quick demonstration:

const fp = require('lodash/fp');

// Data-last + auto-curried: reusable transformers.
const onlyEvens = fp.filter(n => n % 2 === 0);
const doubled   = fp.map(n => n * 2);
const sumAll    = fp.reduce((acc, n) => acc + n, 0);

const pipeline = fp.flow(onlyEvens, doubled, sumAll);
pipeline([1, 2, 3, 4, 5]);
// => 12   (i.e. (2 + 4) doubled, then summed)

2.5 Browser script tag (CDN)

For prototypes, blog posts, and quick fiddles, drop a <script> tag and use the global _:

<script src="https://cdn.jsdelivr.net/npm/lodash@4.18.1/lodash.min.js"></script>
<script>
  console.log(_.kebabCase('helloWorld'));
  // => 'hello-world'
</script>

Warning

Pin a specific version in production CDN URLs. A bare lodash reference (or one pinned to latest) lets a CDN-side breaking change reach your users without warning. Always specify @4.18.1 or whatever version you tested against.

2.6 Verify your install

After installing, confirm the package is wired up:

node -e "console.log(require('lodash').VERSION)"
4.18.1

_.VERSION is a real top-level property of the _ object — if it returns a version string, you have a working install. If you get Cannot find module 'lodash', re-run the install command and check that you ran it from the directory containing your package.json.

2.7 System requirements

Item Requirement
Node.js >=4.0.0 declared; LTS 18/20/22 fully supported in practice
Browser Any browser that runs ES5; the UMD build supports IE11+
Module systems CommonJS (require), ESM (import), AMD (UMD bundle)
TypeScript Type definitions ship separately as @types/lodash (or @types/lodash-es)

Add types if you use TypeScript:

npm install --save-dev @types/lodash
# or for the ESM build:
npm install --save-dev @types/lodash-es

3. Configuration

Lodash is a library, not an application — it has no config file, no environment variables, and no startup options. There are exactly two runtime knobs you may want to touch.

3.1 _.templateSettings — template delimiter customization

_.template recognizes three delimiter regexes that you can override globally before compiling templates:

Setting Default Purpose
_.templateSettings.escape <%- ... %> HTML-escape the interpolated value
_.templateSettings.interpolate <%= ... %> Insert the value as-is
_.templateSettings.evaluate <% ... %> Run the JavaScript inside
_.templateSettings.imports { '_': lodash } Object whose keys become free variables in compiled templates
_.templateSettings.variable 'obj' Data variable name in compiled output

Example — switch to mustache-style delimiters:

_.templateSettings.interpolate = /{{([\s\S]+?)}}/g;

const greet = _.template('hello {{ user }}!');
greet({ user: 'mustache' });
// => 'hello mustache!'

Caution

Setting _.templateSettings mutates a shared global. If two libraries in the same process both override interpolate, the last one wins. Prefer passing per-call options to _.template(string, { interpolate: ... }) over mutating the global. See section 5.10 for the full template recipe and section 7 of the Administrator & Developer Guide for the security analysis.

3.2 _.noConflict() — page-scoped global symbol

When lodash is loaded via a <script> tag it claims the _ global. If another library on the same page also wants _ (historically Underscore), restore the previous owner:

<script src="https://cdn.jsdelivr.net/npm/underscore@1.13.6/underscore-umd-min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/lodash@4.18.1/lodash.min.js"></script>
<script>
  // Page-time: lodash currently owns _.
  const lodash = _.noConflict();
  // Now _ refers to underscore again, and `lodash` owns lodash.
  console.log(lodash.kebabCase('twoLibraries'));
</script>

Note

_.noConflict() only matters in the browser-script scenario. In Node and bundler-driven projects, _ is just whatever local name you bound the import to — there is no global to conflict with.

For everything else — bundle size, build flags, FP options — see the Administrator & Developer Guide, section 3.

4. Core Usage

4.1 Import patterns at a glance

// 1. Default import — full library, every method on `_`.
import _ from 'lodash';
_.debounce(fn, 300);

// 2. Named import — same package, ergonomic destructuring.
import { debounce, cloneDeep } from 'lodash';
debounce(fn, 300);

// 3. Per-method package — dedicated npm module, default-exported.
import debounce from 'lodash.debounce';
debounce(fn, 300);

// 4. ESM build — best for bundler tree-shaking.
import { debounce, cloneDeep } from 'lodash-es';

// 5. Functional variant — auto-curried, data-last, immutable.
import fp from 'lodash/fp';
const debounced = fp.debounce(300, fn);

Important

Mixing imports from lodash and lodash-es in the same project causes both packages to ship to the client and doubles the bundle cost. Pick one and stick with it.

4.2 Method categories

Every lodash method lives in exactly one of eleven categories. Pick the category that matches your data, then look up the method.

Category Methods Purpose Examples
Array 64 Operate on arrays as ordered sequences chunk, compact, flatten, uniq, zip
Collection 28 Operate on arrays and plain objects uniformly forEach, map, filter, groupBy, sortBy
Date 1 Time helpers now
Function 23 Build and modify functions debounce, throttle, curry, partial, memoize
Lang 50 Type checks, value comparison, cloning isEqual, isPlainObject, cloneDeep, clone
Math 15 Numeric reductions and arithmetic sum, mean, max, add, round
Number 3 Number generators and predicates clamp, inRange, random
Object 38 Operate on plain objects pick, omit, merge, get, set, defaults
Seq 9 The chain wrapper API chain, tap, thru, _.prototype.value
String 30 String formatting and tokenization camelCase, kebabCase, template, trim, truncate
Util 32 Compose iteratees, identity-style helpers identity, noop, over, flow, times, range

Every method is also exported as its own per-method package (lodash.<methodname>) and as a named export in lodash and lodash-es. The category labels match the headings in the official API reference at lodash.com/docs.

4.2.1 Array — most-used

Method Signature Returns
_.chunk(array, [size=1]) (Array, number) Array split into groups of size
_.compact(array) (Array) New array with falsey values (false, null, 0, "", undefined, NaN) removed
_.concat(array, [values]) (Array, ...*) New array, original untouched
_.difference(array, [values]) (Array, ...Array) Values in array not in any of values, using SameValueZero
_.flatten(array) (Array) Array flattened one level deep
_.flattenDeep(array) (Array) Array flattened recursively
_.fromPairs(pairs) (Array) Object built from [key, value] pairs
_.head(array) (Array) First element (alias: _.first)
_.last(array) (Array) Last element
_.uniq(array) (Array) Duplicate-free array
_.uniqBy(array, [iteratee]) (Array, Function|string) Duplicate-free array using iteratee for comparison
_.zip([arrays]) (...Array) Groups elements from each input array by index
_.zipObject(props, values) (Array, Array) Object built from parallel keys/values arrays

Worked example — chunk + zip + uniq combined:

const days  = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'];
const temps = [62, 71, 71, 64, 70];

_.zipObject(days, temps);
// => { Mon: 62, Tue: 71, Wed: 71, Thu: 64, Fri: 70 }

_.uniq(temps);
// => [62, 71, 64, 70]

_.chunk(days, 2);
// => [['Mon', 'Tue'], ['Wed', 'Thu'], ['Fri']]

4.2.2 Collection — operate on arrays and objects alike

These work on both arrays and plain objects. The iteratee is invoked with (value, index|key, collection).

Method What it does
_.forEach(collection, iteratee) Iterates; alias _.each
_.map(collection, iteratee) Returns a new array of mapped values
_.filter(collection, predicate) Keeps elements where predicate is truthy
_.reject(collection, predicate) Inverse of filter
_.find(collection, predicate) First element matching predicate
_.findLast(collection, predicate) Last element matching predicate
_.groupBy(collection, iteratee) { key: [values] } keyed by iteratee result
_.keyBy(collection, iteratee) { key: value } (last write wins) keyed by iteratee result
_.partition(collection, predicate) [[truthy], [falsy]]
_.reduce(collection, iteratee, [acc]) Standard left fold
_.reduceRight(collection, iteratee, [acc]) Right fold
_.sortBy(collection, [iteratees]) Stable sort by one or more iteratees
_.orderBy(collection, [iteratees], [orders]) Like sortBy, with explicit 'asc'/'desc' per iteratee
_.size(collection) Length of an array, key count of an object
_.shuffle(collection) Fisher-Yates shuffle
_.sample(collection) / _.sampleSize(collection, [n]) Random pick(s)

4.2.3 Function — debounce, throttle, currying

Method Use it for
_.debounce(func, [wait=0], [options]) Delay invocation until wait ms have elapsed since the last call
_.throttle(func, [wait=0], [options]) Ensure func runs at most once per wait ms
_.once(func) Make a function callable exactly once; later calls return the cached result
_.memoize(func, [resolver]) Cache results keyed by the first argument (or resolver's result)
_.curry(func, [arity]) / _.curryRight(...) Auto-curry to the given arity
_.partial(func, [partials]) / _.partialRight(...) Pre-fill arguments left or right
_.bind(func, thisArg, [partials]) Function.prototype.bind plus partial application
_.bindKey(object, key, [partials]) Bind by method name; the method must exist on the object
_.delay(func, wait, [args]) / _.defer(func, [args]) Schedule with setTimeout
_.flip(func) Reverse argument order
_.negate(predicate) Return a predicate that flips the result
_.spread(func, [start=0]) Convert (args) => f(...args) to accept an array
_.rest(func, [start=func.length-1]) Collect trailing arguments into an array
_.unary(func) Cap to one argument; useful with [].map(_.unary(parseInt))
_.before(n, func) / _.after(n, func) Run before / after n invocations
_.ary(func, [n]) / _.overArgs(func, [transforms]) / _.rearg(func, [indexes]) / _.wrap(value, [wrapper]) Argument-shape adapters

4.2.4 Lang — type checks, equality, cloning

Method What it does
_.isArray(value) Array.isArray (provided for symmetry)
_.isObject(value) True for any non-primitive (objects, arrays, functions, new Number())
_.isPlainObject(value) True only for {}-style objects (not class instances)
_.isString(value) / _.isNumber(value) / _.isBoolean(value) / _.isSymbol(value) / _.isFunction(value) / _.isDate(value) / _.isRegExp(value) / _.isError(value) Exact type predicates
_.isNil(value) / _.isNull(value) / _.isUndefined(value) Nullish checks
_.isEmpty(value) True for null, undefined, [], {}, '', empty Maps/Sets
_.isEqual(value, other) Deep equality (handles arrays, objects, dates, regex, sets, maps, typed arrays)
_.isMatch(object, source) Whether object matches the partial properties of source
_.eq(value, other) / _.gt, _.gte, _.lt, _.lte Same-value-zero and comparisons
_.clone(value) / _.cloneDeep(value) / _.cloneWith(value, customizer) / _.cloneDeepWith(value, customizer) Shallow / deep clone, with optional customizer
_.toArray, _.toFinite, _.toInteger, _.toLength, _.toNumber, _.toPlainObject, _.toSafeInteger, _.toString Coercion helpers

4.2.5 Object — pick, omit, merge, path access

Method What it does
_.assign(object, [sources]) Like Object.assign — own enumerable string-keyed source props
_.assignIn(object, [sources]) Includes inherited source properties (alias: _.extend in fp)
_.merge(object, [sources]) Recursively merges nested objects (mutates object); undefined sources are skipped
_.mergeWith(object, sources, customizer) _.merge with per-property customizer
_.defaults(object, [sources]) Like _.assign but only writes when the destination is undefined
_.defaultsDeep(object, [sources]) Recursive _.defaults
_.pick(object, [paths]) New object containing only the listed paths
_.pickBy(object, [predicate]) New object containing keys where predicate(value, key) is truthy
_.omit(object, [paths]) New object minus the listed paths
_.omitBy(object, [predicate]) New object minus keys matching predicate
_.get(object, path, [default]) Path-string property access (e.g. 'user.profile.name' or ['user', 0, 'name'])
_.set(object, path, value) Path-string property write (mutates)
_.has(object, path) True if path exists as a direct property
_.hasIn(object, path) Like has but includes inherited
_.unset(object, path) Delete the property at path
_.update(object, path, updater) / _.updateWith(...) set(object, path, updater(get(object, path)))
_.invert(object) / _.invertBy(object, [iteratee]) Swap keys and values
_.keys(object) / _.values(object) / _.toPairs(object) Standard projections (keysIn, valuesIn, toPairsIn include inherited)
_.mapKeys(object, iteratee) / _.mapValues(object, iteratee) Transform keys / values
_.findKey(object, predicate) / _.findLastKey(...) Like find for objects, returns the matching key
_.forIn, _.forInRight, _.forOwn, _.forOwnRight Object iterators with control over inherited / direction
_.transform(object, iteratee, [acc]) reduce for objects, with mutable accumulator
_.create(prototype, [properties]) Object.create plus property assignment

4.2.6 String — case conversion, padding, templating

Method What it does
_.camelCase(string) 'hello world' -> 'helloWorld'
_.kebabCase(string) 'helloWorld' -> 'hello-world'
_.snakeCase(string) 'helloWorld' -> 'hello_world'
_.startCase(string) 'hello-world' -> 'Hello World'
_.lowerCase / _.upperCase Space-separated lowercase/uppercase words
_.capitalize, _.lowerFirst, _.upperFirst First-character helpers
_.deburr(string) Strip combining diacritics ('déjà vu' -> 'deja vu')
_.escape / _.unescape HTML entity escape / unescape (handles &, <, >, ", ', `)
_.escapeRegExp(string) Escape regex metacharacters
_.pad, _.padStart, _.padEnd Pad to length
_.repeat(string, [n=1]) Repeat n times
_.replace(string, pattern, replacement) String.prototype.replace lift
_.split(string, separator, [limit]) String.prototype.split lift
_.trim, _.trimStart, _.trimEnd Optional chars argument
_.truncate(string, [options]) Truncate to a length with omission and separator options
_.template(string, [options]) Compile an interpolation/escape/evaluate template — see the security note in section 5.10
_.words(string, [pattern]) Tokenize into words
_.startsWith, _.endsWith Boolean predicates
_.toLower, _.toUpper Whole-string case conversion
_.parseInt(string, [radix=10]) parseInt with a sane default radix

4.2.7 Math — numeric reductions

Method What it does
_.add(augend, addend), _.subtract, _.multiply, _.divide Two-argument arithmetic functions (useful as iteratees)
_.sum(array) / _.sumBy(array, iteratee) Sum with optional iteratee
_.mean(array) / _.meanBy(array, iteratee) Arithmetic mean
_.max(array) / _.maxBy(array, iteratee) Maximum
_.min(array) / _.minBy(array, iteratee) Minimum
_.round, _.floor, _.ceil All accept an optional precision (positive = decimals, negative = tens place)

4.2.8 Number — generators and predicates

Method What it does
_.clamp(number, [lower], upper) Constrain a number to [lower, upper]
_.inRange(number, [start=0], end) Whether number is in [start, end)
_.random([lower=0], [upper=1], [floating]) Random integer or float

4.2.9 Util — composing iteratees

Method What it does
_.identity(value) Returns value unchanged — the default iteratee
_.noop() Returns undefined — placeholder callback
_.constant(value) Returns a function that always returns value
_.flow([funcs]) / _.flowRight([funcs]) Compose left-to-right / right-to-left
_.matches(source) / _.matchesProperty(path, srcValue) Build a _.isMatch-style predicate
_.property(path) / _.propertyOf(object) Build a _.get-style accessor
_.method(path, [args]) / _.methodOf(object, [args]) Build a method-invoker
_.times(n, [iteratee]) Run iteratee n times, collect into array
_.range([start=0], end, [step=1]) / _.rangeRight(...) Build numeric sequences
_.uniqueId([prefix]) Globally-incrementing string id
_.cond(pairs) Multi-branch dispatch (like cond in Lisp)
_.conforms(source) / _.conformsTo(object, source) Build / check a "shape predicate" where each value is a function
_.over([iteratees]) / _.overEvery(...) / _.overSome(...) Combine predicates
_.bindAll(object, [methodNames]) Bind named methods to object (mutates)
_.attempt(func, [args]) Try / catch as a value
_.runInContext([context]) Build a fresh lodash bound to a different global
_.mixin([object], source, [options]) Add functions to lodash itself or another object

4.3 Chaining vs. non-chaining

There are two ways to call lodash:

4.3.1 Direct (non-chained) calls

const xs = [3, 1, 4, 1, 5, 9, 2, 6];
const top = _.take(_.sortBy(_.uniq(xs)), 3);
// => [1, 2, 3]

Reads inside-out. Fine for one or two operations.

4.3.2 Explicit chains with _.chain and .value()

For three or more steps a chain reads top-down:

const top = _.chain(xs)
  .uniq()
  .sortBy()
  .take(3)
  .value();
// => [1, 2, 3]

Important

Chain operations are lazy in lodash. The pipeline does not execute until you call .value() (or one of the chain-terminating methods listed below). Until that point each step just records intent — this lets lodash fuse adjacent operations and skip work on early-terminating queries like .take(3) after .map(...).

Chain-terminating methods that implicitly call .value():

Method Returns
.value() The unwrapped result
.toJSON() (alias of .value) Same
.valueOf() (alias of .value) Same
.next() Iterator step (chains implement the iterator protocol)

Chain-only helpers:

Method What it does
_.chain(value) Wrap value so that all methods (including normally non-chainable ones) return wrappers
_.tap(value, interceptor) Run side effects on the current value, return the wrapper unchanged
_.thru(value, interceptor) Run a transformer on the current value, return its result wrapped
_.prototype.commit() Materialize the chain so far without unwrapping
_.prototype.plant(value) Re-run the recorded chain against a new input value
_.prototype.reverse() Reverse the chain wrapper
_.prototype.at(paths) Index-into the chain result by paths
_.prototype[Symbol.iterator]() Lets you for...of over a chain

Tip

If you wrap a value with _(value) (function call) instead of _.chain(value), the wrapper is implicit: methods that return a primitive (such as _.head or _.reduce) auto-unwrap and return the raw value. Methods that return collections still return a wrapper. Use _.chain when you want every step wrapped; use _(value) when the last step naturally returns the result.

4.4 The lodash/fp variant

lodash/fp is the same library, transformed by an automated converter (fp/_baseConvert.js) that applies five rules:

Rule Effect Default
cap Cap iteratee arity (e.g. _.map iteratee receives only the value, not (value, index, array)) true
curry Auto-curry every method true
fixed Fix each method's arity (no rest args) true
immutable Methods that would mutate (e.g. _.set, _.merge, _.fill) clone first true
rearg Re-order arguments so data is last true

In practice this means every fp method is data-last, point-free, and immutable. Compare:

// Standard lodash — data-first, mutates in `_.merge`.
import _ from 'lodash';
_.merge(targetObj, { a: 1 });

// FP — data-last, returns a new object, target is untouched.
import fp from 'lodash/fp';
const updated = fp.merge({ a: 1 }, targetObj);

Auto-currying gives you reusable transformers without wrappers:

const addAge = fp.set('age');     // (value) => (object) => newObject
const setAge42 = addAge(42);      // (object) => newObject
setAge42({ name: 'fred' });
// => { name: 'fred', age: 42 }

Composition with fp.flow (left-to-right) or fp.pipe (alias of flow):

const headline = fp.flow(
  fp.get('user.name'),
  fp.upperFirst,
  name => `Welcome, ${name}!`
);
headline({ user: { name: 'fred' } });
// => 'Welcome, Fred!'

4.4.1 Ramda-style aliases in lodash/fp

lodash/fp accepts a number of names familiar to Ramda users. The aliases below all map to standard lodash methods:

Ramda alias Real method
pipe flow
compose flowRight
prop, path get
propOr, pathOr getOr
assoc, assocPath set
dissoc, dissocPath unset
pluck map
where, whereEq conformsTo, isMatch
T / F stubTrue / stubFalse
always constant
identical eq
equals isEqual
complement negate
juxt over
useWith overArgs
nAry ary
unapply rest
apply spread
__ placeholder

Note

The fp converter also exposes the *All, *With, and Get/Or/From variants used to thread argument shapes (assignAll, mergeAllWith, getOr, findFrom, etc.). These exist only in lodash/fp — the standard lodash package keeps the original variadic signatures.

5. Common Tasks

The recipes below cover the cases that drive most lodash adoption. Each one tells you the goal, the prerequisites, the steps, and the failure mode to watch for.

5.1 Debounce a search input

Goal: call searchAPI(query) only after the user pauses typing for 300 ms.

import debounce from 'lodash.debounce';

const debouncedSearch = debounce(query => {
  fetch(`/api/search?q=${encodeURIComponent(query)}`)
    .then(r => r.json())
    .then(renderResults);
}, 300);

document.querySelector('#search').addEventListener('input', e => {
  debouncedSearch(e.target.value);
});

The signature is _.debounce(func, [wait=0], [options]). Useful options:

Option Default Effect
leading false Call on the leading edge of the timeout
trailing true Call on the trailing edge (default)
maxWait — Maximum time the function may be delayed

Disposal — the debounced function exposes .cancel() (drop the pending invocation) and .flush() (run it immediately):

debouncedSearch.cancel();
debouncedSearch.flush();

Warning

If you create a new debounced function on every render of a React component, you defeat debouncing entirely — each call goes to a fresh timer. Memoize or lift the debounced function outside the component.

5.2 Deep-clone a config object

Goal: make a fully independent copy of a nested object so downstream code can mutate freely.

import cloneDeep from 'lodash.clonedeep';

const baseConfig = {
  api:     { host: 'api.example.com', headers: { 'X-Trace': '1' } },
  retries: { count: 3, backoffMs: [100, 500, 2000] }
};

const config = cloneDeep(baseConfig);
config.retries.backoffMs.push(5000);
console.log(baseConfig.retries.backoffMs);
// => [100, 500, 2000]   (untouched)

cloneDeep handles arrays, plain objects, dates, regex, sets, maps, typed arrays, and array buffers. For class instances or non-cloneable values, pass a customizer with cloneDeepWith:

const out = _.cloneDeepWith(value, v => {
  if (v instanceof MyDomainObject) return v.snapshot();
});

5.3 merge vs. assign vs. defaults

These three look similar but differ in two axes: depth and overwrite policy.

Method Recursive? Overwrites destination? undefined source values
_.assign(dest, src) No Yes Overwrite
_.merge(dest, src) Yes Yes (for primitives) Skipped
_.defaults(dest, src) No Only when destination key is undefined Skipped
_.defaultsDeep(dest, src) Yes Only fills missing leaves Skipped
const dest = { user: { name: 'fred', tags: ['a'] } };
const src  = { user: { tags: ['b'] } };

_.assign({}, dest, src);
// => { user: { tags: ['b'] } }    — assign overwrites the whole user object

_.merge({}, dest, src);
// => { user: { name: 'fred', tags: ['b'] } }   — note: arrays are merged by index, not concatenated

Caution

_.merge mutates its first argument. Pass a fresh {} as the destination if you want a non-mutating call: _.merge({}, base, override). _.merge also recursively merges arrays by index — to concatenate arrays use _.mergeWith with a customizer that returns [].concat(value, source).

5.4 Throttle a scroll handler

Goal: run onScroll at most once every 100 ms while the user scrolls.

import throttle from 'lodash.throttle';

window.addEventListener(
  'scroll',
  throttle(() => updateStickyHeader(), 100, { leading: true, trailing: true })
);

The same { leading, trailing } options as debounce apply. Use throttle when you need a steady rate; use debounce when you want to wait for activity to stop.

5.5 Group a list by a key

const orders = [
  { id: 1, status: 'paid',    total: 50 },
  { id: 2, status: 'pending', total: 20 },
  { id: 3, status: 'paid',    total: 75 },
  { id: 4, status: 'pending', total: 10 }
];

_.groupBy(orders, 'status');
// => {
//   paid:    [{ id: 1, ... }, { id: 3, ... }],
//   pending: [{ id: 2, ... }, { id: 4, ... }]
// }

Need totals per group? Chain it:

_.chain(orders)
 .groupBy('status')
 .mapValues(group => _.sumBy(group, 'total'))
 .value();
// => { paid: 125, pending: 30 }

If you want one-row-per-key (primary key behavior) use _.keyBy instead — last write wins.

5.6 pick / omit specific keys

const user = { id: 1, name: 'fred', email: '[email removed]', passwordHash: '...' };

_.pick(user, ['id', 'name', 'email']);
// => { id: 1, name: 'fred', email: '[email removed]' }

_.omit(user, ['passwordHash']);
// => { id: 1, name: 'fred', email: '[email removed]' }

For dynamic predicates use the By variants:

_.pickBy(user, (value, key) => !key.toLowerCase().includes('password'));
_.omitBy(user, _.isNil);

5.7 Deep-equal two objects

_.isEqual(
  { a: 1, list: [1, 2, { x: 3 }] },
  { a: 1, list: [1, 2, { x: 3 }] }
);
// => true

_.isEqual(new Date('2026-01-01'), new Date('2026-01-01'));
// => true

_.isEqual walks both values recursively, comparing arrays by index, plain objects by their own enumerable keys, and built-ins (Date, RegExp, Map, Set, ArrayBuffer, typed arrays) by content. For custom equivalence rules use _.isEqualWith(value, other, customizer).

5.8 Partial application and currying

function fetchAt(host, path, query) {
  return fetch(`https://${host}${path}?${query}`);
}

// Pre-fill the host:
const fromAPI = _.partial(fetchAt, 'api.example.com');
fromAPI('/users', 'page=1');

// Or auto-curry:
const fetchCurried = _.curry(fetchAt);
const fromAPIPath  = fetchCurried('api.example.com')('/users');
fromAPIPath('page=1');

Use _.partialRight to fix the trailing arguments. Use _.curryRight to curry from the right.

5.9 The FP pipe pattern

import fp from 'lodash/fp';

const summarizeUsers = fp.pipe(
  fp.filter(u => u.active),
  fp.map(u => ({ name: u.name, age: u.age })),
  fp.sortBy('age'),
  fp.take(5)
);

summarizeUsers(allUsers);

fp.pipe is an alias of fp.flow — both apply functions left-to-right. fp.flowRight (alias fp.compose) goes right-to-left.

5.10 _.template for safe string interpolation

const greet = _.template('Hello, <%= name %>! You have <%- count %> messages.');

greet({ name: 'Fred', count: 3 });
// => 'Hello, Fred! You have 3 messages.'

greet({ name: '<script>', count: 0 });
// => 'Hello, <script>! You have 0 messages.'
//                ^^^^^^^^ <-- NOT escaped because <%= ... %> is interpolate, not escape

Three delimiter forms:

Delimiter Behavior
<%= value %> Insert raw — equivalent to string concatenation
<%- value %> Insert HTML-escaped (<, >, &, ", ', `)
<% js code %> Execute JavaScript inside the template (for, if, print)

Caution

_.template runs as JavaScript in the same realm — never feed it a template string sourced from user input. Treat it as you would eval. The lodash maintainers have flagged _.template as insecure and slated for removal in v5; for new code prefer template literals or a dedicated templating library. See section 7 of the Administrator & Developer Guide for the full security analysis.

5.11 Path-based property access

const order = { customer: { addresses: [{ city: 'Phoenix' }] } };

_.get(order, 'customer.addresses[0].city');         // => 'Phoenix'
_.get(order, ['customer', 'addresses', 0, 'city']); // => 'Phoenix' (array form)
_.get(order, 'customer.addresses[1].city', 'N/A');  // => 'N/A' (default)

_.has(order, 'customer.addresses[0].city');         // => true
_.set(order, 'customer.addresses[0].zip', '85001'); // mutates order

Path-based access avoids the optional-chaining noise of order?.customer?.addresses?.[0]?.city and works with both string and array forms. For functional-style updates use _.update:

_.update(order, 'customer.addresses[0].city', city => city.toUpperCase());

5.12 Memoize an expensive computation

const slowComputation = (id) => /* expensive lookup */ db.findOne(id);
const cached = _.memoize(slowComputation);

cached(42); // hits db
cached(42); // returns cached value

The cache lives on cached.cache (a Map-like object). To customize the cache key for multi-arg functions pass a resolver:

const join = _.memoize((a, b) => `${a}::${b}`, (a, b) => `${a}|${b}`);

Warning

_.memoize keeps every result forever — it has no TTL, no size cap, and no LRU eviction. For functions called with unbounded inputs (request IDs, timestamps), wrap with your own bounded cache or clear cached.cache.clear() periodically.

6. Troubleshooting

Error: ReferenceError: assignWith is not defined (lodash-es 4.18.0 template)

Cause: A regression in lodash-es 4.18.0 left _.template referencing private helpers (assignWith, baseAssignValue) that were not exported at the module level. Tracked as issue #6167.

Solution:

  1. Upgrade past 4.18.0: npm install lodash-es@latest. The fix landed in a subsequent patch.
  2. If you cannot upgrade, switch to the CJS lodash package: npm install lodash and import _ from 'lodash'.
  3. As a third option, use the per-method package directly: npm install lodash.template.

Prevention: Pin lodash-es to a version your CI verified against, and run npm audit regularly.

Error: ReferenceError: baseAssignValue is not defined from _.fromPairs

Cause: Same family of regressions as #6167 — issue #6175. lodash.fromPairs 4.18.0 referenced an internal helper that was no longer module-scoped.

Solution: Upgrade lodash.fromPairs (or lodash-es) to a version past 4.18.0. Or call the equivalent: Object.fromEntries(pairs) if your runtime supports it.

Prevention: When using per-method packages, audit each one's version and exclude 4.18.0 specifically.

_.merge mutated my source object

Cause: _.merge(target, source) mutates target. If you passed your "base config" as target you have rewritten it.

Solution: Always pass a fresh empty object as the first argument:

const merged = _.merge({}, baseConfig, override);

Prevention: Switch to lodash/fp — fp.merge is immutable by default and never mutates either input.

_.cloneDeep returns plain objects instead of class instances

Cause: _.cloneDeep only knows how to deep-clone built-in types (arrays, plain objects, dates, regex, sets, maps, typed arrays, array buffers). Class instances are cloned as plain objects with the same own enumerable properties — methods and the prototype chain are dropped.

Solution: Use _.cloneDeepWith and supply a customizer:

const copy = _.cloneDeepWith(value, v => {
  if (v instanceof MyClass) return new MyClass(v.toJSON());
});

Prevention: Reserve _.cloneDeep for plain data; for domain objects implement an explicit clone() or copy() method.

My bundle is huge after import _ from 'lodash'

Cause: A default import of the full lodash package brings in every method (~70 KB minified+gzipped). Bundlers cannot tree-shake the CJS build.

Solution: One of:

  1. Switch to lodash-es and use named imports — modern bundlers will tree-shake unused methods.
  2. Install only the per-method packages you actually use (lodash.debounce, lodash.clonedeep, etc.).
  3. Use babel-plugin-lodash or lodash-webpack-plugin to rewrite imports automatically.

Prevention: Add a bundle-size budget to CI. See section 6 of the Administrator & Developer Guide for measurement details.

_.sortedUniqBy returns surprising results

Cause: _.sortedUniqBy requires the input array to be already sorted by the iteratee — it works in O(n) by comparing only adjacent elements. On unsorted input it does the wrong thing silently. See issue #4953.

Solution: Either sort first (_.sortBy(arr, iteratee)) or use _.uniqBy(arr, iteratee) which works on any input order.

Prevention: Reach for _.uniqBy by default; use _.sortedUniqBy only when you know the input is sorted and you need the speed.

import { debounce } from 'lodash' is not tree-shaken

Cause: The main lodash package is published as CommonJS. Named imports against a CJS module pull in the whole module — bundlers cannot eliminate dead code.

Solution: Use lodash-es or per-method packages (see sections 2.2 / 2.3).

Prototype-pollution warning from npm audit

Cause: Older lodash versions had several prototype-pollution CVEs in _.set, _.merge, _.defaultsDeep, _.mapKeys, and _.mapValues (issues #6182, #6185, #6186, #6187). The current 4.18.x line includes the patches.

Solution: Upgrade to lodash@4.18.1 or later and re-run npm audit. If you cannot upgrade, validate that user-controlled input never reaches _.set(obj, userPath, ...) / _.merge(obj, userObj) — both can be coerced into writing to __proto__ or constructor.prototype on vulnerable versions.

Prevention: Treat any path-based or recursive-merge call on untrusted input as a potential prototype-pollution sink. Reject paths that contain __proto__, constructor, or prototype segments before passing them to lodash.

_.isEqual says two objects are equal but === says no

Cause: Working as intended. _.isEqual is deep structural equality; === is reference equality. Two distinct object references with identical contents will satisfy isEqual but not ===.

Solution: Use the right operator: === for "same reference", _.isEqual for "same content".

Prevention: None needed — this is the intended distinction.

_.template throws on user-supplied templates

Cause: _.template compiles its input as JavaScript. Untrusted input can contain syntax errors (which throw at compile time) or, far worse, arbitrary JS executed at render time.

Solution: Never pass user input as the template string. The data argument is safe; the template is not. For user-supplied content prefer template literals or a sandboxed templating engine.

Prevention: Treat _.template like eval. The lodash threat model explicitly calls out _.template as deprecated and slated for removal in v5.

7. FAQ

Q: Should I use lodash, lodash-es, or the per-method packages?

If you bundle for the browser and care about size, use lodash-es (named imports + tree-shaking) or per-method packages. If you ship to Node and don't care about kilobytes, the full lodash package is the simplest. Don't mix the three in one project — each one ships its own copy of the code.

Q: Why does my linter flag import _ from 'lodash'?

ESLint plugins like eslint-plugin-lodash-fp or eslint-plugin-lodash often default to forbidding the default import to enforce tree-shaking. Either disable the rule for your build target or switch to named imports from lodash-es.

Q: Is lodash maintained? Should I migrate to native JavaScript?

Lodash is on the 4.18.x line and continues to receive security and bug-fix releases. Many lodash methods do have native equivalents now (Array.prototype.flat, Object.fromEntries, optional chaining, structuredClone, etc.) — and for those, native is the right choice. But _.cloneDeep of arbitrary types, _.debounce, _.merge, _.template, _.get with a default value, _.groupBy with iteratee shorthand, and the FP variant all remain useful. Migration is a per-method judgment call, not a wholesale "drop lodash" decision.

Q: How do I reset _.templateSettings to defaults?

There is no built-in reset. Restore each delimiter by hand or, before mutating, capture the original:

const originalTemplateSettings = { ..._.templateSettings };
// ... do work that mutates _.templateSettings ...
_.assign(_.templateSettings, originalTemplateSettings);

Q: Why does _.cloneDeep claim to handle Maps and Sets but my code shows it doesn't?

Make sure you are on a current 4.18.x. Map/Set support was added in 4.x — older code paths or shimmed environments may report otherwise.

Q: Can I add my own functions to lodash?

Yes — _.mixin({ name: fn }) attaches name to _ (and to chain wrappers, so _(value).name() works). For a fresh isolated lodash instance, see _.runInContext().

Q: How do I configure lodash/fp to not curry / not be immutable?

Pass overrides to lodash/fp.convert:

const fp = require('lodash/fp');
const mutable = fp.convert({ immutable: false });
mutable.merge(obj, src); // mutates obj (like the standard build)

The five flags are cap, curry, fixed, immutable, rearg — each defaults to true. See section 4.4 above.

Q: Why is _.first listed in the docs but _.head is what I see in code?

_.first is an alias of _.head. Both work identically. The canonical name is _.head; _.first is preserved for compatibility.

Q: What's _.placeholder and why does the FP build expose __?

In curried calls, _ (or __ in fp) is a placeholder that says "leave this slot empty for later":

const greet = _.curry((salute, name) => `${salute}, ${name}!`);
const hi = greet(_, 'fred');   // delays the salute argument
hi('Hi');                      // => 'Hi, fred!'

In lodash/fp the placeholder is fp.placeholder (also exported as fp.__).

Q: Is there a way to seed _.random for reproducible tests?

No — _.random calls Math.random() directly. The lodash team has historically declined seeded random as out-of-scope (issue #3289). Use a seeded PRNG library like seedrandom and inject it where you need determinism.

Q: Does lodash work in Deno or Bun?

Yes — both support the CommonJS and ESM builds. For Deno, prefer lodash-es via npm specifier or a JSR-mirrored ESM URL.