Lodash Cheatsheet - Array, Object & Collection Methods

This reference is for JS devs who would rather use a well-tested helper than reimplement the same loop by hand. Entries span arrays, objects, strings, collections, and function utilities, with each command showing the exact call that solves a stubborn problem — like reading a deeply nested value that may not exist, deep-cloning to avoid mutating shared state, or debouncing an input handler. Unlike native methods, Lodash versions handle nullish inputs and edge cases for you. After reading you should be able to pick the right helper for dedupe, grouping, debounce, and safe deep merge.

Languages·44 commands·Last updated 2026-07-21
lodashjavascriptUtility LibrariesFrontend

Typical Use Case

For developers who want clean data-processing code without pulling in a framework, but who also care about bundle size. Lodash earns its place in four situations: safe operations on deeply nested objects, where get and set do not throw when an intermediate level is missing; collection-style data work such as groupBy, keyBy and multi-field sortBy; frequency control through debounce for search input and throttle for scroll and resize; and small string and number helpers such as camelCase, clamp and round. This sheet also records the judgement for which of these the native equivalents now cover, plus the per-function import pattern for keeping size down — so a single debounce does not pull the whole library into your bundle.

Array 7

_.map(arr, (n) => n * 2)
Map to a new array
_.filter(arr, (n) => n > 0)
Filter matching elements
_.reduce(arr, (sum, n) => sum + n, 0)
Reduce / accumulate
_.chunk(arr, 2)
Chunk into groups of 2
_.flattenDeep([1, [2, [3]]])
Deep flatten
_.uniq([1, 2, 1, 3])
Deduplicate an array
_.union([1, 2], [2, 3])
Union multiple arrays (deduped)

Object 7

_.get(obj, "a.b.c", "default")
Safely get a nested property
_.set(obj, "a.b.c", value)
Safely set a nested property
_.pick(obj, ["name", "age"])
Pick specified properties
_.omit(obj, ["password"])
Omit specified properties
_.merge(target, source)
Deep-merge objects
_.cloneDeep(obj)
Deep clone
_.keys(obj) / _.values(obj)
Get keys / values arrays

String 6

_.camelCase("Foo Bar")
Convert to camelCase
_.kebabCase("FooBar")
Convert to kebab-case
_.template("<%= name %>")({ name: "Vue" })
Compile a template string
_.escape("<a>")
HTML escape
_.trim(" abc ")
Trim whitespace
_.pad("abc", 8)
Pad to a length

Collection 6

_.groupBy(users, "age")
Group by a property
_.sortBy(users, ["age", "name"])
Sort by multiple properties
_.countBy([1, 2, 2, 3], Math.floor)
Count by a rule
_.keyBy(users, "id")
Key an array by a property
_.sampleSize(arr, 2)
Sample n random elements
_.shuffle(arr)
Shuffle

Function 6

_.debounce(fn, 300)
Debounce (runs after idle)
_.throttle(fn, 300)
Throttle (fixed rate)
_.memoize(fn)
Memoize results
_.once(fn)
Run once
_.partial(fn, _, 2)
Partial application
_.bind(fn, thisArg, arg1)
Bind this and args

Number & Math 5

_.random(0, 100)
Random integer 0-100
_.range(0, 5)
Range [0, 1, 2, 3, 4]
_.clamp(num, 0, 100)
Clamp to a range
_.inRange(num, 0, 100)
Check if in range
_.round(1.234, 2)
Round to 2 decimals

Utility & Type 7

_.isEmpty(value)
Check if empty
_.isEqual(a, b)
Deep equality
_.isNil(value)
Check null or undefined
_.isArray(value) / _.isObject(value)
Type checks (array/object)
_.has(obj, "a.b")
Check a property path exists
_.defaults({ a: 1 }, { a: 0, b: 2 })
Fill in undefined properties
_.noop()
No-op placeholder

Parameter matrix

ParameterEffectExample
_.map(arr, fn)Transform each item into a new array, leaving the original unchanged_.map(arr, (n) => n * 2)
_.filter(arr, fn)Keep the elements for which the callback returns truthy_.filter(arr, (n) => n > 0)
_.reduce(arr, fn, init)Reduce an array to a single accumulated value_.reduce(arr, (sum, n) => sum + n, 0)
_.chunk(arr, size)Split an array into fixed-size chunks, good for batch processing_.chunk(arr, 2)
_.flattenDeep(nested)Flatten arbitrarily nested arrays to one level_.flattenDeep([1, [2, [3]]])
_.uniq(arr)Deduplicate while preserving first-occurrence order_.uniq([1, 2, 1, 3])
_.union([a], [b])Merge multiple arrays and deduplicate to unique values_.union([1, 2], [2, 3])
_.get(obj, "a.b.c", def)Safely read a deep property, falling back to a default when missing_.get(obj, "a.b.c", "default")
_.set(obj, "a.b.c", v)Safely write a deep property, auto-creating intermediate levels_.set(obj, "a.b.c", value)
_.pick(obj, keys)Pick only the specified keys into a new object_.pick(obj, ["name", "age"])
_.omit(obj, keys)Drop the specified keys, often to strip sensitive fields_.omit(obj, ["password"])
_.merge(target, source)Deep merge; nested objects merge by level instead of being replaced wholesale_.merge(target, source)
_.cloneDeep(obj)Recursive deep copy: nested objects, arrays and dates are duplicated too, so mutating the clone can never write through to the original reference._.cloneDeep(obj)
_.keys(obj) / _.values(obj)One call gives the array of own enumerable key names, the other gives the array of the matching values, in the same iteration order._.keys(obj) / _.values(obj)
_.camelCase(str)Rewrites a string into lower camelCase, so "Foo Bar" becomes "fooBar"._.camelCase("Foo Bar")
_.kebabCase(str)Rewrites a string into lower kebab-case, so "FooBar" becomes "foo-bar"._.kebabCase("FooBar")
_.template(str)(data)Compiles the template into a reusable function once, then fills it with data on every call. Parsing up front keeps repeated renders cheap._.template("<%= name %>")({ name: "Vue" })
_.escape(str)Escapes the characters that carry markup meaning, so user-supplied text is rendered literally instead of being parsed as a tag._.escape("<a>")
_.trim(str) / _.pad(str, len)trim removes leading and trailing whitespace; pad grows the string with filler on both sides until it reaches the requested length, for fixed-width output._.trim(" abc ")
_.groupBy(arr, "age")Buckets the array into an object keyed by the value of the given property (or by what the iteratee returns), collapsing a manual loop into one pass._.groupBy(users, "age")
_.sortBy(arr, ["age", "name"])Orders the array by comparing the listed properties in sequence, using the next property only to break ties left by the previous one._.sortBy(users, ["age", "name"])
_.countBy(arr, fn)Counts how often each derived value occurs and returns them as an object of key to count, a histogram in a single call._.countBy([1, 2, 2, 3], Math.floor)
_.keyBy(arr, "id")Rebuilds the array as an object keyed by the chosen property, so a collection becomes a lookup table and every element is reachable in constant time by id._.keyBy(users, "id")
_.sampleSize(arr, n)Draws n elements at random without replacement, so no element can appear twice even if it occurs several times in the source._.sampleSize(arr, 2)
_.shuffle(arr)Fisher-Yates shuffle that returns a new array with the order randomised, leaving the input array as it was._.shuffle(arr)
_.debounce(fn, 300)Debounce: the wrapped function runs only once, after calls have stopped for the given milliseconds, so a burst of events collapses into a single execution._.debounce(fn, 300)
_.throttle(fn, 300)Throttle: the wrapped function runs at most once per window, so a fast stream is rate limited instead of being executed on every event._.throttle(fn, 300)
_.memoize(fn)Caches the return value and reuses it whenever the arguments are equal, which pays off for expensive pure functions called with the same inputs._.memoize(fn)
_.once(fn)Wraps the function so that only the very first call reaches the original; later calls return the memoised first result and the body never runs again._.once(fn)
_.partial(fn, _, 2)Partial application: the listed arguments are bound ahead of time and the placeholder marks the position that stays open, producing a smaller function to call later._.partial(fn, _, 2)
_.bind(fn, thisArg, arg1)Binds a this value and a prefix of arguments, returning a function that supplies them automatically. Useful for methods detached from their object._.bind(fn, thisArg, arg1)
_.random(0, 100)Returns a random integer from the lower bound up to but excluding the upper bound, which suits dice rolls, sampling and test data._.random(0, 100)
_.range(0, 5)Builds the array of numbers from the lower bound up to but excluding the upper bound, so _.range(0, 5) yields 0, 1, 2, 3, 4._.range(0, 5)
_.clamp(num, 0, 100)Constrains a number to stay within the given bounds, returning the value itself when it already sits inside the range._.clamp(num, 0, 100)
_.inRange(num, 0, 100)Reports whether a number falls inside the given bounds, with the lower bound inclusive and the upper bound exclusive._.inRange(num, 0, 100)
_.round(1.234, 2)Rounds a number half away from zero to the requested number of decimal places, fixing the floating point noise that appears in totals._.round(1.234, 2)
_.isEmpty(value)Reports whether a value is empty, covering empty arrays, empty objects, empty strings, empty maps and sets. A plain null or undefined is not considered empty here, so pair it with isNil._.isEmpty(value)
_.isEqual(a, b)Deep equality check that walks nested structures and compares values recursively, far more reliable than the identity comparison provided by the == operator._.isEqual(a, b)
_.isNil(value)Tests specifically for null or undefined, so both spellings of an absent value are covered by one check._.isNil(value)
_.isArray / _.isObjectRuntime type guards for arrays and plain objects, useful before reaching into a value whose shape is not guaranteed._.isArray(value) / _.isObject(value)
_.has(obj, "a.b")Reports whether the property exists at the given path, following dots into nested objects, without caring whether the stored value is truthy or falsy._.has(obj, "a.b")
_.defaults({ a: 1 }, { a: 0, b: 2 })Assigns each source property to the destination only when the destination leaves it undefined, so explicit values already set on the first argument always win._.defaults({ a: 1 }, { a: 0, b: 2 })
_.noop()Does nothing and returns undefined. Handy as a safe default for a callback parameter that a caller may choose not to supply._.noop()

Common pitfalls

SymptomThe search box fires a request on every keystroke, hammering the backend when you type fast.

CauseRemote search is bound directly to the input event with no rate control.

FixSwitch to _.debounce(fn, 300): it fires one request only after 300ms of no triggering, rather than on every keystroke.

SymptomThe page stutters and drops frames noticeably during scroll or resize.

CauseHeavy computation or layout work runs on high-frequency events with no limit on execution frequency.

FixUse _.throttle(fn, 300) to cap handling at a fixed frequency; throttling suits this kind of continuous triggering rather than waiting for it to stop.

SymptomUser input containing a < tag is rendered as real HTML, breaking the page structure.

CauseUnescaped text is inserted directly into HTML.

FixEscape the HTML with _.escape(str) before inserting, or switch to writing plain text via textContent.

SymptomImporting the whole lodash package adds several hundred KB to the bundle.

CauseWith a whole-package import, bundlers struggle to tree-shake unused functions in all cases.

FixSwitch to the lodash-es ES Module build, or import single functions on demand: import cloneDeep from "lodash/cloneDeep", so the bundler can tree-shake the rest.

SymptomAfter _.merge, a field in the source that should have been replaced wholesale stays in the target object.

Causemerge is a deep merge; nested objects are not overwritten wholesale but merged recursively key by key.

FixWhen you really want wholesale replacement, _.cloneDeep first and then assign, or use a processing approach with overwrite semantics instead of relying on merge to replace.

Symptom_.isEmpty returns true for an empty string or a number, so the business check for having a value is wrong.

CauseisEmpty means having no enumerable own properties, and it returns true even for primitives like 0.

FixFirst use _.isNil(value) to rule out null/undefined, then branch on truthiness or type-specific checks like _.inRange.

Symptom_.get(obj, "a.b.c") keeps falling back to the default even though the data has a value.

CauseThe path is given as a string, and if a key name itself contains a dot (such as "a.b" being a single key), it gets split as a nested path.

FixPass the path as an array, _.get(obj, ["a.b", "c"]), to make key boundaries explicit and avoid the dot ambiguity.

Troubleshooting

  1. 1Safely read a deep property and observe the fallback value

    _.get(obj, "a.b.c", "default")

    When a or a.b does not exist it does not throw but returns "default", so you can confirm where the chain breaks.

  2. 2Confirm whether an array is really empty

    _.isEmpty(value)

    Works for arrays, objects and empty strings; it returns true for numbers, so pair it with _.isNil.

  3. 3Deep clone and modify the copy to verify it does not pollute the original

    _.cloneDeep(obj)

    Change a nested field on the copy and print the original; if the original is unchanged, the deep clone worked.

  4. 4View the result of deduplication and merging

    _.union([1, 2], [2, 3])

    Outputs [1, 2, 3], confirming the unique-value merge semantics behave as expected.

  5. 5Add debounce to high-frequency events

    _.debounce(fn, 300)

    Wrap fn and assign the wrapper to the event handler; it runs only the last call after continuous triggering ends.

  6. 6Import single functions to keep the bundle small

    import cloneDeep from "lodash/cloneDeep"

    Pulling in only the helper you call lets the bundler tree-shake the rest of lodash away, since each module lives in its own file under the lodash package root.

Command Examples

Debounced search plus throttled scroll handler

const onSearch = _.debounce(function (q) { $.getJSON("/api/search?q=" + q, render) }, 300) const onScroll = _.throttle(function () { updateParallax() }, 100) $("#q").on("input", (e) => onSearch(e.target.value))

debounce waits for input to stop before firing, which suits search and autosave; throttle fires at a fixed rate for continuous events like scroll and resize. Note that with default options the first call is delayed to the end of the quiet period.

omit sensitive fields and read nested config with get

const safe = _.omit(user, ["password", "token"]) const timeout = _.get(config, "http.timeout", 3000) _.set(config, "http.timeout", 5000)

A centralized allowlist-style serializer (omit) is far easier to keep complete than per-endpoint field lists. get returns its default only when the resolved value is undefined; an explicitly stored null comes back untouched.

groupBy, keyBy indexing, and multi-key sortBy

const byAge = _.groupBy(users, "age") const byId = _.keyBy(users, "id") const sorted = _.sortBy(users, ["age", "name"]) const counts = _.countBy([1, 2, 2, 3], Math.floor)

Repeating users.find(u => u.id === id) inside a loop is an O(n) scan, while keyBy turns the array into an object once for O(1) lookups. The array form of sortBy compares keys in order, and that signature is the one most often misused.

memoize an expensive computation and once-guard initialization

const expensive = _.memoize(function (key) { return heavyLookup(key) }) const init = _.once(function () { client.start() })

memoize keys on the first argument and is safest for pure functions; passing objects makes the cache miss because each reference differs. once guards module-level initialization against repeated requires or double clicks.

Cleaning pipeline: chunk, flattenDeep, uniq

const ids = _.uniq(_.flattenDeep(raw)); const pages = _.chunk(ids, 100); for (const page of pages) { upload(page) }

External APIs often return mixed arrays with nulls and nested structures. flattenDeep normalizes them, uniq removes duplicate ids, and chunking submits in fixed-size batches instead of one oversized body the gateway would reject.

Common Pitfalls

  • _.merge(target, source) mutates and returns its first argument, so a config object shared across requests gets silently dirtied. Merge into _.cloneDeep instead.
  • _.uniq and _.union only do a SameValueZero shallow comparison and will not remove duplicate objects from an array of objects. Use _.uniqWith(arr, _.isEqual) for content-based deduplication.
  • _.get(obj, "a.b", fallback) only falls back when the resolved value is undefined. An explicitly stored null is returned as null, and every downstream check written against the default then fails.
  • <%= %> in _.template performs no HTML escaping, so interpolating user input is an XSS hole. Reserve the raw <%- %> tag for content that really is HTML.
  • _.memoize has no cache bound and keys on argument values; calling it per record turns the whole dataset into cache entries, showing up as memory that only grows on long-lived pages.
  • _.debounce and _.throttle each return a new function, so $(el).on("click", _.debounce(fn, 300)) creates a fresh debounced instance per binding. The net effect is throttling, or multiple executions, rather than debouncing.

Tips

  • lodash-es is the ESM build with tree-shaking for smaller bundles.
  • _.get/_.set safely handle deeply nested objects without undefined errors.
  • Use debounce for search inputs, throttle for scroll/resize.
  • Modern JS can replace some Lodash with natives like flat() and Object.keys().
  • Import a single method (e.g. lodash/cloneDeep) to shrink the bundle further.

FAQ

What is the practical timing difference between debounce and throttle?

_.debounce(fn, 300) measures from the last trigger: every new call resets the timer and the function only runs once, 300ms after activity stops, which is exactly right for a search input where you want to fire after typing finishes. _.throttle(fn, 300) runs at a fixed frequency — no matter how many events arrive, it executes at most once per 300ms window and runs again in the next window if events keep coming, which suits scroll and resize where continuous feedback matters. A symptom of choosing wrong: a search box firing a burst of requests (you needed debounce), or a list that never loads its tail when you reach the bottom (you needed throttle).

When must you reach for cloneDeep/merge instead of the spread operator?

The spread {...obj} is a shallow copy: it copies one level of references, so obj.nested is still shared with the original and mutating the copy's nested field leaks back. _.cloneDeep(obj) copies recursively, so edits to the copy never touch the original — the right base for editor-style "cancel discards changes" semantics. _.merge(target, source) deep-merges instead of replacing: nested objects are merged level by level, so fields absent from source keep their target values. The dangerous pattern is {...defaults, ...user}: it works for flat objects, but the moment configuration nests it wipes whole subtrees of defaults. Merge from defaults instead.

Which function turns an array into a dictionary keyed by id?

_.keyBy(users, "id") keys the output object by that property, producing { "1": user1, "2": user2 }, and later lookups are O(1) — ideal when you index a list once before rendering. Two neighbours with different semantics: _.groupBy(users, "age") buckets by value into { 25: [user...], 30: [user...] }, collecting every matching element under one key, which is what you want when displaying grouped sections; and _.pick(obj, ["name", "age"]) selects a whitelist of keys from a single object, often paired with _.omit(obj, ["password"]) to strip sensitive fields.

Why is lodash-es with per-function imports preferred over importing the whole package?

A default import of "lodash" pulls the entire library into the build even if you only call debounce and groupBy. lodash-es is the ES Module build, so Tree Shaking can see unused named exports and drop them, and combining that with single-file imports such as import cloneDeep from "lodash/cloneDeep" means only the functions you actually call get bundled. The catch is that shaking depends on static import statements: if you pass the namespace object around or index into it at runtime, the bundler can no longer prove what is unused and shaking fails. Import style and usage style have to be designed together — named imports, and never store the library object for later.

How do _.get, _.set and _.has work together on nested objects?

All three share the same "property path string" convention. _.get(obj, "a.b.c", "default") returns the default instead of throwing when a or a.b is missing, which makes it safe for untrusted response payloads. _.set(obj, "a.b.c", value) creates the intermediate objects on the way down, saving you from null-checking each level. _.has(obj, "a.b") answers whether the path exists and works well as a required-field guard. Paths also accept arrays — _.get(obj, ["a", "b"]) — and you must use that form when a key itself contains a dot, otherwise it gets split into levels. Note that _.get only reads: it returns undefined rather than creating nodes, so use _.set when you actually want the structure built.

Official References

Each command links to its official documentation below, so you can verify the latest usage and read deeper.

Maintained by LaoHand

Publicly updated on Jul 21, 2026, continuously proofread against official docs.

Contact Us

Wrong command or description? Send us corrections, business inquiries or product feedback by email.

Contact Us