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
ReferenceErrorafter upgrading, jump to section 6.
2. Installation
Lodash supports five distinct install paths. Pick the one that matches your project.
2.1 Full library — lodash (recommended default)
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.jsondeclaresengines.node >= 4.0.0. In practice every modern Node release (LTS 18, 20, 22) is supported, but if you ship to legacy environments check thatenginesfield 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
lodashandlodash.debounceships the debounce code twice. Pick one strategy per app: fulllodash, individual per-method packages, orlodash-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-esplus named imports gives you the same per-method savings as the per-method packages — but with a single dependency inpackage.jsonand 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
lodashreference (or one pinned tolatest) lets a CDN-side breaking change reach your users without warning. Always specify@4.18.1or 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
_.templateSettingsmutates a shared global. If two libraries in the same process both overrideinterpolate, 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
lodashandlodash-esin 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_.heador_.reduce) auto-unwrap and return the raw value. Methods that return collections still return a wrapper. Use_.chainwhen 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, andGet/Or/Fromvariants used to thread argument shapes (assignAll,mergeAllWith,getOr,findFrom, etc.). These exist only inlodash/fp— the standardlodashpackage 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
_.mergemutates its first argument. Pass a fresh{}as the destination if you want a non-mutating call:_.merge({}, base, override)._.mergealso recursively merges arrays by index — to concatenate arrays use_.mergeWithwith 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
_.templateruns as JavaScript in the same realm — never feed it a template string sourced from user input. Treat it as you wouldeval. The lodash maintainers have flagged_.templateas 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
_.memoizekeeps 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 clearcached.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:
- Upgrade past 4.18.0:
npm install lodash-es@latest. The fix landed in a subsequent patch. - If you cannot upgrade, switch to the CJS
lodashpackage:npm install lodashandimport _ from 'lodash'. - 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:
- Switch to
lodash-esand use named imports — modern bundlers will tree-shake unused methods. - Install only the per-method packages you actually use (
lodash.debounce,lodash.clonedeep, etc.). - 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.