Lodash _.merge() is a JavaScript utility method that recursively deep merges properties of source objects into a destination object. Unlike Object.assign() or the spread operator (...), _.merge() handles nested objects and arrays without overwriting inner properties — making it ideal for merging configuration objects, API responses, and complex state updates.
_.merge(destinationObject, sourceObject1, sourceObject2, ...)
Here is a quick example that shows how _.merge() works:
const defaults = { theme: "light", editor: { fontSize: 14, lineNumbers: true } };
const userPrefs = { editor: { fontSize: 18 } };
const config = _.merge(defaults, userPrefs);
Notice how editor.lineNumbers is preserved. With Object.assign() or spread, the entire editor object would be replaced, and lineNumbers would be lost.
In this guide, you will learn how _.merge() works, how to deep merge objects and arrays, how to customize merge behavior with _.mergeWith(), and when to use alternatives like Object.assign or the spread operator instead.
🔍 Working with Lodash? Also check out our guides on Lodash _.sortBy() for sorting arrays and objects, and Lodash _.groupBy() for grouping data by key.
How to Install and Import Lodash Merge#
Before using _.merge(), you need Lodash in your project. Here are the most common ways to set it up:
Install the full Lodash library:
npm install lodash
Import _.merge() in your code:
import merge from 'lodash/merge';
const merge = require('lodash/merge');
import _ from 'lodash';
Lightweight alternative — install only the merge module:
npm install lodash.merge
import merge from 'lodash.merge';
> Tip: Importing lodash/merge instead of the full lodash library can reduce your bundle size significantly. The full Lodash library is ~70KB (minified + gzipped), while lodash.merge alone is ~6KB.
Lodash Merge Syntax and Parameters#
Here is the complete syntax for _.merge():
_.merge(object, [sources])
| Parameter |
Type |
Description |
object |
Object |
The destination object. This object is mutated directly. |
[sources] |
...Object |
One or more source objects to merge into the destination. |
| Returns |
Object |
Returns the modified destination object. |
Important: _.merge() mutates the original destination object. If you want to keep the original intact, pass an empty object as the first argument:
const result = _.merge({}, object1, object2);
Merging Two Objects (Top-Level Merge)#
The most common use case for _.merge() is combining two objects. Properties from the source object are copied into the destination. If both objects share a key, the source value overwrites the destination value.
const object1 = { a: 1, b: 2 };
const object2 = { b: 3, c: 4 };
const result = _.merge(object1, object2);
console.log(result);
Here, b existed in both objects, so the value from object2 (which is 3) takes priority.
You can also merge more than two objects at once:
const obj1 = { a: 1 };
const obj2 = { b: 2 };
const obj3 = { c: 3, a: 99 };
const result = _.merge(obj1, obj2, obj3);
console.log(result);
The rightmost object always wins when keys conflict.
Deep Merge Nested Objects with Lodash#
This is where _.merge() truly stands out. When objects contain nested properties, _.merge() recursively walks into each level and merges them — instead of replacing the entire nested object.
const object1 = {
user: {
name: "Alice",
address: { city: "Kolkata", zip: "700001" }
}
};
const object2 = {
user: {
address: { city: "Mumbai" },
role: "admin"
}
};
const result = _.merge({}, object1, object2);
console.log(result);
Notice what happened:
user.name ("Alice") was preserved because object2 didn't have it.
user.address.city was updated to "Mumbai".
user.address.zip ("700001") was preserved — this is the deep merge behavior.
user.role ("admin") was added from object2.
With Object.assign() or the spread operator, the entire user.address object would have been replaced, losing the zip property.
Lodash Merge Arrays — How It Actually Works#
This is a commonly misunderstood behavior. Unlike what many articles incorrectly state, _.merge() does NOT concatenate arrays. Instead, it merges arrays by index — meaning element at index 0 of the source replaces element at index 0 of the destination, and so on.
const arr1 = [1, 2, 3];
const arr2 = [10, 20];
const result = _.merge([], arr1, arr2);
console.log(result);
Here is what happened:
- Index 0:
1 was replaced by 10
- Index 1:
2 was replaced by 20
- Index 2:
3 was preserved (no corresponding index in arr2)
Lodash Merge Array of Objects#
When arrays contain objects, _.merge() deep merges the objects at matching indices:
const users1 = [
{ id: 1, name: "Alice", role: "developer" },
{ id: 2, name: "Bob" }
];
const users2 = [
{ id: 1, name: "Alice", role: "lead" },
{ id: 2, name: "Bob", role: "designer" }
];
const result = _.merge([], users1, users2);
console.log(result);
The objects at each index were deep merged — not replaced or concatenated.
How to Actually Concatenate Arrays During Merge#
If you need to concatenate arrays instead of merging by index, use _.mergeWith() with a customizer function:
const object1 = { tags: ["javascript", "lodash"] };
const object2 = { tags: ["node", "npm"] };
const result = _.mergeWith({}, object1, object2, (objValue, srcValue) => {
if (_.isArray(objValue)) {
return objValue.concat(srcValue);
}
});
console.log(result);
Customize Merge Behavior with _.mergeWith()#
_.mergeWith() accepts a customizer function as the last argument. This function is called for each property being merged, giving you full control over the merge logic.
Syntax:
_.mergeWith(object, sources, customizer)
The customizer function receives these arguments:
customizer(objValue, srcValue, key, object, source)
| Argument |
Description |
objValue |
Current value in the destination object |
srcValue |
Value from the source object |
key |
The property key being merged |
object |
The destination object |
source |
The source object |
When the customizer returns undefined, the default merge behavior is used.
Example: Keep the Larger Value#
const scores1 = { math: 85, science: 92 };
const scores2 = { math: 90, science: 88 };
const result = _.mergeWith({}, scores1, scores2, (objValue, srcValue) => {
if (typeof objValue === 'number' && typeof srcValue === 'number') {
return Math.max(objValue, srcValue);
}
});
console.log(result);
Example: Deduplicate Arrays During Merge#
const config1 = { plugins: ["auth", "logger"] };
const config2 = { plugins: ["logger", "cache"] };
const result = _.mergeWith({}, config1, config2, (objValue, srcValue) => {
if (_.isArray(objValue)) {
return _.union(objValue, srcValue);
}
});
console.log(result);
Lodash Merge vs Object.assign vs Spread Operator#
This is one of the most commonly asked comparisons. Here is exactly how they differ:
| Feature |
_.merge() |
Object.assign() |
Spread {...} |
| Deep merge |
✅ Yes — recursively merges nested objects |
❌ No — replaces nested objects entirely |
❌ No — replaces nested objects entirely |
| Array handling |
Merges arrays by index |
Replaces arrays entirely |
Replaces arrays entirely |
| Mutates destination |
✅ Yes |
✅ Yes |
❌ No — creates a new object |
Handles undefined |
Skips undefined values |
Copies undefined values |
Copies undefined values |
| Prototype properties |
Copies inherited properties |
Only own enumerable properties |
Only own enumerable properties |
| Bundle size |
~6KB (lodash.merge) |
0KB (native) |
0KB (native) |
| Browser support |
All (via npm) |
ES6+ |
ES2018+ |
Side-by-Side Code Comparison#
const target = { a: 1, nested: { b: 2, c: 3 } };
const source = { nested: { c: 99 } };
_.merge({}, target, source);
Object.assign({}, target, source);
{ ...target, ...source };
When to use which:
- Use
_.merge() when you need to deep merge nested objects or arrays
- Use
Object.assign() for flat, one-level object merging
- Use spread
{...} when you want a quick shallow copy/merge without mutation
Practical Examples of Lodash Merge#
Merging Configuration Objects#
One of the most common real-world uses — combining default settings with user overrides:
const defaultConfig = {
server: { port: 3000, host: "localhost" },
database: { host: "localhost", port: 5432, name: "myapp" },
logging: { level: "info", format: "json" }
};
const envConfig = {
server: { port: 8080 },
database: { host: "prod-db.example.com", name: "myapp_prod" },
logging: { level: "warn" }
};
const config = _.merge({}, defaultConfig, envConfig);
console.log(config);
All default values are preserved unless explicitly overridden.
Updating User Profiles#
When a user updates their profile, you typically want to modify specific fields without losing existing data:
const userProfile = {
username: "john_doe",
bio: "Web developer",
social: {
twitter: "johndoe_twitter",
linkedin: "johndoe_linkedin"
},
preferences: {
theme: "dark",
notifications: { email: true, push: false }
}
};
const updates = {
bio: "Full-stack developer",
social: { github: "johndoe_github" },
preferences: { notifications: { push: true } }
};
const updatedProfile = _.merge({}, userProfile, updates);
console.log(updatedProfile);
Every existing field is preserved. Only the specified updates are applied.
Merging API Responses#
When fetching paginated or partial data from APIs:
const cachedData = {
users: [
{ id: 1, name: "Alice", lastSeen: "2025-01-01" }
],
meta: { page: 1, total: 100 }
};
const freshData = {
users: [
{ id: 1, name: "Alice", lastSeen: "2025-02-09", online: true }
],
meta: { page: 1, total: 102 }
};
const merged = _.merge({}, cachedData, freshData);
> Next steps after merging: Once your data is merged, you can sort it with _.sortBy() or group it by category with _.groupBy() for display.
Lodash Merge with TypeScript#
If you are using TypeScript, _.merge() works with proper type inference:
import merge from 'lodash/merge';
interface Config {
server: { port: number; host: string };
debug: boolean;
}
const defaults: Config = {
server: { port: 3000, host: "localhost" },
debug: false
};
const overrides: Partial<Config> = {
server: { port: 8080, host: "localhost" }
};
const config: Config = merge({}, defaults, overrides);
Install type definitions if you are using the modular package:
npm install @types/lodash
Common Pitfalls and Edge Cases#
1. Mutation — _.merge() Changes the Original Object#
const original = { a: 1, b: { c: 2 } };
const source = { b: { d: 3 } };
_.merge(original, source);
console.log(original);
Fix: Always pass an empty object as the first argument if you want to keep originals intact:
const result = _.merge({}, original, source);
2. undefined Values Are Skipped#
const obj1 = { a: 1, b: 2 };
const obj2 = { a: undefined, b: 3 };
_.merge({}, obj1, obj2);
This is different from Object.assign(), which would set a to undefined.
3. Circular References Can Cause Issues#
_.merge() does handle circular references internally using a stack-based approach, but deeply circular structures can lead to unexpected results. Avoid passing objects with circular references when possible.
4. Prototype Pollution Vulnerability#
This is a security concern. Older versions of Lodash (before 4.17.12) were vulnerable to prototype pollution via _.merge(). An attacker could inject properties into Object.prototype through crafted input:
const malicious = JSON.parse('{"__proto__": {"isAdmin": true}}');
_.merge({}, malicious);
How to protect yourself:
- Always use Lodash 4.17.21 or later (latest stable)
- Sanitize user input before passing it to
_.merge()
- Consider using
Object.create(null) for sensitive merge targets
Check your installed version:
npm list lodash
_.merge() is fast enough for most applications, but deep merging has a cost:
- Time complexity: O(n) where n is the total number of properties across all levels
- Space complexity: O(d) where d is the maximum nesting depth (due to recursion stack)
Tips for better performance:
- Only deep merge when necessary — use
Object.assign() or spread for flat objects
- Import
lodash/merge instead of the full Lodash library to reduce bundle size
- For very large or deeply nested objects, consider using Immer which uses structural sharing
Native Alternatives to Lodash Merge in 2025#
Modern JavaScript now offers native options that may reduce your dependency on Lodash:
structuredClone() + Spread (ES2022+)#
const result = { ...structuredClone(target), ...source };
This handles deep cloning but not deep merging — nested objects still get replaced.
Custom Recursive Deep Merge#
function deepMerge(target, source) {
const result = { ...target };
for (const key of Object.keys(source)) {
if (
source[key] && typeof source[key] === 'object' && !Array.isArray(source[key]) &&
target[key] && typeof target[key] === 'object' && !Array.isArray(target[key])
) {
result[key] = deepMerge(target[key], source[key]);
} else {
result[key] = source[key];
}
}
return result;
}
This works for simple cases but lacks the edge case handling that _.merge() provides (inherited properties, typed arrays, buffers, etc.).
When to Still Use Lodash Merge#
Use _.merge() when you need:
- Deep merging of nested objects with reliable edge case handling
- Array-by-index merging behavior
- Custom merge logic via
_.mergeWith()
- Battle-tested, well-maintained code in production
Use native alternatives when:
- You only need shallow merging (spread or
Object.assign())
- Bundle size is critical and you have simple merge needs
- Your project already avoids external dependencies
If you are working with Lodash for data manipulation, these guides cover the other essential methods:
FAQs#
What is the difference between Lodash merge and assign?#
_.merge() performs a deep recursive merge — it walks into nested objects and merges their individual properties. _.assign() (and Object.assign()) performs a shallow copy — nested objects are replaced entirely, not merged. Use _.merge() when you have nested data structures that need to be combined without losing inner properties.
Does Lodash merge concatenate arrays?#
No. _.merge() merges arrays by index, not by concatenation. Element at index 0 of the source overwrites element at index 0 of the destination. If you need to concatenate arrays during a merge, use _.mergeWith() with a customizer function that calls concat() on array values.
Does Lodash merge mutate the original object?#
Yes. _.merge() modifies the destination object (the first argument) directly and returns it. To avoid mutation, pass an empty object {} as the first argument: _.merge({}, obj1, obj2).
Can I use Lodash merge in Node.js?#
Yes. _.merge() works in both browser and Node.js environments. Install via npm install lodash and import with const merge = require('lodash/merge') or import merge from 'lodash/merge'.
Is Lodash merge safe from prototype pollution?#
Lodash versions 4.17.12 and later include fixes for known prototype pollution vulnerabilities in _.merge(). Always use the latest version (4.17.21+) and sanitize any user-provided input before merging.
What can I use instead of Lodash merge?#
For shallow merging, use the native spread operator ({...obj1, ...obj2}) or Object.assign(). For deep merging without Lodash, you can write a custom recursive merge function or use libraries like deepmerge. However, _.merge() remains the most battle-tested option for complex deep merging scenarios.
Comments
Sign in to join the discussion.
No comments yet. Be the first to share your thoughts.